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

2022-04-12 00:00:00 java classloader java-9

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

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

这打破了旧代码,旧代码按如下方式扫描类路径:

Java<;=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+,提出了以下解决方法作为PR at the Apache Ignite Project,在给定JVM运行时选项中的调整后,该方法可以按预期工作:--add-opens java.base/jdk.internal.loader=ALL-UNNAMED。但是,正如下面的评论中所提到的,此PR从未合并到他们的主分支。

/*
 * 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;
}

但是,由于在这里使用Reflection,这会导致一些严重的头痛。这是一种反模式,受到了forbidden-apis maven plugin:

的严厉批评

禁止的方法调用:java.lang.reflect.AccessibleObject#setAccessible(boolean)[使用反射来解决访问标志在SecurityManager中失败,并且可能不再适用于JAVA 9中的运行时类]

问题

在OpenJDK 9/10中,是否有安全访问类/模块路径中所有资源的URLs列表的方法,而无需使用sun.misc.*导入(例如,使用Unsafe)?

更新(与评论相关)

我知道,我能做到

 String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
获取类路径中的元素,然后将它们解析为URL。然而,据我所知,此属性仅返回应用程序启动时给定的类路径。然而,在容器环境中,这将是应用程序服务器的一个,并且可能是不够的,例如,然后使用EAR包。

更新2

感谢您的所有评论。我将测试System.getProperty("java.class.path")是否可用于我们的目的,如果这能满足我们的需要,我将更新问题。

然而,似乎其他项目(可能是因为其他原因,例如ApacheTomee 8)遭受了与URLClassLoader相同的痛苦-因此,我认为这是一个有价值的问题。

更新3

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


解决方案

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

请详细说明您为什么要这样做?

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

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

的提供程序配置文件
META-INF/services/com.example.CodecFactory

包含行:

com.example.impl.StandardCodecs # Standard codecs

相关文章