如何在Java 9 +中安全地访问类路径中所有资源文件的URL?

2022-08-31 23:42:38

我们从 Java 9 的发行说明中了解到:

应用程序类装入器不再是 java.net.URLClassLoader 的实例(在以前的发行版中从未指定过实现细节)。假定 ClassLoader::getSytemClassLoader 返回 URLClassLoader 对象的代码将需要更新。

这会中断旧代码,该代码将按如下方式扫描类路径:

爪哇 <= 8

URL[] ressources = ((URLClassLoader) classLoader).getURLs();

它遇到一个

java.lang.ClassCastException: 
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to 
java.base/java.net.URLClassLoader

因此,对于Java 9 +,以下解决方法被提议作为Apache Ignite项目的PR,它按照预期工作,给定JVM运行时选项中的调整:。但是,正如下面的评论中提到的,这个PR从未合并到他们的主分支中。--add-opens java.base/jdk.internal.loader=ALL-UNNAMED

/*
 * Java 9 + Bridge to obtain URLs from classpath...
 */
private static URL[] getURLs(ClassLoader classLoader) {
    URL[] urls = new URL[0];

    try {
        //see https://github.com/apache/ignite/pull/2970
        Class builtinClazzLoader = Class.forName("jdk.internal.loader.BuiltinClassLoader");

        if (builtinClazzLoader != null) {
            Field ucpField = builtinClazzLoader.getDeclaredField("ucp");
            ucpField.setAccessible(true);

            Object ucpObject = ucpField.get(classLoader);
            Class clazz = Class.forName("jdk.internal.loader.URLClassPath");

            if (clazz != null && ucpObject != null) {
                Method getURLs = clazz.getMethod("getURLs");

                if (getURLs != null) {
                    urls = (URL[]) getURLs.invoke(ucpObject);
                }
            }
        }

    } catch (NoSuchMethodException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
        logger.error("Could not obtain classpath URLs in Java 9+ - Exception was:");
        logger.error(e.getLocalizedMessage(), e);
    }
    return urls;
}

但是,由于在此处使用反射,这会导致一些严重的头痛。这是一种反模式,受到forbidden-apis maven插件的严格批评:

禁止的方法调用:java.lang.reflect.AccessibleObject#setAccessible(布尔值) [反射用法在 SecurityManagers 中无法解决访问标志,并且可能不再适用于 Java 9 中的运行时类]

问题

有没有一种安全的方法可以在OpenJDK 9/10中访问类/模块路径中所有资源的列表,该列表可以由给定的类加载器访问,而无需使用导入(例如,通过使用)?URLssun.misc.*Unsafe

更新(与评论相关)

我知道,我可以做到

 String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));

以获取类路径中的元素,然后将它们解析为 s。但是 - 据我所知 - 此属性仅返回应用程序启动时给定的类路径。但是,在容器环境中,这将是应用程序服务器之一,可能还不够,例如,使用 EAR 捆绑包。URL

更新 2

感谢您的所有评论。我将测试,如果能达到我们的目的,并更新问题,如果这满足了我们的需求。System.getProperty("java.class.path")

然而,似乎其他项目(也许是出于其他原因,例如Apache TomEE 8)遭受了与之相关的相同痛苦 - 出于这个原因,我认为这是一个有价值的问题。URLClassLoader

更新 3

最后,我们确实切换到了 classgraph 并将我们的代码迁移到此库,以解决我们的用例,以便从类路径中加载捆绑为 JAR 的 ML 资源。


答案 1

我认为这是一个XY问题。访问类路径上所有资源的 URL 在 Java 中不是受支持的操作,尝试这样做也不是一件好事。正如您在此问题中已经看到的那样,如果您尝试这样做,您将一直与框架作斗争。将有一百万个边缘情况会破坏您的解决方案(自定义类加载器,EE容器等)。

请您详细解释一下为什么要这样做?

如果你有某种插件系统,并且正在寻找与代码接口的模块,这些模块可能在运行时提供,那么你应该使用ServiceLoader API,即:

打包为类路径的 JAR 文件的服务提供程序通过将提供程序配置文件放在资源目录中来标识。提供程序配置文件的名称是服务的完全限定二进制名称。提供程序配置文件包含服务提供程序的完全限定二进制名称的列表,每行一个。例如,假设服务提供程序打包在类路径的 JAR 文件中。JAR 文件将包含一个名为META-INF/servicescom.example.impl.StandardCodecs

META-INF/services/com.example.CodecFactory

包含以下行:

com.example.impl.StandardCodecs # Standard codecs

答案 2

AFAIK,您可以解析系统属性以获取网址:java.class.path

String classpath = System.getProperty("java.class.path");
String[] entries = classpath.split(File.pathSeparator);
URL[] result = new URL[entries.length];
for(int i = 0; i < entries.length; i++) {
    result[i] = Paths.get(entries[i]).toAbsolutePath().toUri().toURL();
}

System.out.println(Arrays.toString(result)); // e.g. [file:/J:/WS/Oxygen-Stable/jdk10/bin/]

推荐