128

在 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

可以做些什么来避免异常并让程序成功运行?

4

17 回答 17

172

该异常是由 Java 9 中引入的Java Platform Module System引起的,特别是它对强封装的实现。它只允许在某些条件下访问,最突出的是:

  • 类型必须是公开的
  • 必须导出拥有的包

同样的限制也适用于反射,导致异常的代码试图使用反射。更准确地说,异常是由对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} 未向 {B}“打开 {package}”

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

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

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

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

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

1.对JDK的反射调用

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

因此,在上述情况下(缩短)...

无法使 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 中执行,这可能会特别困难。

如果要添加的标志太多,您可以考虑使用封装终止开关 --permit-illegal-access。它将允许类路径上的所有代码反映整个命名模块。请注意,此标志 仅适用于 Java 9

2. 对应用程序代码的反思

在这种情况下,您可能可以编辑反射用于闯入的模块。(如果不是,那么您实际上是第一种情况。)这意味着命令行标志不是必需的,而是{A}可以使用 module 的描述符来打开其内部。有多种选择:

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

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

于 2016-12-21T14:34:40.087 回答
15

对于 Eclipse,您需要做两件事,转到

  1. Window -> Preference -> java -> Compiler Set Compiler Compliance Level to specific version 在我的例子中,Eclipse 版本设置为 JDK 16 我将其恢复为 1.8,因为我的代码是用 1.8 编写的

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

它对我来说很顺利..

于 2021-09-24T05:22:48.540 回答
11

只是最近的反馈

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

根据 Oracle 的说法,随着JEP 403 (link1)JEP 403 (link2)已决定从 JDK 17 及更高版本交付,启动器选项--illegal-access将停止工作!

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

通过此更改,最终用户将不再可能使用 --illegal-access 选项来启用对 JDK 内部元素的访问。(受影响的包列表可在此处获得。) sun.misc 和 sun.reflect 包仍将由 jdk.unsupported 模块导出,并且仍将打开,以便代码可以通过反射访问它们的非公共元素。不会以这种方式打开其他 JDK 包。

仍然可以使用--add-opens 命令行选项或 Add-Opens JAR 文件清单属性来打开特定的包。

所以以下解决方案将继续工作

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

但是解决方案--illegal-access将从此停止工作JDK 17

于 2021-09-13T09:35:11.160 回答
7

使用 --add-opens 应该被视为一种解决方法。正确的做法是让 Spring、Hibernate 和其他非法访问的库来解决他们的问题。

于 2017-03-27T10:25:41.583 回答
7

这是一个非常难以解决的问题;正如其他人所指出的, --add-opens 选项只是一种解决方法。一旦 Java 9 公开可用,解决基本问题的紧迫性只会增加。

我在 Java 9 上测试基于 Hibernate 的应用程序时收到这个确切的 Javassist 错误后发现自己出现在此页面上。由于我的目标是在多个平台上支持 Java 7、8 和 9,因此我努力寻找最佳解决方案。(请注意,当 Java 7 和 8 JVM 在命令行上看到无法识别的“--add-opens”参数时,它们将立即中止;因此无法通过对批处理文件、脚本或快捷方式进行静态更改来解决此问题。)

很高兴能从主流库(例如 Spring 和 Hibernate)的作者那里获得官方指导,但是距离目前预计的 Java 9 发布还有 100 天,这个建议似乎仍然很难找到。

经过大量的实验和测试,我松了一口气,找到了 Hibernate 的解决方案:

  1. 使用 Hibernate 5.0.0 或更高版本(早期版本不起作用),并且
  2. 请求构建时字节码增强(使用 Gradle、Maven 或 Ant 插件)。

这避免了 Hibernate 在运行时执行基于 Javassist 的类修改的需要,消除了原始帖子中显示的堆栈跟踪。

但是,您应该在之后彻底测试您的应用程序。Hibernate 在构建时应用的字节码更改似乎与在运行时应用的不同,导致应用程序行为略有不同。当我启用构建时字节码增强时,我的应用程序中成功多年的单元测试突然失败了。(我不得不追查新的 LazyInitializationExceptions 和其他问题。)而且行为似乎从一个版本的 Hibernate 到另一个版本有所不同。谨慎行事。

于 2017-04-07T03:42:24.813 回答
7

添加 --illegal-access=warn 和 --add-opens java.base/java.lang=ALL-UNNAMED 到你的 eclipse.ini

于 2021-06-30T10:32:49.173 回答
4

我在 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

它就像一个魅力:)

于 2021-07-12T15:27:14.230 回答
3

我收到了休眠 5 的警告。

Illegal reflective access by javassist.util.proxy.SecurityActions

我将最新的 javassist 库添加到依赖项 gradle:

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

这解决了我的问题。

于 2018-01-12T11:12:55.247 回答
3

2021 年,我使用 OpenJDK 语言版本 16 时才出现此问题。当我降级到较低版本时,它才有效。我猜这是因为 OpenJDK 做了这个:JEP 396: Strongly Encapsulate JDK Internals by Default

于 2021-09-30T07:44:13.817 回答
2

转到 Eclipse 目录中的 eclipse.ini 文件并添加以下内容。这将解决问题。

--非法访问=警告

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

于 2021-09-15T09:16:37.670 回答
1

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

现有包:Java 16.0.2、MacOS (BigSur)

  • 安装 sonar_scanner
  • 运行这个命令:export SONAR_SCANNER_OPTS="--illegal-access=permit"

希望这对遇到同样问题的人有所帮助,她/他可以尝试一下。:-)

于 2021-09-13T04:48:58.707 回答
1

在日食中:

Windows -> Preferences -> Java -> Installed JREs

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

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

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

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

于 2021-09-16T08:12:39.573 回答
0

编译时如何告诉eclipse添加导出

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

于 2021-07-13T03:29:51.103 回答
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

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

它对我有用。

于 2021-07-27T09:05:52.683 回答
0

对我来说,我只是更改了 spring-boot-starter-web 版本并为我工作

于 2021-07-31T20:56:23.257 回答
0

我今天花了很多时间试图绕过添加 --add-opens JVM 选项。升级到 SpringBoot 2.6.4(如果您使用依赖管理,将升级到 spring 框架 5.3.16)在不添加 JVM 选项的情况下修复了该问题。

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

于 2022-03-03T00:48:00.673 回答
-1

面临同样的问题。原因之一可能是使用了错误的 JDK / Project SDK。

要在 Intellij 中解决此问题:

右键单击 Maven 项目 -> 打开模块设置 -> 项目设置 -> 项目 -> 选择您的 SDK 以已安装 JDK 1.8

它对我有用!

于 2021-08-12T08:51:07.680 回答