クラスパスに動的にJARファイルを追加(Java9以降)

【PR】当サイトはプロモーションが含まれています。

Java9 からはクラス ローダーの階層が変更されており、システムクラスローダー( アプリケーションクラスローダー) はクラス jdk.internal.loader.ClassLoaders$AppClassLoader となりました。この内部クラスはURLClassLoaderを継承ないため、メソッド URLClassLoader#addURL を利用できません。

クラス jdk.internal.loader.ClassLoaders$AppClassLoader のメソッド appendToClassPathForInstrumentation を使用します。

public static void addClassPath(ClassLoader classLoader, File file) throws Exception {
  Method method = classLoader.getClass().getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
  method.setAccessible(true);
  method.invoke(classLoader, file.getPath());
}

JDK内部APIへのアクセスのため、このコードだけでは次の様な例外となります。

java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @239963d8
	at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:388)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:364)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:312)
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:203)
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:197)

リフレクションを使用して内部APIにアクセスする場合は、 –add-opens オプションを使用します。無名モジュールの場合は次の様な記述となります。

--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED

Java8 及びそれ以降のバージョンに対応するコードは次の様になります。

/**
 * Add the classpath to the parameter ClassLoader.
 *
 * @param classLoader Collection<File>
 * @param ufileCollection Collection<File>
 * @return ClassLoader
 */
public static ClassLoader addClassPath(ClassLoader classLoader, Collection<File> fileCollection) throws Exception {
  if (getJavaMajorVersion() >= 9) {
    // Java9 or later
    Method method = classLoader.getClass().getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    for (File file : fileCollection) {
      method.invoke(classLoader, file.getPath());
    }
    return classLoader;

  } else {
    // Java8
    URLClassLoader urlClassLoader = null;
    if (classLoader instanceof URLClassLoader) {
      urlClassLoader = (URLClassLoader)classLoader;
    } else {
      URL[] urlsDummy = {};
      urlClassLoader = URLClassLoader.newInstance(urlsDummy, classLoader);
    }
    Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
    method.setAccessible(true);
    for (File file : fileCollection) {
      URL url = file.toURI().toURL();
      method.invoke(urlClassLoader, new Object[] { url });
    }
    return urlClassLoader;
  }
}

github: ext/base/runtime/BcRuntimeUtil.java

Java8については次の記事を参照

クラスパスに動的にJARファイルを追加(Java8)を参照。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です