web-dev-qa-db-ja.com

Java 9/10のクラスパスにあるすべてのリソースファイルのURLに安全にアクセスする方法は?

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"));
_

クラスパスの要素を取得し、URLsに解析します。ただし、私が知る限り、このプロパティは、アプリケーションの起動時に指定されたクラスパスのみを返します。ただし、コンテナ環境では、これはアプリケーションサーバーの1つであり、十分ではない場合があります。 EARバンドルを使用します。

更新2

コメントありがとうございます。 System.getProperty("Java.class.path")ourの目的で機能するかどうかをテストし、これでニーズが満たされる場合は質問を更新します。

ただし、他のプロジェクト(他の理由、たとえばApache TomEE 8など)URLClassLoader-に関連する同じ痛みに苦しんでいるようですこの理由から、それは価値のある質問だと思います。

44
rzo

これは 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
18
Rich

知る限り、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/]
9
Jorn Vernee