Java 9 のリリースノートから学びました
アプリケーションクラスローダーは、Java.net.URLClassLoader(以前のリリースでは指定されていなかった実装の詳細)のインスタンスではなくなりました。 ClassLoader :: getSytemClassLoaderがURLClassLoaderオブジェクトを返すことを前提とするコードは、更新する必要があります。
これにより、次のようにクラスパスをスキャンする古いコードが破損します。
Java <= 8
_URL[] ressources = ((URLClassLoader) classLoader).getURLs();
_
にぶつかる
_Java.lang.ClassCastException:
Java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to
Java.base/Java.net.URLClassLoader
_
Java 9 +では、次の回避策が Apache IgniteプロジェクトのPR として提案されました。 JVMランタイムオプション:_--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED
_。ただし、以下のコメントで言及されているように、このPRはマスターブランチにマージされませんでした。
_/*
* Java 9 + Bridge to obtain URLs from classpath...
*/
private static URL[] getURLs(ClassLoader classLoader) {
URL[] urls = new URL[0];
try {
//see https://github.com/Apache/ignite/pull/2970
Class builtinClazzLoader = Class.forName("jdk.internal.loader.BuiltinClassLoader");
if (builtinClazzLoader != null) {
Field ucpField = builtinClazzLoader.getDeclaredField("ucp");
ucpField.setAccessible(true);
Object ucpObject = ucpField.get(classLoader);
Class clazz = Class.forName("jdk.internal.loader.URLClassPath");
if (clazz != null && ucpObject != null) {
Method getURLs = clazz.getMethod("getURLs");
if (getURLs != null) {
urls = (URL[]) getURLs.invoke(ucpObject);
}
}
}
} catch (NoSuchMethodException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
logger.error("Could not obtain classpath URLs in Java 9+ - Exception was:");
logger.error(e.getLocalizedMessage(), e);
}
return urls;
}
_
ただし、これは Reflection を使用しているため、深刻な頭痛の種になります。これは一種のアンチパターンであり、 forbidden-apis maven plugin によって厳密に批判されています:
禁止されたメソッドの呼び出し:Java.lang.reflect.AccessibleObject#setAccessible(boolean)[アクセスフラグを回避するためのリフレクションの使用はSecurityManagersで失敗し、おそらくJava 9]のランタイムクラスでは動作しません。
クラスパス/モジュールパス内のすべてのリソースURLs
のリストにアクセスするsafe方法はありますか? OpenJDK 9/10で_Sun.misc.*
_インポートを使用せずに(たとえば、Unsafe
を使用して)与えられたクラスローダー?
[〜#〜] update [〜#〜](コメントに関連)
私はできることを知っている
_ String[] pathElements = System.getProperty("Java.class.path").split(System.getProperty("path.separator"));
_
クラスパスの要素を取得し、URL
sに解析します。ただし、私が知る限り、このプロパティは、アプリケーションの起動時に指定されたクラスパスのみを返します。ただし、コンテナ環境では、これはアプリケーションサーバーの1つであり、十分ではない場合があります。 EARバンドルを使用します。
更新2
コメントありがとうございます。 System.getProperty("Java.class.path")
がourの目的で機能するかどうかをテストし、これでニーズが満たされる場合は質問を更新します。
ただし、他のプロジェクト(他の理由、たとえばApache TomEE 8など)はURLClassLoader
-に関連する同じ痛みに苦しんでいるようですこの理由から、それは価値のある質問だと思います。
これは XY問題 であると思います。クラスパス上のすべてのリソースのURLへのアクセスは、Javaでサポートされている操作ではないため、実行するのは良いことではありません。この質問で既に見たように、これを行おうとすると、フレームワーク全体になります。ソリューション(カスタムクラスローダー、EEコンテナーなど)を破壊するEdgeケースが100万件あります。
これを行う理由を詳しく説明していただけますか?
ある種のプラグインシステムがあり、実行時に提供されたコードとインターフェイスするモジュールを探している場合は、 ServiceLoader API 、つまり:
クラスパス用のJARファイルとしてパッケージ化されているサービスプロバイダーは、プロバイダーの設定ファイルをリソースディレクトリに配置することで識別されます
META-INF/services
。プロバイダー構成ファイルの名前は、サービスの完全修飾バイナリ名です。プロバイダー構成ファイルには、サービスプロバイダーの完全修飾バイナリ名のリストが1行に1つずつ含まれています。たとえば、サービスプロバイダーcom.example.impl.StandardCodecs
は、クラスパスのJARファイルにパッケージ化されています。 JARファイルには、次の名前のプロバイダー構成ファイルが含まれます。META-INF/services/com.example.CodecFactory
次の行が含まれます。
com.example.impl.StandardCodecs # Standard codecs
知る限り、Java.class.path
URLを取得するシステムプロパティ:
String classpath = System.getProperty("Java.class.path");
String[] entries = classpath.split(File.pathSeparator);
URL[] result = new URL[entries.length];
for(int i = 0; i < entries.length; i++) {
result[i] = Paths.get(entries[i]).toAbsolutePath().toUri().toURL();
}
System.out.println(Arrays.toString(result)); // e.g. [file:/J:/WS/Oxygen-Stable/jdk10/bin/]