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} { ... }打開整個模塊,這將使其所有包在運行時(帶有或不帶有反射)可用於所有代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM