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

在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 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}不能向{B}打开{package}

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

  • 库或框架使用反射来调用JDK模块。
    在这种情况下:
  • {A}是一个Java模块(以java.jdk.前缀)
  • {member}{package}是Java API
  • 的一部分

  • {B}是一个库,框架或应用程序模块;经常unnamed module @...
  • 一个基于反射的库/框架,例如Spring,Hibernate,JAXB等,通过应用程序代码反射以访问bean,实体等。
    在这种情况下:
  • {A}是一个应用程序模块
  • {member}{package}是应用程序代码
  • 的一部分

  • {B}是框架模块或unnamed module @...
  • 请注意,某些库(例如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。

    Java-如何将此字符串转换为日期? - java

    我从服务器收到此消息,我不明白T和Z的含义,2012-08-24T09:59:59Z将此字符串转换为Date对象的正确SimpleDateFormat模式是什么? java大神给出的解决方案 这是ISO 8601标准。您可以使用SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM…

    为什么`if(guess!='a'|| guess!='A'||…)`不起作用? - java

    Improve this question 这是我的代码,我知道if语句真的很长,代码可能会更高效,但是我只是想知道答案,因为它使我发疯。while (whileloop == 1) { if (guess != 'a' || guess != 'A' || guess != 'b' || gues…

    输入URL字段时,出现错误“远程URL测试失败:不支持协议'git clone HTTPS'” - java

    当我单击“定义远程”时,输入在Android Studio的Bitbucket站点中提供的HTTPS URL时,出现错误远程URL测试失败:不支持协议'git clone https'我使用了SSH URL,这给了我错误远程URL测试失败:警告:将IP地址“ 18.205.93.0”的RSA主机密钥永久添加到已知主机列表中。无法从远程存储库读取。我什至尝试从…

    运行jar的python子进程给出错误 - java

    在终端中运行此命令可以正常工作:java -jar file.jar --arg1 --arg2 pathTofile 然后,当我尝试使用以下代码从python代码执行此操作时:subprocess.call(['java', '-jar', 'file.jar', '--arg1…

    获取字符的unicode值 - java

    有什么方法可以获取字符的键码吗?例如getKeycode('C'); 有没有类似的东西?谢谢 参考方案 char ch='c'; int code = ch; System.out.println(code); 输出:99 只是为了转义char \,您必须像char ch='\\';一样使用