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