次のコードは、jarファイルをビルドパスに追加し、Java 8で正常に動作します。ただし、Java 9で例外をスローします。この例外は、 URLClassLoaderへのキャスト。これを解決する方法はありますか?最適なソリューションは、Java 8&9。
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
システムクラスローダーがURLClassLoader
ではなくなったという事実に遭遇しました。 ClassLoader::getSystemClassLoader
の戻り値の型で示されているように、無視できない量のコードが依存していたものの、これは実装の詳細でした。
コメントから判断すると、実行時にクラスを動的にロードする方法を探しています。 Alan Batemanが指摘 のように、これはJava 9ではクラスパスに追加することでは実行できません。
代わりに、そのための新しいクラスローダーの作成を検討する必要があります。これには、新しいクラスがアプリケーションクラスローダーに読み込まれないため、新しいクラスを削除できるという利点もあります。 Java 9に対してコンパイルする場合、 layers を参照する必要があります。これらは完全に新しいモジュールグラフをロードするための明確な抽象化を提供します。
しばらく前にこの問題を見つけました。多くの人が、質問と同様の方法を使用しました
_private static int AddtoBuildPath(File f)
_
実行時にクラスパスに動的にパスを追加します。問題のコードはおそらく複数の面で悪いスタイルです:1)ClassLoader.getSystemClassLoader()
がURLClassLoader
を返すと仮定すると、ドキュメント化されていない実装の詳細であり、2)リフレクションを使用してaddURL
をパブリックにすることはおそらく別のものです。
クラスパスを動的に追加するためのよりクリーンな方法
Loading _Class.forName
_“を通じてクラスをロードするために追加のクラスパスURLを使用する必要がある場合、クリーンでエレガントで互換性のある(Java 8から10)ソリューションは次のとおりです。
1)パブリックaddURL
メソッドを使用して、URLクラスローダーを拡張し、独自のクラスローダーを記述します
_public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
_
2)クラスローダーの(シングルトン/アプリ全体の)オブジェクトを宣言します
_private final MyClassloader classLoader;
_
とそれをインスタンス化
_classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
_
注:システムクラスローダーは親です。 classLoader
を介してロードされるクラスは、this.getClass().getClassLoader()
を使用してロードできるクラスを認識しますが、その逆はできません。
3)必要に応じて(動的に)追加のクラスパスを追加します。
_File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
_
4)シングルトンクラスローダーを介してオブジェクトまたはアプリをインスタンス化する
_cls = Class.forName(name, true, classLoader);
_
注:クラスローダーは、クラス(および親からその親)をロードする前に親クラスローダーへの委任を試みるため、ロードするクラスが親クラスローダーから見えないことを確認して、指定されたクラスローダーを通じてロードされます。これをより明確にするために、システムクラスパスにClassPathB
があり、後でClassPathB
といくつかのClassPathA
をカスタムclassLoader
に追加すると、ClassPathB
の下のクラスはシステムクラスローダーを通じてロードされ、ClassPathA
の下のクラスはそれらに認識されません。ただし、システムクラスパスからClassPathB
を削除すると、そのようなクラスはカスタムclassLoader
を介して読み込まれ、ClassPathAの下のクラスはClassPathBの下のクラスに認識されます。
5)クラスローダーをスレッドに渡すことを検討することができます
_setContextClassLoader(classLoader)
_
そのスレッドがgetContextClassLoader
を使用する場合。
ShadovOracleコミュニティ のスレッドをポイントしました。正しい答えがあります:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
そこに記載されている警告も重要です:
警告:
Java.util.ServiceLoaderは、スレッドのClassLoaderコンテキストThread.currentThread()。setContextClassLoader(specialloader);を使用します。
Java.sql.DriverManagerは、スレッドのClassLoaderではなく、呼び出し元のクラスのClassLoaderを尊重します。 Class.forName( "drivername"、true、new URLClassLoader(urlarrayofextrajarsordirs).newInstance();を使用して直接ドライバを作成します。
javax.activationはスレッドのClassLoaderコンテキストを使用します(javax.mailにとって重要)。
たとえば、現在のクラスパスと同じクラスパスで別のJVMを起動したい場合など、現在のクラスパスを読み取るだけの場合は、次のようにすることができます。
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
デフォルトでは、$ AppClassLoaderクラスの最後のフィールドはリフレクション経由でアクセスできません。追加のフラグをJVMに渡す必要があります。
--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED
私はこれを見つけて、私のために働いた。
文字列pathSeparator = Syste .getProperty( "path.separator"); String [] classPathEntries = System.getProperty( "Java.class.path").split(pathSeparator);
webサイトから https://blog.codefx.org/Java/java-11-migration-guide/#Casting-To-URL-Class-Loader
Ediのソリューションを参照すると、これは私にとってうまくいきました:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* @return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
Eclipse内で実行する場合は、VM引数をJUnit起動/デバッグ構成に設定する必要があります。コマンドラインでmavenを実行するには、2つのオプションがあります。
オプション1
次の行をpom.xmlに追加します:
<plugin>
<groupId>org.Apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
オプション2
実行mvn test -DargLine="-Dsystem.test.property=--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED"