在 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) throws java.lang.ClassFormatError 可访问:模块 java.base不会“打开 java.lang”到未命名的模块 @1941a8ff
可以做些什么来避免异常并让程序成功运行?
原文由 Nicolai Parlog 发布,翻译遵循 CC BY-SA 4.0 许可协议
该异常是由 Java 9 中引入的 Java Platform Module System 引起的,特别是它对强封装的实现。它只允许在某些条件下 访问,最突出的是:
同样的限制也适用于反射,导致异常的代码试图使用反射。更准确地说,异常是由调用
setAccessible
引起的。这可以在上面的堆栈跟踪中看到,其中javassist.util.proxy.SecurityActions
中的相应行如下所示:为了确保程序成功运行,必须说服模块系统允许访问调用了
setAccessible
的元素。所需的所有信息都包含在异常消息中,但有 许多机制 可以实现这一点。哪个是最好的取决于导致它的确切情况。到目前为止,最突出的场景有以下两种:
库或框架使用反射来调用 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 模块对于应用程序开发人员来说是不可变的,因此我们无法更改它们的属性。这只剩下一种可能的解决方案: 命令行标志。有了它们,就可以打开特定的包进行反射。
因此,在上述情况下(缩短)…
…正确的解决方法是按如下方式启动 JVM:
如果反射代码在命名模块中,
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} { ... }
打开整个模块,这使得它的所有包在运行时(有或没有反射)对所有代码都可用有关这些方法的更详细讨论和比较,请参阅 这篇文章。