【问题标题】:Scanning classpath/modulepath in runtime in Java 9在 Java 9 运行时扫描类路径/模块路径
【发布时间】:2017-01-30 09:36:19
【问题描述】:

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

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

更新:Jetty 项目为此创建了一个 JEP proposal 用于标准化 API。如果您有办法帮助实现这一目标,请这样做。否则,请等待和希望。

更新 2:找到 this 相关的发声帖子。后人引用代码sn-p:

如果您真的只是想了解模块的内容 引导层(在启动时解析的模块)然后你会做 像这样:

  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);
             }
         });

【问题讨论】:

  • 我认为没有太大变化,我的意思是如果一个模块对另一个模块可见,您仍然可以做通常的事情。如果您无法从模块访问类,您可能会遇到InaccessibleObjectException 或类似的异常
  • @Eugene 你确定吗?询问是因为类路径现在似乎是“遗留”的东西,被 modulepath 取代。所以我猜想扫描过去的类路径已经改变了......
  • 查看下面我的回答,不仅可以扫描引导模块层。
  • github.com/classgraph/classgraph/wiki/Code-examples ClassGraph(以前的 FastClassGraph)对我很有帮助。

标签: java classpath java-9 java-platform-module-system java-module


【解决方案1】:

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

请注意,即使在 JDK9+ 中,您仍然可以使用传统的类路径元素以及模块路径元素,因此对于完整的模块路径 + 类路径扫描,您可能应该使用适当的类路径扫描解决方案,例如 ClassGraph,它支持使用以下机制进行模块扫描(免责声明,我是作者)。您可以找到以下代码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));
            }
        }
    }
}

【讨论】:

  • 这里是同一作者更详细的调查:github.com/lukehutch/fast-classpath-scanner/issues/36
  • @kaqqao 我用完整的扫描码更新了我的答案。
  • @LukeHutchison 先生,您是人类的神皇 :) 这是否意味着 FastClasspathScanner 现在能够处理带有模块路径(并且没有类路径)的 Java 9?
  • @LukeHutchison 也许如果模块的位置 url 具有方案jrt:?这意味着该模块属于 java-runtime,对吧?
  • 您可以将堆栈遍历器的使用简化为单个 return AccessController.doPrivileged((PrivilegedAction&lt;Class&lt;?&gt;[]&gt;)() -&gt; StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE) .walk(s -&gt; s.map(StackFrame::getDeclaringClass).toArray(Class&lt;?&gt;[]::new))); 语句。无需手动添加到ArrayList,之后也无需将其转换为数组。在 Java 9 代码中,您可以使用 lambda 表达式(就像您已经在某些地方所做的那样),而不是匿名内部类。
【解决方案2】:

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

我所做的如下:

  • 获取当前类的当前模块描述符
  • 获取所有requires 模块
  • 对于MANIFEST.MF 的每个此类模块开放资源
  • 从资源 url 中移除 MANIFEST.MF 路径
  • 剩下的是模块的类路径,即它的 jar 或文件夹。

我对当前模块做同样的事情,以获取当前代码的类路径。

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

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

Code.

【讨论】:

  • modulepath 上的模块 jars 在 Java 9+ 的传统类路径中没有列出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-22
  • 2014-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多