在 Java 中的运行时扫描 classpath/modulepath 9
Scanning classpath/modulepath in runtime in Java 9
我似乎找不到任何关于扫描所有可用 类(对于接口、注释等)是否仍然可以在运行时、方式 Spring、反射和许多其他框架的信息面对 Jigsaw 相关的加载方式变化 类,图书馆目前也在做。
编辑:
这个问题是关于扫描寻找类的真实物理文件路径。 是关于动态 加载 类 和资源。它是相关的,但非常不是重复的。
UPDATE: Jetty project 已经为此做了一个标准化的 API JEP proposal。如果你有办法帮助实现这一目标,请去做。否则,等待和希望。
更新 2:发现 this 相关声音 post。引用代码片段 posterity:
If you are really just looking to get at the contents of the modules in
the boot layer (the modules that are resolved at startup) then you'll do
something like this:
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);
}
});
以下代码实现了Java9+(Jigsaw/JPMS)中的模块路径扫描。它在调用堆栈上找到所有 classes,然后对于每个 class 引用,调用 classRef.getModule().getLayer().getConfiguration().modules()
,其中 returns 一个 List<ResolvedModule>
,而不仅仅是一个 [=13] =]. (ResolvedModule
使您可以访问模块资源,而 Module
则不能。)给定每个模块的 ResolvedModule
引用,您可以调用 .reference()
方法来获取 ModuleReference
一个模块。 ModuleReference#open()
给你一个 ModuleReader
,它允许你使用 ModuleReader#list()
列出模块中的资源,或者使用 Optional<InputStream> ModuleReader#open(resourcePath)
或 Optional<ByteBuffer> ModuleReader#read(resourcePath)
打开资源。完成模块后,您可以关闭 ModuleReader
。这在我见过的任何地方都没有记录。很难弄清楚这一切。但这是代码,希望其他人能从中受益。
请注意,即使在 JDK9+ 中,您仍然可以使用传统的 classpath 元素以及模块路径元素,因此对于完整的模块路径 + classpath 扫描,您可能应该使用适当的 class 路径扫描解决方案,例如 ClassGraph, which supports module scanning using the below mechanism (disclaimer, I am the author). You can find a reflection-based version of the following code here.
另请注意,在 JDK 9 之后的几个 JDK 版本中,StackWalker 中有一个 bug 必须解决,请参阅上面基于反射的代码了解详细信息。
package main;
import java.lang.StackWalker;
import java.lang.StackWalker.Option;
import java.lang.StackWalker.StackFrame;
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 {
/** Recursively find the topological sort order of ancestral layers. */
private static void findLayerOrder(ModuleLayer layer,
Set<ModuleLayer> visited, Deque<ModuleLayer> layersOut) {
if (visited.add(layer)) {
List<ModuleLayer> 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<Entry<ModuleReference, ModuleLayer>> findModuleRefs(
Class<?>[] callStack) {
Deque<ModuleLayer> layerOrder = new ArrayDeque<>();
Set<ModuleLayer> visited = new HashSet<>();
for (int i = 0; i < callStack.length; i++) {
ModuleLayer layer = callStack[i].getModule().getLayer();
findLayerOrder(layer, visited, layerOrder);
}
Set<ModuleReference> addedModules = new HashSet<>();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = new ArrayList<>();
for (ModuleLayer layer : layerOrder) {
Set<ResolvedModule> modulesInLayerSet = layer.configuration()
.modules();
final List<Entry<ModuleReference, ModuleLayer>> 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<ModuleReference, ModuleLayer> 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<Class<?>[]> stackWalkerAction =
(PrivilegedAction<Class<?>[]>) () ->
StackWalker.getInstance(
Option.RETAIN_CLASS_REFERENCE)
.walk(s -> s.map(
StackFrame::getDeclaringClass)
.toArray(Class[]::new));
try {
// Try with doPrivileged()
return AccessController
.doPrivileged(stackWalkerAction);
} catch (Exception e) {
}
try {
// Try without doPrivileged()
return stackWalkerAction.run();
} catch (Exception e) {
}
// Try SecurityManager
PrivilegedAction<Class<?>[]> callerResolverAction =
(PrivilegedAction<Class<?>[]>) () ->
new SecurityManager() {
@Override
public Class<?>[] getClassContext() {
return super.getClassContext();
}
}.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<Class<?>> 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
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) {
String name = moduleReference.descriptor().name();
if (name == null) {
return false;
}
return name.startsWith("java.") || name.startsWith("jdk.")
|| name.startsWith("javafx.") || name.startsWith("oracle.");
}
public static void main(String[] args) throws Exception {
// Get ModuleReferences for modules of all classes in call stack,
List<Entry<ModuleReference, ModuleLayer>> systemModuleRefs = new ArrayList<>();
List<Entry<ModuleReference, ModuleLayer>> nonSystemModuleRefs = new ArrayList<>();
Class<?>[] callStack = getCallStack();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = findModuleRefs(
callStack);
// Split module refs into system and non-system modules based on module name
for (Entry<ModuleReference, ModuleLayer> m : moduleRefs) {
(isSystemModule(m.getKey()) ? systemModuleRefs
: nonSystemModuleRefs).add(m);
}
// List system modules
System.out.println("\nSYSTEM MODULES:\n");
for (Entry<ModuleReference, ModuleLayer> 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<ModuleReference, ModuleLayer> 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<URI> location = ref.location();
if (location.isPresent()) {
System.out.println(" Location: " + location.get());
}
try (ModuleReader moduleReader = ref.open()) {
Stream<String> stream = moduleReader.list();
stream.forEach(s -> System.out.println(" File: " + s));
}
}
}
}
这里的实际问题是找到 class 路径上所有 jar 和文件夹的路径。一旦你有了它们,你就可以扫描。
我所做的是:
- 获取当前class
的当前模块描述符
- 获取所有
requires
个模块
- 对于
MANIFEST.MF
的每个此类模块开放资源
- 从资源中删除
MANIFEST.MF
路径 url
- 剩下的是模块的 class路径,即它的 jar 或文件夹。
我对当前模块执行相同操作,以获取当前代码的 class路径。
通过这种方式,我收集了 class 当前工作模块及其所有必需模块的路径(距离 1 步)。这对我有用 - 我的 Java8 扫描仪仍然能够完成这项工作。这种方法不需要任何额外的 VM 标志等。
我可以扩展此方法以轻松获得 所有 所需的模块(不仅是第一级),但目前,我不需要那个。
Code.
我似乎找不到任何关于扫描所有可用 类(对于接口、注释等)是否仍然可以在运行时、方式 Spring、反射和许多其他框架的信息面对 Jigsaw 相关的加载方式变化 类,图书馆目前也在做。
编辑:
这个问题是关于扫描寻找类的真实物理文件路径。
UPDATE: Jetty project 已经为此做了一个标准化的 API JEP proposal。如果你有办法帮助实现这一目标,请去做。否则,等待和希望。
更新 2:发现 this 相关声音 post。引用代码片段 posterity:
If you are really just looking to get at the contents of the modules in the boot layer (the modules that are resolved at startup) then you'll do something like this:
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);
}
});
以下代码实现了Java9+(Jigsaw/JPMS)中的模块路径扫描。它在调用堆栈上找到所有 classes,然后对于每个 class 引用,调用 classRef.getModule().getLayer().getConfiguration().modules()
,其中 returns 一个 List<ResolvedModule>
,而不仅仅是一个 [=13] =]. (ResolvedModule
使您可以访问模块资源,而 Module
则不能。)给定每个模块的 ResolvedModule
引用,您可以调用 .reference()
方法来获取 ModuleReference
一个模块。 ModuleReference#open()
给你一个 ModuleReader
,它允许你使用 ModuleReader#list()
列出模块中的资源,或者使用 Optional<InputStream> ModuleReader#open(resourcePath)
或 Optional<ByteBuffer> ModuleReader#read(resourcePath)
打开资源。完成模块后,您可以关闭 ModuleReader
。这在我见过的任何地方都没有记录。很难弄清楚这一切。但这是代码,希望其他人能从中受益。
请注意,即使在 JDK9+ 中,您仍然可以使用传统的 classpath 元素以及模块路径元素,因此对于完整的模块路径 + classpath 扫描,您可能应该使用适当的 class 路径扫描解决方案,例如 ClassGraph, which supports module scanning using the below mechanism (disclaimer, I am the author). You can find a reflection-based version of the following code here.
另请注意,在 JDK 9 之后的几个 JDK 版本中,StackWalker 中有一个 bug 必须解决,请参阅上面基于反射的代码了解详细信息。
package main;
import java.lang.StackWalker;
import java.lang.StackWalker.Option;
import java.lang.StackWalker.StackFrame;
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 {
/** Recursively find the topological sort order of ancestral layers. */
private static void findLayerOrder(ModuleLayer layer,
Set<ModuleLayer> visited, Deque<ModuleLayer> layersOut) {
if (visited.add(layer)) {
List<ModuleLayer> 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<Entry<ModuleReference, ModuleLayer>> findModuleRefs(
Class<?>[] callStack) {
Deque<ModuleLayer> layerOrder = new ArrayDeque<>();
Set<ModuleLayer> visited = new HashSet<>();
for (int i = 0; i < callStack.length; i++) {
ModuleLayer layer = callStack[i].getModule().getLayer();
findLayerOrder(layer, visited, layerOrder);
}
Set<ModuleReference> addedModules = new HashSet<>();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = new ArrayList<>();
for (ModuleLayer layer : layerOrder) {
Set<ResolvedModule> modulesInLayerSet = layer.configuration()
.modules();
final List<Entry<ModuleReference, ModuleLayer>> 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<ModuleReference, ModuleLayer> 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<Class<?>[]> stackWalkerAction =
(PrivilegedAction<Class<?>[]>) () ->
StackWalker.getInstance(
Option.RETAIN_CLASS_REFERENCE)
.walk(s -> s.map(
StackFrame::getDeclaringClass)
.toArray(Class[]::new));
try {
// Try with doPrivileged()
return AccessController
.doPrivileged(stackWalkerAction);
} catch (Exception e) {
}
try {
// Try without doPrivileged()
return stackWalkerAction.run();
} catch (Exception e) {
}
// Try SecurityManager
PrivilegedAction<Class<?>[]> callerResolverAction =
(PrivilegedAction<Class<?>[]>) () ->
new SecurityManager() {
@Override
public Class<?>[] getClassContext() {
return super.getClassContext();
}
}.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<Class<?>> 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
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) {
String name = moduleReference.descriptor().name();
if (name == null) {
return false;
}
return name.startsWith("java.") || name.startsWith("jdk.")
|| name.startsWith("javafx.") || name.startsWith("oracle.");
}
public static void main(String[] args) throws Exception {
// Get ModuleReferences for modules of all classes in call stack,
List<Entry<ModuleReference, ModuleLayer>> systemModuleRefs = new ArrayList<>();
List<Entry<ModuleReference, ModuleLayer>> nonSystemModuleRefs = new ArrayList<>();
Class<?>[] callStack = getCallStack();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = findModuleRefs(
callStack);
// Split module refs into system and non-system modules based on module name
for (Entry<ModuleReference, ModuleLayer> m : moduleRefs) {
(isSystemModule(m.getKey()) ? systemModuleRefs
: nonSystemModuleRefs).add(m);
}
// List system modules
System.out.println("\nSYSTEM MODULES:\n");
for (Entry<ModuleReference, ModuleLayer> 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<ModuleReference, ModuleLayer> 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<URI> location = ref.location();
if (location.isPresent()) {
System.out.println(" Location: " + location.get());
}
try (ModuleReader moduleReader = ref.open()) {
Stream<String> stream = moduleReader.list();
stream.forEach(s -> System.out.println(" File: " + s));
}
}
}
}
这里的实际问题是找到 class 路径上所有 jar 和文件夹的路径。一旦你有了它们,你就可以扫描。
我所做的是:
- 获取当前class 的当前模块描述符
- 获取所有
requires
个模块 - 对于
MANIFEST.MF
的每个此类模块开放资源
- 从资源中删除
MANIFEST.MF
路径 url - 剩下的是模块的 class路径,即它的 jar 或文件夹。
我对当前模块执行相同操作,以获取当前代码的 class路径。
通过这种方式,我收集了 class 当前工作模块及其所有必需模块的路径(距离 1 步)。这对我有用 - 我的 Java8 扫描仪仍然能够完成这项工作。这种方法不需要任何额外的 VM 标志等。
我可以扩展此方法以轻松获得 所有 所需的模块(不仅是第一级),但目前,我不需要那个。
Code.