module java.base does not "opens java.lang" to unnamed module

在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} { ... }打开整个模块,这将使其所有包在运行时(带有或不带有反射)可用于所有代码

升级JDK17出现:module java.base does not “opens java.lang.reflect“ to unnamed module 

解决方法 增加运行参数

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

posted @ 2021-08-08 10:29  滔天蟹  阅读(50131)  评论(2编辑  收藏  举报