如何解决InaccessibleObjectException("无法使{member}可访问:模块{A}在Java 9上没有'打开{package}'到{B}")?

Nic*_*lai 52 java reflection java-9

在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)
Run Code Online (Sandbox Code Playgroud)

消息说:

无法使受保护的最终java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte [],int,int,java.security.ProtectionDomain)抛出java.lang.ClassFormatError可访问:module java.base没有"打开java.lang"到未命名的模块@ 1941a8ff

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

Nic*_*lai 81

例外是由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;
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

为了确保程序成功运行,必须说服模块系统允许访问setAccessible被调用的元素.所需的所有信息都包含在异常消息中,但有许多机制可以实现这一点.哪个是最好的取决于导致它的确切场景.

无法访问{member}:模块{A}没有'打开{package}'到{B}

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

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

    • {A}是一个Java模块(以java.或为前缀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
Run Code Online (Sandbox Code Playgroud)

如果反射代码在命名模块中,ALL-UNNAMED则可以用其名称替换.

请注意,有时很难找到将此标志应用于实际执行反射代码的JVM的方法.如果有问题的代码是项目构建过程的一部分并且在构建工具生成的JVM中执行,那么这可能会特别困难.

如果要添加的标志太多,您可以考虑使用封装kill开关 --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} { ... },这使得所有包在运行时(有或没有反射)可用于所有代码

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

  • @Nicolai我认为你应该更新你的答案,因为JDK 9现在默认允许非法访问,而`--permit-illegal-access`将改为`--illegal-access`:http://mail.openjdk.java.net /pipermail/jigsaw-dev/2017-May/012673.html (2认同)

Pan*_*kos 37

只是最近的反馈

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

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

总结 严格封装 JDK 的所有内部元素,除了关键的内部 API(例如 sun.misc.Unsafe)。将不再可能通过单个命令行选项来放松内部元素的强封装,这在 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
Run Code Online (Sandbox Code Playgroud)

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


小智 25

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

对于 Eclipse 你需要做两件事。去

  1. 窗口 -> 首选项 -> java -> 编译器 将编译器合规级别设置为特定版本 在我的例子中,Eclipse 版本设置为 JDK 16 我将其恢复为 1.8,因为我的代码是用 1.8 编写的

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

它对我来说很顺利..


Puk*_*oni 9

Java模块化后,有些jdk内​​部类无法访问,就会直接报错。因此,在执行Java程序时,您需要在VM配置中添加以下选项:

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

--add-opens java.base/java.lang=ALL-UNNAMED
Run Code Online (Sandbox Code Playgroud)

此链接可能会有所帮助:升级到 Java 17


小智 7

添加到 JVM 命令行或转到eclipse.iniEclipse 目录中的文件并添加以下内容:

--add-opens java.base/java.lang=ALL-UNNAMED
Run Code Online (Sandbox Code Playgroud)

Java 17 之前的版本(因为 Java 17 删除了此参数)您还需要添加:

--illegal-access=warn
Run Code Online (Sandbox Code Playgroud)


Ala*_*man 5

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

  • 对于特定示例,它看起来像 Hibernate 或 Spring 使用 Javassist 侵入非公开的 defineClass 方法。Lookup.defineClass 方法是专门添加来帮助库注入类的,因此它是该用例的前进方向。对于需要访问其使用者的私有成员的 JPA 和其他库的情况,那么他们将需要记录使用者将包打开到库(对于基于注释的框架(如 JPA),那么这可能在构建时检查) . (4认同)
  • 了解您建议如何“解决他们的问题”会很有帮助。是通过方法还是var句柄?恕我直言,通过读/写私有字段来访问状态本质上并不是坏事,例如,这是 JPA 规范明确预见的事情。 (2认同)

小智 5

这是一个非常具有挑战性的问题。正如其他人所指出的, --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 的一个版本而异。谨慎行事。


小智 5

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


小智 5

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

Window => Preferences => Java => Installed JREs.
Run Code Online (Sandbox Code Playgroud)

我使用添加选项添加了一个新的 JRE(如下所述),浏览到 openJdk 文件夹,选择“确定”。将新的 JDK 设置为默认值。单击应用并关闭。

/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre
Run Code Online (Sandbox Code Playgroud)

它就像一个魅力:)


归档时间:

查看次数:

17195 次

最近记录:

7 年,9 月 前