リフレクションで発生する InaccessibleObjectException
Java9 で導入された JavaPlatform Module System によりリフレクションを利用するフレームワークで以下のような InaccessibleObjectException
が発生する場合がある。
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @7ba4f24f at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:349) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:289) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:174) at java.base/java.lang.reflect.Field.setAccessible(Field.java:168) at com.sun.appserv.ClassLoaderUtil.getField(ClassLoaderUtil.java:308) at com.sun.appserv.ClassLoaderUtil.initForClosingJars(ClassLoaderUtil.java:290) at com.sun.appserv.ClassLoaderUtil.init(ClassLoaderUtil.java:263) at com.sun.appserv.ClassLoaderUtil.releaseLoader(ClassLoaderUtil.java:139) at com.sun.appserv.ClassLoaderUtil.releaseLoader(ClassLoaderUtil.java:111) at org.glassfish.web.loader.WebappClassLoader.stop(WebappClassLoader.java:1956) at org.glassfish.web.loader.WebappClassLoader.preDestroy(WebappClassLoader.java:1917) at org.glassfish.deployment.common.DeploymentContextImpl.getClassLoader(DeploymentContextImpl.java:289) at org.glassfish.deployment.common.DeploymentContextImpl.getClassLoader(DeploymentContextImpl.java:231) at com.sun.enterprise.v3.server.ApplicationLifecycle.prepare(ApplicationLifecycle.java:561) at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:579) at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:556) at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:552) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/javax.security.auth.Subject.doAs(Subject.java:363) at com.sun.enterprise.v3.admin.CommandRunnerImpl$2.execute(CommandRunnerImpl.java:551) at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:582) at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:574) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/javax.security.auth.Subject.doAs(Subject.java:363) at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:573) at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1497) at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1300(CommandRunnerImpl.java:120) at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1879) at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1755) at com.sun.enterprise.admin.cli.embeddable.DeployerImpl.deploy(DeployerImpl.java:137) // ...
上記例外は、JavaEE(Payara) へ war をデプロイする処理で発生したもの。
war のデプロイの前に、WebappClassLoader
の開放処理があり、その中で、JDK の内部クラスである URLClassPath
のフィールドをリフレクションにより取得している。
以下のような実装になっている。
private static void initForClosingJars() throws NoSuchFieldException { // ... loadersField = getField(ucpCLass, URLCLASSPATH_LOADERS_FIELD_NAME); }
ucpCLass
は jdk.internal.loader.URLClassPath
であり、URLCLASSPATH_LOADERS_FIELD_NAME
は "loaders"
というフィールドを指す。
つまり、以下のフィールドをリフレクションにより取得している。
package jdk.internal.loader; public class URLClassPath { // ... private final ArrayList<URLClassPath.Loader> loaders; }
その後、取得した Loader
が保持している JarFile
のクローズを行う前処理である。
さて、jdk.internal.loader
は、java.base
モジュールに含まれており、module-info.java
では以下のようなエクスポート定義となっている。
module java.base {
// ...
exports jdk.internal.loader to
java.instrument,
java.logging;
}
つまり、jdk.internal.loader
パッケージは、java.instrument
モジュールと java.logging
モジュールにしか公開されていない。
そのため、jdk.internal.loader.URLClassPath
を外部からリフレクションでアクセスすることが出来ずに InaccessibleObjectException
が発生する。
Automatic Module(自動モジュール) や Unnamed Module(無名モジュール) によるモジュールロードが行われることで、対象のモジュールは全て exports 扱い(java.base
モジュールを exports しなくとも使える)になるものの、そのモジュール内で、jdk.internal.loader
パッケージが限定公開となっているため如何ともし難い。
JavaPlatform Module System と Automatic Module(自動モジュール) / Unnamed Module(無名モジュール)については以下を参照。
リフレクションを許可する
--add-opens
オプションを利用してアプリケーションを実行することでリフレクションを許可することができる。
--add-opens
オプションの構文は次のとおり。
--add-opens <module>/<package>=<target-module>(,target-module)*
このオプションにより起動することで <module>
にある <package>
を <target-module>
へ公開することを許可することができる。
<target-module>
に ALL-UNNAMED
を使用すれば、すべての Unnamed Module(無名モジュール) に対して公開されることになる。
つまり今回のケースでは以下のようなコマンドライン引数を指定すれば良い(今回はモジュールシステムを使っていないので)。
java --add-opens java.base/jdk.internal.loader=ALL-UNNAMED
例えば Gradle のアプリケーションプラグインで実行する場合には以下のようになる。
application { mainClass = 'xxx' applicationDefaultJvmArgs = ['--add-opens', 'java.base/jdk.internal.loader=ALL-UNNAMED'] }
Gradle Kotlin DSL の場合は以下。
application { mainClass.set("xxx") applicationDefaultJvmArgs = listOf("--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED") }
これにて InaccessibleObjectException は発生しなくなる。
なお、アプリケーション側のモジュール定義の問題であれば、自身のコードの module-info.java
に opens
などでリフレクションに対して可視設定することは言うまでもない。
以上。