在Java 9中运行时扫描类路径/模块路径

我似乎无法找到关于是否仍然可以在运行时扫描所有可用类(用于接口,注释等)的任何信息,就像Spring,Reflections和许多其他框架和库当前所做的那样,面对Jigsaw相关的更改类的加载方式。

编辑 :这个问题是关于扫描寻找类的真实物理文件路径。 另一个问题是关于动态加载类和资源。 这是相关的,但非常重复

更新 :Jetty项目为此制定了标准化API的JEP提议 。 如果你有办法帮助实现这个目标,请做。 否则,等等和希望。

更新2 :找到这个相关的发声post。 引用后代的代码片段:

如果您真的只是想要获取引导层中模块的内容(在启动时解析的模块),那么您将执行以下操作:

ModuleLayer.boot().configuration().modules().stream() .map(ResolvedModule::reference) .forEach(mref -> { System.out.println(mref.descriptor().name()); try (ModuleReader reader = mref.open()) { reader.list().forEach(System.out::println); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } }); 

以下代码在Java 9+(Jigsaw)中实现了模块路径扫描。 它找到callstack上的所有类,然后为每个类引用调用classRef.getModule().getLayer().getConfiguration().modules() ,它返回一个List ,而不仅仅是List 。 ( ResolvedModule允许您访问模块资源,而Module不允许。)给定每个模块的ResolvedModule引用,您可以调用.reference()方法来获取模块的ModuleReferenceModuleReference#open()为您提供了一个ModuleReader ,它允许您使用ModuleReader#list()模块中的资源,或使用Optional ModuleReader#open(resourcePath)Optional ModuleReader#read(resourcePath) Optional ModuleReader#open(resourcePath) Optional ModuleReader#read(resourcePath) 。 然后,在完成模块后关闭ModuleReader 。 这在我见过的任何地方都没有记录。 要弄清楚这一切是非常困难的。 但这是代码,希望其他人能从中受益。

请注意,即使在JDK9 +中,您仍然可以使用传统的类路径元素以及模块路径元素,因此对于完整的模块路径+类路径扫描,您应该使用适当的类路径扫描解决方案,例如ClassGraph ,它支持使用以下模块扫描机制(免责声明,我是作者)。

 package main; import java.lang.StackWalker.Option; import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.net.URI; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; public class Java9Scanner { private static final class CallerResolver extends SecurityManager { /** Get classes in the call stack. */ @Override protected Class[] getClassContext() { return super.getClassContext(); } } /** Recursively find the topological sort order of ancestral layers. */ private static void findLayerOrder(ModuleLayer layer, Set visited, Deque layersOut) { if (visited.add(layer)) { List parents = layer.parents(); for (int i = 0; i < parents.size(); i++) { findLayerOrder(parents.get(i), visited, layersOut); } layersOut.push(layer); } } /** Get ModuleReferences from a Class reference. */ private static List> findModuleRefs( Class[] callStack) { Deque layerOrder = new ArrayDeque<>(); Set visited = new HashSet<>(); for (int i = 0; i < callStack.length; i++) { ModuleLayer layer = callStack[i].getModule().getLayer(); findLayerOrder(layer, visited, layerOrder); } Set addedModules = new HashSet<>(); List> moduleRefs = new ArrayList<>(); for (ModuleLayer layer : layerOrder) { Set modulesInLayerSet = layer.configuration() .modules(); final List> modulesInLayer = new ArrayList<>(); for (ResolvedModule module : modulesInLayerSet) { modulesInLayer .add(new SimpleEntry<>(module.reference(), layer)); } // Sort modules in layer by name for consistency Collections.sort(modulesInLayer, (e1, e2) -> e1.getKey().descriptor().name() .compareTo(e2.getKey().descriptor().name())); // To be safe, dedup ModuleReferences, in case a module occurs in multiple // layers and reuses its ModuleReference (no idea if this can happen) for (Entry m : modulesInLayer) { if (addedModules.add(m.getKey())) { moduleRefs.add(m); } } } return moduleRefs; } /** Get the classes in the call stack. */ private static Class[] getCallStack() { // Try StackWalker (JDK 9+) PrivilegedAction[]> stackWalkerAction = new PrivilegedAction[]>() { @Override public Class[] run() { List> stackFrameClasses = new ArrayList<>(); StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE) .forEach(sf -> stackFrameClasses .add(sf.getDeclaringClass())); return stackFrameClasses.toArray(new Class[0]); } }; try { // Try with doPrivileged() return AccessController .doPrivileged(stackWalkerAction); } catch (Exception e) { } try { // Try without doPrivileged() return stackWalkerAction.run(); } catch (Exception e) { } // Try SecurityManager PrivilegedAction[]> callerResolverAction = new PrivilegedAction[]>() { @Override public Class[] run() { return new CallerResolver().getClassContext(); } }; try { // Try with doPrivileged() return AccessController .doPrivileged(callerResolverAction); } catch (Exception e) { } try { // Try without doPrivileged() return callerResolverAction.run(); } catch (Exception e) { } // As a fallback, use getStackTrace() to try to get the call stack try { throw new Exception(); } catch (final Exception e) { final List> classes = new ArrayList<>(); for (final StackTraceElement elt : e.getStackTrace()) { try { classes.add(Class.forName(elt.getClassName())); } catch (final Throwable e2) { // Ignore } } if (classes.size() > 0) { return classes.toArray(new Class[0]); } else { // Last-ditch effort -- include just this class in the call stack return new Class[] { Java9Scanner.class }; } } } /** * Return true if the given module name is a system module. There can be * system modules in layers above the boot layer. */ private static boolean isSystemModule( final ModuleReference moduleReference) { URI location = moduleReference.location().orElse(null); if (location == null) { return true; } final String scheme = location.getScheme(); return scheme != null && scheme.equalsIgnoreCase("jrt"); } public static void main(String[] args) throws Exception { // Get ModuleReferences for modules of all classes in call stack, List> systemModuleRefs = new ArrayList<>(); List> nonSystemModuleRefs = new ArrayList<>(); Class[] callStack = getCallStack(); List> moduleRefs = findModuleRefs( callStack); // Split module refs into system and non-system modules based on module name for (Entry m : moduleRefs) { (isSystemModule(m.getKey()) ? systemModuleRefs : nonSystemModuleRefs).add(m); } // List system modules System.out.println("\nSYSTEM MODULES:\n"); for (Entry e : systemModuleRefs) { ModuleReference ref = e.getKey(); System.out.println(" " + ref.descriptor().name()); } // Show info for non-system modules System.out.println("\nNON-SYSTEM MODULES:"); for (Entry e : nonSystemModuleRefs) { ModuleReference ref = e.getKey(); ModuleLayer layer = e.getValue(); System.out.println("\n " + ref.descriptor().name()); System.out.println( " Version: " + ref.descriptor().toNameAndVersion()); System.out.println( " Packages: " + ref.descriptor().packages()); System.out.println(" ClassLoader: " + layer.findLoader(ref.descriptor().name())); Optional location = ref.location(); if (location.isPresent()) { System.out.println(" Location: " + location.get()); } try (ModuleReader moduleReader = ref.open()) { Stream stream = moduleReader.list(); stream.forEach(s -> System.out.println(" File: " + s)); } } } } 

这里的实际问题是找到类路径上所有jar和文件夹的路径。 一旦你拥有它们,你就可以扫描。

我做的是以下内容:

  • 获取当前类的当前模块描述符
  • 得到所有requires模块
  • 对于每个这样的模块,打开MANIFEST.MF资源
  • 从资源URL中删除MANIFEST.MF路径
  • 剩下的是模块的类路径,即它的jar或文件夹。

我对当前模块执行相同操作,以获取当前代码的类路径。

这样我收集当前工作模块的类路径及其所有必需的模块(1步之外)。 这对我有用 – 我的Java8扫描仪仍然可以完成这项工作。 这种方法不需要任何额外的VM标志等。

我可以扩展这种方法以轻松获得所有必需的模块(不仅仅是第一级),但是现在,我不需要它。

代码 。