連鎖クラスローダーの謎

私はJavaクラスローダーに苦労しています。多分、誰かがこれを見ているかもしれません。私は問題の本質を以下に抽出しました:

ClassLoaderTest LoadedClass LoadedClassDep の3つのクラスがあります。彼らはすべて異なった道にあります。

ClassLoaderTest instantiates a new URLClassLoader - myClassLoader, priming it with the paths to the remaining two classes and it's own classloader (i.e. the application classloader) as parent. It then uses Class.forName("com.example.LoadedClass", true, myClassLoader) to load the LoadedClass through reflection. The LoadedClass imports the LoadedClassDep. If I run the above, using:

java -cp /path/to/the/ClassLoaderTest ClassLoaderTest "/path/to/LoadedClass" "/path/to/LoadedClassDep"

URLClassLoader を初期化するためにコマンドライン引数を使用すると、すべて正常に動作します。静的初期化子を使用して、2つのクラスに URLClassLoader のインスタンスがロードされていることを確認します。 しかし、私がそうした場合、これは問題です。

java -cp /path/to/the/ClassLoaderTest:/path/to/the/LoadedClass ClassLoaderTest "/path/to/LoadedClassDep"

LoadedClassDep( ClassNotFoundException )がロードされません。 LoadClass は正しくロードされますが、 URLClassLoader ではなく、 sun.misc.Launcher $ AppClassLoader アプリケーションクラスローダーは LoadedClass をロードできるので、 LoadClassDep のロードを試行し、 URLClassLoader は無視します。

ここに完全なソースコードがあります:

package example.bc; public class ClassloaderTest { public static void main(String[] args) { new ClassloaderTest().run(args); } private void run(String[] args) { URLClassLoader myClasLoader = initClassLoader(args); try { Class<?> cls = Class.forName("com.example.bc.LoadedClass", true, myClasLoader); Object obj = cls.newInstance(); cls.getMethod("call").invoke(obj); } catch (Exception e) { e.printStackTrace(); } } private URLClassLoader initClassLoader(String[] args) { URL[] urls = new URL[args.length]; try { for (int i = 0; i < args.length; i++) { urls[i] = new File(args[i]).toURI().toURL(); } } catch (MalformedURLException e) { e.printStackTrace(); } return new URLClassLoader(urls, getClass().getClassLoader()); } } package com.example.bc; import com.bc.LoadedClassDep; public class LoadedClass { static { System.out.println("LoadedClass " + LoadedClass.class.getClassLoader().getClass()); } public void call() { new LoadedClassDep(); } } package com.bc; public class LoadedClassDep { static { System.out.println("LoadedClassDep " + LoadedClassDep.class.getClassLoader().getClass()); } }

私はこれを十分に明確にしたいと思う。私の問題は、コンパイル時に ClassLoadeTest へのパスしかわからないため、実行時に他のパスに文字列を使用する必要があります。ですから、どのようにして2番目のシナリオを作成するのですか?

1

1 答え

クラスローダーが最初に親に委譲するので、アプリケーションクラスローダーが LoadedClass をロードすることを期待しています。これは標準的な動作を参照してください。 2番目のケースでは、 LoadedClass が親のクラスパス上にあるため、 URLClassLoader tryを放棄せずにクラスをロードします。

アプリケーションクラスローダは LoadedClassDep の読み込みを試みます。これは LoadedClass で直接読み込まれ参照されるためです。

public void call() {
    new LoadedClassDep();
}

これらのクラスを実行時に動的にロードする必要がある場合は、このようにクラス間で直接参照することはできません。

クラスローダーの試行順序を変更することもできます - Javaクラスローダー:なぜ親クラスローダーを検索するのですか?を参照してください。

1
追加された
それは正しいようです。親に URLClassloader が委譲しても、呼び出しが開始された場所であるため、ロードされたクラスにはクラスローダーとして残っていることを期待していました。しかし、いいえ、それ以降のAppClassLoaderです。ありがとう
追加された 著者 Blazej Czapp,
ここで私は何をしたのですか? LoadedClass LoadedClassDep だけをロードするために URLClassloader )。したがって、上記のいずれかでない限り、親クラスローダーに委譲します。この場合、クラスローダー自体がロードされます。このように LoadedClass にはカスタムクラスローダーがバインドされており、それに依存関係をロードするように要求します。
追加された 著者 Blazej Czapp,