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

时间:2016-12-21 14:34:40

标签: 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)

消息说:

  

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

可以采取哪些措施来避免异常并使程序成功运行?

9 个答案:

答案 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}

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

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

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

    • {A}是一个应用程序模块
    • {member}{package}是应用程序代码的一部分
    • {B}是框架模块或unnamed module @...
  3. 请注意,某些库(例如JAXB)可能会在两个帐户上都失败,因此请仔细查看您所处的情况! 问题中的一个是案例1.

    1。反思调用JDK

    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

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

    在这种情况下,您可能可以编辑反射用于闯入的模块。 (如果不是,那么你的实际情况就是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的解决方案后,我感到很欣慰:

  1. 使用Hibernate 5.0.0或更高版本(早期版本无效)和
  2. 请求构建时字节码增强(使用Gradle,Maven或Ant插件)。
  3. 这避免了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 版本并为我工作