java.lang.reflect.InaccessibleObjectException: Unable to make {member} accessible: module {A} does not “opens {package} ” to {B}
How to solve InaccessibleObjectException (“Unable to make {member} accessible: module {A} does not ‘opens {package}’ to {B}”) on Java 9 or later?
在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)
该消息显示:
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
如何避免发生异常并使程序成功运行?
该异常是由Java 9中引入的Java平台模块系统引起的,特别是其强封装的实现。 它仅允许在特定条件下访问,最突出的条件是:
- 类型必须是公开的
- 拥有包必须导出
对于反射,导致异常的代码尝试使用相同的限制。 更确切地说,该异常是由对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的元素。 所需的所有信息都包含在异常消息中,但是有许多机制可以实现此目的。 哪一个最好,取决于导致它的确切情况。
Unable to make {member} accessible: module {A} does not ‘opens {package}’ to {B}
到目前为止,最突出的情况是以下两种:
库或框架使用反射来调用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模块对于应用程序开发人员而言是不变的,因此我们无法更改其属性。 这仅留下一种可能的解决方案:命令行标志。 有了它们,就有可能打开特定的包进行反射。
因此在上述情况下(缩短)…
Unable to make java.lang.ClassLoader.defineClass accessible: module java.base does not”opens java.lang” to unnamed module @1941a8ff
…正确的解决方法是按以下方式启动JVM:
格式 | # –add-opens has the following syntax: {A}/{package}={B} |
---|---|
示例 | java –add-opens java.base/java.lang=ALL-UNNAMED |
如果反映代码在命名模块中,则ALL-UNNAMED可以用其名称替换。
请注意,有时可能很难找到一种将该标志应用于将实际执行反射代码的JVM的方法。 如果有问题的代码是项目的构建过程的一部分,并且在构建工具生成的JVM中执行,则这可能会特别困难。
如果要添加的标志太多,则可以考虑使用封装终止开关–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} { … }打开整个模块,这将使其所有软件包在运行时(具有或不具有反射功能)可用于所有代码
有关这些方法的详细讨论和比较,请参见此帖子。
- 在情况1中Lombok是否很难找到一种方法将此标志应用于将实际执行反射代码的JVM,因为它是项目构建过程的一部分?
- 是的,Lombok是第一种情况。在这里应用标记的难度是一个问题。
- 感谢您的解释。对于我非常相似的情况,即使使用该–add-opens选项,它仍然会失败。奇怪。
- 乍一看,问题可能是您配置了Surefire插件,但故障是由failsafe-plugin引起的。如果我错了,请再问一个问题。
- @Nicolai我认为您应该更新您的答案,因为JDK 9现在默认情况下允许非法访问,并且–permit-illegal-access将更改为–illegal-access:mail.openjdk.java.net/pipermail/jigsaw-dev/2017-May/012673.h TML
- @ZhekaKozlov我正在等待第一个公开该行为的EA构建,然后再更新答案。
- export JAVA_TOOL_OPTIONS=”$JAVA_TOOL_OPTIONS –add-opens=java.base/java.lang=ALL-UNNAMED”因bugs而用于OpenJDK 9.openjdk.java.net/browse/JDK-8173128
- 您能否看一下这个问题stackoverflow.com/questions/60918134/…
这是一个很难解决的问题;并且正如其他人所指出的那样,–add-opens选项仅是一种解决方法。只有当Java 9公开可用时,解决基本问题的紧迫性才会增加。
在Java 9上测试基于Hibernate的应用程序时,收到此确切的Javassist错误后,我在本页上找到了自己。由于我的目标是在多个平台上支持Java 7、8和9,因此我一直在努力寻找最佳解决方案。 (请注意,Java 7和8 JVM在命令行上看到无法识别的”-add-opens “参数时,将立即中止;因此无法通过对批处理文件,脚本或快捷方式进行静态更改来解决此问题。 。)
从主流库(例如Spring和Hibernate)的作者那里获得正式指导是很高兴的,但是距离Java 9的当前预计发布还有100天的时间,这种建议似乎仍然很难找到。 >
经过大量的试验和测试,我为找到Hibernate的解决方案感到欣慰:
使用Hibernate 5.0.0或更高版本(早期版本将不起作用),并且
请求构建时字节码增强(使用Gradle,Maven或Ant插件)。
这避免了Hibernate在运行时执行基于Javassist的类修改的需要,从而消除了原始文章中显示的堆栈跟踪。
但是,之后您应该彻底测试您的应用程序。 Hibernate在构建时应用的字节码更改似乎与运行时所应用的字节码更改不同,从而导致应用程序行为略有不同。当启用启用构建时字节码增强功能时,我的应用程序中已经成功多年的单元测试突然失败了。 (我不得不解决新的LazyInitializationExceptions和其他问题。)而且行为似乎从一个Hibernate版本到另一个版本都不同。请谨慎操作。
- 您是否考虑将对Hibernate的实验用于该问题的答案?
使用–add-opens应该被认为是一种解决方法。对于Spring,Hibernate和其他进行非法访问以修复其问题的库来说,正确的做法是。
- 了解您建议如何”解决他们的问题”会很有帮助。是通过方法还是var句柄?恕我直言,通过读取/写入私有字段来访问状态并不是天生的坏事,例如JPA规范明确指出了这一点。
- 对于特定示例,看起来像Hibernate或Spring使用Javassist侵入非公共的defineClass方法。专门添加了Lookup.defineClass方法来帮助库注入类,因此这是该用例的前进之路。对于需要访问其使用者的私有成员的JPA和其他库的情况,他们将需要证明使用者将包打开到库中(对于基于注释的框架(如JPA),则可能在构建时进行检查) 。