【发布时间】:2018-03-29 13:31:35
【问题描述】:
我们从Java 9 的发行说明中了解到
应用程序类加载器不再是 java.net.URLClassLoader 的实例(以前版本中从未指定的实现细节)。假设 ClassLoader::getSytemClassLoader 返回 URLClassLoader 对象的代码需要更新。
这会破坏旧代码,它会按如下方式扫描类路径:
Java
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。但是,正如下面的 cmets 所述,此 PR 从未合并到其 Master 分支中。
/*
* 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 中不使用@ 987654334@ 导入(例如使用Unsafe)?
UPDATE(与 cmets 有关)
我知道,我可以做到
String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
获取类路径中的元素,然后将它们解析为URLs。但是 - 据我所知 - 此属性仅返回应用程序启动时给出的类路径。但是,在容器环境中,这将是应用程序服务器之一,可能还不够,例如然后使用 EAR 包。
更新 2
感谢您的所有 cmets。我将测试System.getProperty("java.class.path") 是否适用于我们的 目的并更新问题,如果这满足我们的需求。
然而,似乎其他项目(可能出于其他原因,例如 Apache TomEE 8) 遭受与URLClassLoader 相关的同样痛苦 - 因此,我认为这是一个有价值的问题.
更新 3
最后,我们确实切换到 classgraph 并将我们的代码迁移到这个库,以解决我们的用例,以从类路径加载捆绑为 JAR 的 ML 资源。
【问题讨论】:
-
如果您想要类路径元素的 URL,请查看系统属性 java.class.path。
-
很容易拆分 java.class.path 属性的值并为每个元素创建一个文件 URL,这正是应用程序类加载器在初始化时所做的。
-
System.getProperty("java.class.path")仍然有效,您可以轻松地为其创建 URL。但关键是,这就是类路径。模块路径可能涉及更多,没有人说它必须完全可以表示为URLs 的列表。我想,这就是不再将URLClassLoader作为应用程序加载器的原因之一;你不应该假设你在一堆URLs 上运行。 -
“这个属性只返回应用程序启动时给出的类路径”——确切地说,因为 Java 9 将禁止修改类路径,所以这是唯一的类应用程序类加载器的路径。但是当您谈论容器时,您根本就不是在谈论应用程序加载器。只要你没有改变你的应用服务器,容器加载器可能仍然是
URLClassLoaders。最后,它似乎是一个XY problem。你想对那些URLs 做什么? -
顺便说一下,你提到的 PR 实际上并没有合并,但是团队已经在他们的 IgniteUtils.java#classLoaderUrls() 中采用了与你类似的方法,尽管我现在已经提出了一个错误 here
标签: java classloader java-9