web-dev-qa-db-ja.com

Java 9、ClassLoader.getSystemClassLoaderとの互換性の問題

次のコードは、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;
}
11
Mostafa abdo

システムクラスローダーがURLClassLoader ではなくなったという事実に遭遇しました。 ClassLoader::getSystemClassLoaderの戻り値の型で示されているように、無視できない量のコードが依存していたものの、これは実装の詳細でした。

コメントから判断すると、実行時にクラスを動的にロードする方法を探しています。 Alan Batemanが指摘 のように、これはJava 9ではクラスパスに追加することでは実行できません。

代わりに、そのための新しいクラスローダーの作成を検討する必要があります。これには、新しいクラスがアプリケーションクラスローダーに読み込まれないため、新しいクラスを削除できるという利点もあります。 Java 9に対してコンパイルする場合、 layers を参照する必要があります。これらは完全に新しいモジュールグラフをロードするための明確な抽象化を提供します。

6
Nicolai

しばらく前にこの問題を見つけました。多くの人が、質問と同様の方法を使用しました

_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を使用する場合。

3
Christian Fries

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にとって重要)。

2
PaL

たとえば、現在のクラスパスと同じクラスパスで別の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
1
Edi

私はこれを見つけて、私のために働いた。

文字列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

0

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"

0
Splioo