module java.base does not "opens java.lang" to unnamed module
在Java 9及以上版本运行应用程序时,在各种情况下都会发生此异常。
某些库和框架(Spring,Hibernate,JAXB)特别容易使用。
这是来自Javassist的示例:
java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff
at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:102)
at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:180)
at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.java:163)
at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:501)
at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:486)
at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.java:422)
at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:394)
消息说:
无法使受保护的最终java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte [],int,int,java.security.ProtectionDomain)抛出java.lang.ClassFormatError可访问:模块java.base不会“打开java.lang”到未命名的模块@ 1941a8ff
为了避免异常并使程序成功运行,该怎么办?
参考方案
异常是由Java 9及以上版本中引入的Java Platform Module System引起的,特别是强封装的实现。
它仅在特定条件下允许access,最突出的条件是:
- 类型必须是公共的
- 必须导出拥有的软件包
对于反射,导致异常的代码尝试使用相同的限制。
更确切地说,异常是由对 setAccessible
的调用引起的。
在上面的堆栈跟踪中可以看到这一点,其中javassist.util.proxy.SecurityActions
中的相应行如下所示:
static void setAccessible(final AccessibleObject ao,
final boolean accessible) {
if (System.getSecurityManager() == null)
ao.setAccessible(accessible); // <~ Dragons
else {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ao.setAccessible(accessible); // <~ moar Dragons
return null;
}
});
}
}
为了确保程序成功运行,必须说服模块系统允许访问调用了setAccessible
的元素。
所需的所有信息都包含在异常消息中,但是可以通过a number of mechanisms来实现。
哪一个最好,取决于导致它的确切情况。
无法使{member}可以访问:模块{A}不能向{B}打开{package}
到目前为止,最突出的方案是以下两种:
- 库或框架使用反射来调用JDK模块。
在这种情况下: {A}
是一个Java模块(以java.
或jdk.
前缀){member}
和{package}
是Java API
的一部分
{B}
是一个库,框架或应用程序模块;经常unnamed module @...
- 一个基于反射的库/框架,例如Spring,Hibernate,JAXB等,通过应用程序代码反射以访问bean,实体等。
在这种情况下: {A}
是一个应用程序模块{member}
和{package}
是应用程序代码
的一部分
{B}
是框架模块或unnamed module @...
请注意,某些库(例如JAXB)在两个帐户上都可能失败,因此请仔细查看您所处的场景!
问题中的一个是案例1。
1.反射调用JDK
JDK模块对于应用程序开发人员而言是不变的,因此我们无法更改其属性。
这仅留下一种可能的解决方案:command line flags。
有了它们,就有可能打开特定的包装以进行反思。
因此,在上述情况下(缩短)...
无法使java.lang.ClassLoader.defineClass可访问:模块java.base不会“打开java.lang”到未命名的模块@ 1941a8ff
...正确的解决方法是按以下方式启动JVM:
# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
如果反射代码在命名模块中,则ALL-UNNAMED
可以替换为其名称。
请注意,有时可能很难找到一种方法将此标志应用于将实际执行反射代码的JVM。
如果所讨论的代码是项目构建过程的一部分,并且在构建工具产生的JVM中执行,则这可能会特别困难。
如果要添加的标志太多,则可以考虑使用encapsulation kill switch --permit-illegal-access
代替。它将允许类路径上的所有代码反映所有已命名的模块。请注意,此标记仅在Java 9 中有效!
2.对应用程序代码的反思
在这种情况下,您很可能可以编辑反射被用来进入的模块。
(如果没有,则实际上是在情况1中。)这意味着不需要命令行标志,而是可以使用模块{A}
的描述符打开其内部。
有多种选择:
- 使用
exports {package}
导出软件包,这使得它在编译和运行时可用于所有代码 - 使用
exports {package} to {B}
将包导出到访问模块,这使得它在编译和运行时可用,但仅对{B}
可用。
- 使用
opens {package}
打开包,这使得它在运行时(带或不带反射)可用于所有代码 - 使用
opens {package} to {B}
将包打开到访问模块,这使其在运行时可用(有或没有反射),但仅适用于{B}
- 使用
open module {A} { ... }
打开整个模块,这将使其所有包在运行时(带有或不带有反射)可用于所有代码
升级JDK17出现:module java.base does not “opens java.lang.reflect“ to unnamed module
解决方法 增加运行参数
--add-opens java.base/java.lang.reflect=ALL-UNNAMED