Java、クラスパス、クラスローディング=>同じjar /プロジェクトの複数のバージョン


117

これは経験豊富なコーダーにとってばかげた質問かもしれません。しかし、私は私のプロジェクトで使用される他のフレームワーク/ jarのいくつかが必要とするライブラリ(httpクライアント)を持っています。ただし、それらすべてに次のような異なるメジャーバージョンが必要です。

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

クラスローダーは、どういうわけかそれらを分離するのに十分インテリジェントですか?おそらくそうではありませんか?クラスが3つのすべてのjarで同じ場合、クラスローダーはこれをどのように処理しますか。どれがロードされ、なぜですか?

Classloaderはjarを1つだけピックアップしますか、それともクラスを任意に混合しますか?たとえば、クラスがVersion-1.jarからロードされた場合、同じクラスローダーからロードされた他のすべてのクラスはすべて同じjarに入りますか?

この問題をどのように処理しますか?

jarを「required.jar」に何らかの形で「組み込む」ためのトリックがあり、それによって、それらは「1つのユニット/パッケージ」として表示されます。 Classloader、それか、何らかの形でリンクされますか?

回答:


56

クラスローダー関連の問題は非常に複雑な問題です。いずれにしても、いくつかの事実に留意する必要があります。

  • アプリケーションのクラスローダーは通常、単一のクラスローダーではありません。ブートストラップクラスローダーは、適切なものに委任します。新しいクラスをインスタンス化すると、より具体的なクラスローダーが呼び出されます。ロードしようとしているクラスへの参照が見つからない場合は、ブートストラップクラスローダーに到達するまで、その親などに委任します。ロードしようとしているクラスへの参照が見つからない場合は、ClassNotFoundExceptionが発生します。

  • 同じバイナリー名で2つのクラスがあり、同じクラスローダーで検索できる場合、どのクラスをロードしているかを知りたい場合は、特定のクラスローダーがクラス名を解決しようとする方法のみを検査できます。

  • Java言語仕様によると、クラスのバイナリ名には一意性の制約はありませんが、私の知る限り、クラスローダーごとに一意である必要があります。

同じバイナリ名で2つのクラスをロードする方法を見つけることができます。デフォルトの動作をオーバーライドする2つの異なるクラスローダーによってそれら(およびすべての依存関係)をロードする必要があります。大まかな例:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

クラスローダーのカスタマイズは常に難しい作業だと思っていました。可能であれば、互換性のない複数の依存関係を回避することをお勧めします。


13
ブートストラップクラスローダーは、適切なものに委任します。新しいクラスをインスタンス化すると、より具体的なクラスローダーが呼び出されます。ロードしようとしているクラスへの参照が見つからない場合は、その親に委任します。 ご容赦ください。ただし、デフォルトでは親が最初であるクラスローダーのポリシーによって異なります。つまり、子クラスは最初に親にクラスをロードするように要求し、階層全体がそれをロードできなかった場合にのみロードします。
dockingraj 2013年

5
いいえ-通常、クラスローダーはクラス自体を探す前に親に委譲します。Classloaderのクラスjavadocを参照してください。
Joe Kearney

1
Tomcatはここで説明する方法でそれを行うと思いますが、「従来の」委任は最初に親に依頼することです
rogerdpack '26

@deckingraj:グーグル検索した後、これをoracle docsから見つけました:「委譲設計では、クラスローダーは、クラス自体をロードする前にクラスローディングをその親委任します。[...]親クラスローダーがクラスをロードできない場合、クラスローダーはクラス自体をロードしようとします。実際、クラスローダーは、親が使用できないクラスのみをロードする責任があります。さらに調査します。これがデフォルトの実装として現れる場合、それに応じて応答を更新します。(docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html
ルカPutzu

20

各クラスロードは1つのクラスのみを選択します。通常、最初に見つかったもの。

OSGiは、同じjarの複数のバージョンの問題を解決することを目的としています。EquinoxApache Felixは、OSGiの一般的なオープンソース実装です。


6

クラスローダーは、クラスパスに最初にあったjarからクラスをロードします。通常、ライブラリの互換性のないバージョンではパッケージに違いがありますが、まれなケースでは、それらは本当に互換性がなく、1つに置き換えることができません。jarjarを試してください。


6

クラスローダーはオンデマンドでクラスをロードします。これは、アプリケーションと関連ライブラリが最初に必要とするクラスが他のクラスの前にロードされることを意味します。依存クラスをロードする要求は、通常、依存クラスのロードおよびリンクプロセス中に発行されます。

出会う可能性が高い LinkageError重複したクラス定義は、通常のクラスローダのために遭遇していることを示すS(ローダーのクラスパス内に同じ名前の存在の2つの以上のクラスが存在する場合)最初にロードすべきクラスを決定しようとしません。時々、クラスローダーはクラスパスで発生する最初のクラスをロードし、重複するクラスを無視しますが、これはローダーの実装に依存します。

この種のエラーを解決するための推奨される方法は、依存関係が競合するライブラリのセットごとに個別のクラスローダーを使用することです。このようにして、クラスローダーがライブラリーからクラスをロードしようとした場合、依存クラスは、他のライブラリーおよび依存関係にアクセスできない同じクラスローダーによってロードされます。


1

URLClassLoaderfor requireを使用して、jarのdiff-2バージョンからクラスをロードできます。

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.