在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
可以采取哪些措施来避免异常并使程序成功运行?
答案 0 :(得分:69)
异常是由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}没有'打开{package}'到{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.
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 !
在这种情况下,您可能可以编辑反射用于闯入的模块。
(如果不是,那么你的实际情况就是1.)这意味着命令行标志不是必需的,而模块{A}
的描述符可以用来打开它的内部。
有多种选择:
exports {package}
导出包,这使其在编译和运行时可用于所有代码exports {package} to {B}
将包导出到访问模块,这使其在编译和运行时可用,但仅限于{B}
opens {package}
打开包,这使得它在运行时(有或没有反射)可用于所有代码opens {package} to {B}
将包打开到访问模块,这使其在运行时可用(有或没有反射),但仅限于{B}
open module {A} { ... }
打开整个模块,这使得所有程序包在运行时(有或没有反射)可用于所有代码有关这些方法的更详细讨论和比较,请参阅this post。
答案 1 :(得分:3)
使用--add-opens应该被视为一种解决方法。正确的做法是Spring,Hibernate和其他图书馆非法访问以修复他们的问题。
答案 2 :(得分:3)
这是一个非常具有挑战性的问题需要解决;正如其他人所说, - add-opens选项只是一种解决方法。只有Java 9公开后,解决潜在问题的紧迫性才会增加。
我在Java 9上测试基于Hibernate的应用程序时收到这个确切的Javassist错误后发现自己在这个页面上。由于我的目标是在多个平台上支持Java 7,8和9,我很难找到最佳解决方案。 (请注意,当Java 7和8 JVM在命令行上看到无法识别的“--add-opens”参数时,它们会立即中止;因此,对批处理文件,脚本或快捷方式进行静态更改无法解决此问题。)< / p>
收到主流图书馆的作者(如Spring和Hibernate)的官方指导会很好,但是在目前预计发布的Java 9版本的100天之后,这个建议似乎仍然难以找到。
经过大量的实验和测试,找到Hibernate的解决方案后,我感到很欣慰:
这避免了Hibernate在运行时执行基于Javassist的类修改的需要,从而消除了原始帖子中显示的堆栈跟踪。
HOWEVER ,之后您应该彻底测试您的应用程序。 Hibernate在构建时应用的字节码更改似乎与运行时应用的字节码更改不同,导致应用程序行为略有不同。当我启用构建时字节码增强时,我的应用程序中已经成功多年的单元测试突然失败了。 (我不得不追逐新的LazyInitializationExceptions和其他问题。)从一个版本的Hibernate到另一个版本,行为似乎各不相同。谨慎行事。
答案 3 :(得分:2)
我在 2021 年使用 openJDK 1.8 和 STS 4 时遇到了同样的问题。
Window => Preferences => Java => Installed JREs.
我使用添加选项添加了一个新的 JRE(如下所述),浏览到 openJdk 文件夹,选择确定。将新的 JDK 设为默认值。点击应用并关闭。
/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre
它就像一个魅力:)
答案 4 :(得分:1)
我有hibernate 5的警告。
Illegal reflective access by javassist.util.proxy.SecurityActions
我将最新的javassist库添加到依赖项gradle:
compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
这解决了我的问题。
答案 5 :(得分:1)
将 --illegal-access=warn 和 --add-opens java.base/java.lang=ALL-UNNAMED 添加到您的 eclipse.ini
答案 6 :(得分:0)
How to tell eclipse to add-exports when compiling
我按照顶部评论中的步骤操作,但在 Add-exports configuration
弹出窗口中,我输入了错误中的模块并选择了 opens
复选框而不是 exports
复选框并保存它。这解决了我的问题。
答案 7 :(得分:0)
在将现有的基于 JDK1.8 的 Springboot 项目导入 SpringTestSuite4 后,我遇到了同样的问题。当我在嵌入式 Tomcat 服务器上启动应用程序时,出现此错误
<块引用>java.lang.reflect.InaccessibleObjectException:无法制作 受保护的最终 java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) 抛出 java.lang.ClassFormatError 可访问:模块 java.base 做 不是“打开 java.lang”到未命名的模块 @140c9f39 %09
我在“窗口”->“首选项”->“Java”下的“已安装 JRE”部分将预装的 JDK1.8 JRE 从我的 PC 添加到 SpringTestSuite4,并将添加的 JRE 设为默认值。然后我点击应用然后关闭。
它对我有用。
答案 8 :(得分:0)
对我来说,我只是更改了 spring-boot-starter-web 版本并为我工作