如何解决 Java 9 上的 InaccessibleObjectException(“无法使 {member} 可访问:模块 {A} 不会向 {B} '打开 {package}'”)?

2023-11-28

在 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

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


该异常是由以下原因引起的Java平台模块系统Java 9 中引入了这一点,特别是其强封装的实现。 它只允许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被称为。 所需的所有信息都包含在异常消息中,但有多项机制为了达成这个。 哪一种是最好的取决于导致它的具体场景。

无法使 {member} 可访问:模块 {A} 未向 {B} “打开 {package}”

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

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

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

    • {A}是一个应用程序模块
    • {member} and {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. 对应用程序代码的反思

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

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

See 这个帖子对这些方法进行更详细的讨论和比较。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何解决 Java 9 上的 InaccessibleObjectException(“无法使 {member} 可访问:模块 {A} 不会向 {B} '打开 {package}'”)? 的相关文章

随机推荐