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

rzo*_*rzo 44 java classloader java-9

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

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

这会破坏旧代码,它会扫描类路径,如下所示:

Java <= 8

URL[] ressources = ((URLClassLoader) classLoader).getURLs();
Run Code Online (Sandbox Code Playgroud)

哪个遇到了

java.lang.ClassCastException: 
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to 
java.base/java.net.URLClassLoader
Run Code Online (Sandbox Code Playgroud)

因此,对于Java 9+,以下解决方法被提议作为Apache Ignite项目PR,它在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;
}
Run Code Online (Sandbox Code Playgroud)

然而,由于在这里使用反射,这导致一些严重的头痛.这是一种反模式,并且受到forbidden-apis maven插件的严格批评:

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

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

更新(与评论相关)

我知道,我能做到

 String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
Run Code Online (Sandbox Code Playgroud)

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

更新2

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

然而,似乎其他项目(可能由于其他原因,例如Apache TomEE 8)遭受与之相关的痛苦URLClassLoader- 因此,我认为这是一个有价值的问题.

Ric*_*ich 18

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

请问你能扩展你想要这样做的原因吗?

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

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

META-INF/services/com.example.CodecFactory
Run Code Online (Sandbox Code Playgroud)

包含该行:

com.example.impl.StandardCodecs # Standard codecs
Run Code Online (Sandbox Code Playgroud)

  • +1 根本没有*安全*的方法来获取所有类路径源作为 URL。自 Java 1.0 以来,Java VM 已经能够从任意源加载类文件,而不限于通过 URL“可访问”的任何内容。当以字符串形式提供类名时,类加载器不需要提供比能够加载类更多的功能。类路径浏览和对内部 URL 的访问将与某些类加载器一起使用,但在其他运行时环境中根本不可用。 (2认同)

Jor*_*nee 9

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/]
Run Code Online (Sandbox Code Playgroud)