Java 17运行时获取已加载JAR路径:反射访问ucp字段失败
在Java 17中访问AppClassLoader父类BuiltinClassLoader的ucp字段
问题原因
- Java 17里
jdk.internal.loader.ClassLoaders.AppClassLoader的ucp字段已迁移至父类BuiltinClassLoader,且该类属于JDK内部模块jdk.base的jdk.internal.loader包,默认受模块系统封装限制,普通反射无法直接访问。 - 调用
AppClassLoader.getClass().getDeclaredFields()返回空数组,是因为AppClassLoader本身未声明任何字段,所有字段都在父类BuiltinClassLoader中,且模块封装导致反射无法感知内部类的字段。
解决方案
方法一:通过启动参数解除模块封装(适合可控制JVM启动参数的场景)
启动Java程序时添加以下参数,允许未命名模块访问jdk.internal.loader包:
--add-opens jdk.base/jdk.internal.loader=ALL-UNNAMED
之后直接通过反射获取父类的ucp字段:
// 获取AppClassLoader的父类BuiltinClassLoader Class<?> builtinClassLoaderClass = Class.forName("jdk.internal.loader.BuiltinClassLoader"); Field ucpField = builtinClassLoaderClass.getDeclaredField("ucp"); ucpField.setAccessible(true); URLClassPath ucp = (URLClassPath) ucpField.get(classLoader);
方法二:运行时通过反射打开模块(无需修改启动参数,适合动态场景)
如果无法修改JVM启动参数,可通过反射动态打开jdk.base模块中jdk.internal.loader包的访问权限:
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); // 加载BuiltinClassLoader类 Class<?> builtinClassLoaderClass = Class.forName("jdk.internal.loader.BuiltinClassLoader"); // 打开jdk.internal.loader包给当前模块 Module builtinModule = builtinClassLoaderClass.getModule(); Module currentModule = Thread.currentThread().getContextClassLoader().getClass().getModule(); builtinModule.addOpens("jdk.internal.loader", currentModule); // 获取并访问ucp字段 Field ucpField = builtinClassLoaderClass.getDeclaredField("ucp"); ucpField.setAccessible(true); URLClassPath ucp = (URLClassPath) ucpField.get(classLoader);
注意事项
@Stable注解仅用于JVM优化,不影响反射对字段的访问权限,只需调用setAccessible(true)即可突破私有字段限制。- 该方案依赖JDK内部实现,未来版本可能存在变动,需做好兼容性测试。
内容的提问来源于stack exchange,提问作者ebc149
相关产品推荐
相关产品推荐

