如何在Java 9+中安全地访问类路径中所有资源文件的URL?
我们从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
相关文章