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

问题描述:

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

I can't seem to find any info on whether scanning all available classes (for interfaces, annotations etc) is still possible in runtime, the way Spring, Reflections and many other frameworks and libraries currently do, in the face of Jigsaw related changes to the way classes are loaded.

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

EDIT: This question is about scanning the real physical file paths looking for classes. The other question is about dynamically loading classes and resources. It's related but very much not a duplicate.

更新 :Jetty项目有为此制作了一个 JEP提案,以获得标准化的API。如果你有办法帮助实现这个目标,请做。否则,请等待并希望。

UPDATE: Jetty project has made a JEP proposal for a standardized API for this. If you have a way to help make this reality, please do. Otherwise, wait and hope.

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

UPDATE 2: Found this relevant sounding post. Quoting the code snippet for 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);
             }
         });


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

The following code achieves module path scanning in Java 9+ (Jigsaw). It finds all classes on the callstack, then for each class reference, calls classRef.getModule().getLayer().getConfiguration().modules(), which returns a a List<ResolvedModule>, rather than just a List<Module>. (ResolvedModule gives you access to the module resources, whereas Module does not.) Given a ResolvedModule reference for each module, you can call the .reference() method to get the ModuleReference for a module. ModuleReference#open() gives you a ModuleReader, which allows you to list the resources in a module, using ModuleReader#list(), or to open a resource using Optional<InputStream> ModuleReader#open(resourcePath) or Optional<ByteBuffer> ModuleReader#read(resourcePath). You then close the ModuleReader when you're done with the module. This is not documented anywhere that I have seen. It was very difficult to figure all this out. But here is the code, in the hope that someone else will benefit from this.

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

Note that even in JDK9+, you can still utilize traditional classpath elements along with module path elements, so for a complete module path + classpath scan, you should probably use a proper classpath scanning solution, such as ClassGraph, which supports module scanning using the below mechanism (disclaimer, I am the author).

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<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 = new PrivilegedAction<Class<?>[]>() {
            @Override
            public Class<?>[] run() {
                List<Class<?>> 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<Class<?>[]> callerResolverAction = new PrivilegedAction<Class<?>[]>() {
            @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<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 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<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));
            }
        }
    }
}