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),则可能在构建时进行检查) 。

Leave a Reply

Your email address will not be published. Required fields are marked *

lWoHvYe 无悔,专一