如何在 Java9上解决 InaccessibleObjectException (“无法使{ member }可访问: 模块{ A }没有‘打开{ package }’到{ B }”) ?

在 Java9上运行应用程序时,此异常发生在各种各样的场景中。 某些库和框架(Spring、 Hibernate、 JAXB)特别容易使用它。 下面是 Javassociation 的一个例子:

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。Java.lang 类。DefeClass (java.lang.String,byte [] ,int,int,java.security.ProtectionDomain)抛出 java.lang。可访问的 ClassFormatError: 模块 java.base 不会“打开 java.lang”到未命名的模块@1941a8ff

如何避免异常并使程序成功运行?

204655 次浏览

这个异常是由 Java9中引入的 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的元素。 所有需要的信息都包含在异常消息中,但是有 许多机械装置来实现这一点。 哪个是最好的取决于造成这一切的具体情况。

无法使{ member }可访问: 模块{ A }不会“打开{ package }”到{ B }

到目前为止,最突出的情况有以下两种:

  1. 库或框架使用反射调用 JDK 模块。 在这种情况下:

    • {A}是一个 Java 模块(前缀为 java.jdk.)
    • {member}{package}是 JavaAPI 的一部分
    • {B}是一个库、框架或应用程序模块; 通常是 unnamed module @...
  2. 像 Spring、 Hibernate、 JAXB... 这样的基于反射的库/框架反射应用程序代码来访问 bean、实体..。 在这种情况下:

    • {A}是一个应用程序模块
    • {member}{package}是应用程序代码的一部分
    • {B}要么是一个框架模块,要么是 unnamed module @...

注意,有些库(例如 JAXB)可能在两个帐户上都失败,因此请仔细查看您所处的场景! 问题中的那个是情况1。

1. 对 JDK 的反思性调用

JDK 模块对于应用程序开发人员来说是不可变的,因此我们不能更改它们的属性。 这就只剩下一个可能的解决方案: 命令行标志。 有了它们,就可以打开特定的包进行反思。

所以在上面这种情况下..。

无法使 java.lang 可访问: 模块 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 中执行的,那么这可能特别困难。

如果有太多的标志要添加,您可以考虑使用 封装自杀开关封装自杀开关 --permit-illegal-access代替。它将允许类路径上的所有代码反映整个命名模块。注意这个标志 只能在 Java9中使用

2. 应用程序代码的反思

在这个场景中,您很可能可以编辑反射用于闯入的模块。 (如果不是,那么在情况1中就是有效的。)这意味着命令行标志是不必要的,相反,模块 {A}的描述符可用于打开其内部。 有各种各样的选择:

  • 使用 exports {package}导出包,这样就可以在编译和运行时对所有代码使用它
  • 使用 exports {package} to {B}将包导出到访问模块,这使得它在编译和运行时可用,但只能导出到 {B}
  • 使用 opens {package}打开包,使其在运行时(有或没有反射)可用于所有代码
  • 使用 opens {package} to {B}将包打开到访问模块,这使其在运行时(有或没有反射)可用,但只能对 {B}可用
  • 使用 open module {A} { ... }打开整个模块,使其所有包在运行时(有或没有反射)对所有代码可用

有关这些方法的更详细的讨论和比较,请参见 这篇文章

使用—— add-open 应该被认为是一种解决方案。对于 Spring、 Hibernate 和其他非法访问的库来说,正确的做法是修复它们的问题。

这是一个要解决的非常具有挑战性的问题; 正如其他人所指出的,—— add-open 选项只是一个解决方案。只有当 Java9公开可用时,解决基本问题的紧迫性才会增加。

在 Java9上测试基于 Hibernate 的应用程序时,我发现自己正好在这个页面上。由于我的目标是在多个平台上支持 Java7、 Java8和 Java9,因此我努力寻找最佳解决方案。(请注意,当 Java 7和8 JVM 在命令行上看到一个未识别的“—— add-open”参数时,它们会立即中止; 因此这不能通过对批处理文件、脚本或快捷方式的静态更改来解决。)

从主流库(比如 Spring 和 Hibernate)的作者那里得到官方的指导是很好的,但是距离当前计划发布的 Java9还有100天,这个建议似乎仍然很难找到。

经过大量的实验和测试,我终于找到了 Hibernate 的解决方案:

  1. 使用 Hibernate 5.0.0或更高版本(早期版本无法工作) ,以及
  2. 请求 构建时字节码增强(使用 Gradle、 Maven 或 Ant 插件)。

这样就避免了 Hibernate 在运行时执行基于 Javhelp 的类修改,从而消除了原始文章中显示的堆栈跟踪。

然而 之后,您应该彻底测试您的应用程序。Hibernate 在构建时应用的字节码更改似乎与在运行时应用的字节码更改不同,从而导致应用程序行为略有不同。当我启用构建时字节码增强时,我的应用程序中成功多年的单元测试突然失败了。(我必须查找新的 LazyInitializationException 和其他问题。)不同版本的 Hibernate 似乎有不同的行为。小心行事。

我在冬眠5号上收到了警告。

Illegal reflective access by javassist.util.proxy.SecurityActions

我在依赖级别中添加了最新的 javhelp 库:

compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

这解决了我的问题。

在 eclipse.ini 中添加—— non-access = police 和—— Add-open java.base/java.lang = ALL-UNNAMED

我在2021年使用 openJDK 1.8和 STS 4时遇到过同样的问题。

Window => Preferences => Java => Installed JREs.

我使用 add 选项添加了一个新的 JRE (下面提到) ,浏览到 openJdk 文件夹,选择 OK。将新的 JDK 设置为默认值。点击申请然后关闭。

/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre

效果非常好:)

编译时如何告诉 eclipseto 添加-导出

我按照顶部注释中的步骤操作,但是在 Add-exports configuration弹出窗口中,我输入了出现错误的模块,并选择了 opens复选框而不是 exports复选框并保存了它。这解决了我的问题。

在将基于 JDK1.8的现有 Springboot 项目导入 SpringTestSuite4之后,我遇到了同样的问题。当我在嵌入式 Tomcat 服务器上启动应用程序时,出现了这个错误

映射. InaccessibleObjectException: 无法使 受保护的最终 java.lang 类 ClassLoader.defeClass (java.lang.String,byte [] ,int,int,java.security. ProtectionDomain) 可访问的 ClassFormatError: module java.base 执行 而不是“ open java.lang”到未命名模块@140c9f39% 09

我在 Window-> Preferences-> Java 下面的 Install JRE 部分中将预安装的 JDK1.8 JRE 从我的 PC 添加到 SpringTestSuite4,并将添加的 JRE 作为默认设置。然后我点击应用,然后关闭。

这招对我很管用。

对我来说,我只是改变了弹簧启动启动器网络版本,并为我工作

面对同样的问题,其中一个原因可能是使用了错误的 JDK/ProjectSDK。

在 Intellij 解决这个问题:

右键单击 Maven Project-> Open Module Settings-> Project 设置-> 项目-> 选择您的 SDK 到已经安装的 JDK 1.8

我成功了!

我在运行 SornarQube 的时候也遇到过类似的问题,我通过以下操作得到了上传声纳报告的解决方案:

现有软件包: Java16.0.2,MacOS (BigSur)

  • 已安装声纳扫描仪
  • 运行以下命令: 导出 SONAR _ SCANNER _ OPTS = “—— non-access = 許可”

希望这将有助于某人如果面临同样的问题,她/他可以尝试这一点。 : -)

只是最近的反馈

解决这个问题的许多建议都与 vm 启动器选项 --illegal-access有关。

根据甲骨文,与 JEP-403(连结1)JEP-403(link2)已被决定为 由 JDK 17及以后发布,启动器选项 --illegal-access将停止工作!

强烈封装 JDK 的所有内部元素,除了 对于关键的内部 API,如 sun.misc。不安全。它将不再 有可能通过放松强大的内部元素封装 一个单一的命令行选项,这在 JDK 9到 JDK 16中是可能的。

还有

有了这个更改,最终用户将不再可能使用 it < strong > 非法访问选项 可以访问 JDK。(这里有受影响的包的列表。) Sun.misc 和 sun.丢包仍然会被出口 不支持 jdk.unsupport 模块,并且仍然是打开的,以便代码可以访问 其他 JDK 包都不会通过反射实现它们的非公共元素 以这种方式开放。

仍然可以使用 < strong > —— add-open 命令行选项,或添加-打开 JAR 文件清单属性,用于打开特定的包。

因此,下面的解决方案将继续工作

# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED

但与 --illegal-access的解决方案将停止工作,从 JDK 17和以后。

添加到您的 JVM 命令行或转到 Eclipse Directory 中的 eclipse.ini文件,并添加以下内容:

--add-opens java.base/java.lang=ALL-UNNAMED

在 Java17之前(因为 Java17删除了这个参数) ,您还需要添加:

--illegal-access=warn

返回文章页面日食:

Windows -> Preferences -> Java -> Installed JREs

在打开的向导中,单击 Add,选择 Standard VM并单击 Next,然后浏览到程序文件中已安装 Java 的路径。对我来说是 C:\Program Files\Java\jre1.8.0_301\lib

单击 Finish,然后选择新添加的 JR。

Right-click on your Project -> Build Path -> Configure Build Path

在打开的向导中,转到 Libraries选项卡,单击 Add Library,然后选择 JRE System Library,单击下一步。选择 Alternate JRE,然后从下拉列表中选择您之前添加的 JRE。单击 Finish。 转到 Order and Export选项卡,选择您添加的 JRE,并将其移到导致问题的其他系统库之上。选择“应用”并关闭。你完蛋了。

仍然可以尝试使用较旧的 JKD 版本。

对于 Eclipse,您需要做两件事

  1. Window-> Preferences-> java-> Compiler 窗口-> 首选项-> java-> 编译器 将编译器遵从级别设置为特定版本 在我的例子中,Eclipse 版本被设置为 JDK 16,我将其还原为1.8,因为我的代码是用1.8编写的

  2. 窗口-> 首选项-> Java-> 已安装的 JRE。 添加 JRE 安装路径(选择 Standard VM)

对我来说很顺利。

在2021年,当我使用 OpenJDK 语言版本16时,这个问题才出现。当我降级到一个更低的版本时,它起作用了。我猜这是因为 OpenJDK 制作了这个: JEP 396: 默认情况下强烈封装 JDK 内部

今天我花了很多时间试图绕过添加—— add-open JVM 选项。升级到 SpringBoot2.6.4(如果使用依赖管理,将升级到 Spring 框架5.3.16)修复了这个问题,而没有添加 JVM 选项。

Https://stackoverflow.com/a/71330846/14396065

在 Java 模块化之后,一些 jdk 内部类不能被访问,因此它们将直接报告错误。因此,在执行 Java 程序时,需要在 VM 配置中添加以下选项:

对于 Intellij 用户,请按照以下步骤操作: 编辑运行/调试配置-> 在 VM 选项中添加以下选项:

--add-opens java.base/java.lang=ALL-UNNAMED

这个链接可能会有帮助: 升级到 Java17