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

时间:2018-03-29 13:31:35

标签: 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)[解决访问标志的反射用法因SecurityManagers而失败,可能在Java 9中的运行时类上不再有用]

问题

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

更新(与评论相关)

我知道,我可以做到

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

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

更新2

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

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

2 个答案:

答案 0 :(得分:18)

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

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

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

  

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

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

包含以下行:

com.example.impl.StandardCodecs # Standard codecs

答案 1 :(得分: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/]