我们从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+,提出了以下解决方案,作为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;
}
然而,由于在这里使用反射,这会引起一些严重的头痛。这是一种反模式,受到禁用-apis maven插件的严格批评。
禁止方法调用:使用java.lang.reflect.AccessibleObject#setAccessible(boolean)反射处理访问标志的方法在SecurityManagers中失败,很可能不再适用于Java9中的运行时类。
问题
是否有一种安全的方法来访问类/模块路径中的所有资源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
相关的同样痛苦--因此,我认为这是一个有价值的问题。
更新3
最后,我们切换到类图,并将代码迁移到这个库中,以解决我们的用例,从而从类路径中加载绑定为JAR的ML资源。
发布于 2018-04-09 02:51:01
我想这是XY问题。在Java中,访问类路径上所有资源的URL不是一个受支持的操作,也不是一件好事。正如您在这个问题中已经看到的,如果您尝试这样做的话,您将一直在与框架作斗争。将有一百万种边缘情况会破坏您的解决方案(自定义类加载器、EE容器等)。
请你详述一下你为什么要这么做?
如果您有某种插件系统,并且正在寻找与代码接口的模块,这些模块可能是在运行时提供的,那么您应该使用ServiceLoader API,即:
通过在资源目录
META-INF/services
中放置提供者配置文件来标识打包为类路径的JAR文件的服务提供者。提供程序配置文件的名称是服务的完全限定的二进制名称。provider-configuration文件包含服务提供程序的完全限定二进制名称的列表,每一行一个。例如,假设服务提供者com.example.impl.StandardCodecs
被打包到一个JAR文件中,用于类路径。JAR文件将包含一个提供者配置文件,名为: META-INF/services/com.example.CodecFactory 包含以下内容的行: com.example.impl.StandardCodecs #标准编解码器
发布于 2018-03-29 05:52:01
AFAIK您可以解析java.class.path
系统属性以获得urls:
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/]
https://stackoverflow.com/questions/49557431
复制