スレッドのコンテキストクラスローダーと通常のクラスローダーの違いは何ですか?
つまり、Thread.currentThread().getContextClassLoader()
とgetClass().getClassLoader()
が異なるクラスローダーオブジェクトを返す場合、どちらが使用されますか?
各クラスは、独自のクラスローダーを使用して他のクラスをロードします。したがって、ClassA.class
がClassB.class
を参照する場合、ClassB
は、ClassA
のクラスローダーまたはその親のクラスパス上にある必要があります。
スレッドコンテキストクラスローダーは、現在のスレッドの現在のクラスローダーです。オブジェクトは、ClassLoaderC
のクラスから作成し、ClassLoaderD
が所有するスレッドに渡すことができます。この場合、オブジェクトは、独自のクラスローダーで使用できないリソースをロードする場合、Thread.currentThread().getContextClassLoader()
を直接使用する必要があります。
Javaworld.comには、違いを説明する記事があります=> どのClassLoaderを使用すべきか
(1)
スレッドコンテキストクラスローダーは、クラスローディング委任スキームの裏口を提供します。
JNDIを例にとると、そのガットはrt.jarのbootstrapクラス(J2SE 1.3以降)によって実装されますが、これらのコアJNDIクラスは、独立ベンダーによって実装され、アプリケーションの-classpathに潜在的にデプロイされるJNDIプロバイダーをロードする場合があります。このシナリオでは、親クラスローダー(この場合は原始クラスローダー)を呼び出して、子クラスローダーの1つ(たとえばシステムクラスローダー)から見えるクラスをロードします。通常のJ2SE委任は機能せず、回避策はコアJNDIクラスがスレッドコンテキストローダーを使用するようにすることで、適切な委任とは反対の方向にクラスローダー階層を効果的に「トンネリング」します。
(2)同じソースから:
この混乱は、おそらくしばらくJavaにとどまるでしょう。あらゆる種類の動的リソースのロードを備えたJ2SE APIを使用し、使用するロード戦略を推測してください。サンプリングは次のとおりです。
- JNDIはコンテキストクラスローダーを使用します
- Class.getResource()およびClass.forName()は現在のクラスローダーを使用します
- JAXPはコンテキストクラスローダーを使用します(J2SE 1.4以降)
- Java.util.ResourceBundleは、呼び出し元の現在のクラスローダーを使用します
- Java.protocol.handler.pkgsシステムプロパティで指定されたURLプロトコルハンドラーは、bootstrapおよびシステムクラスローダーでのみ検索されます。
- Java Serialization APIはデフォルトで呼び出し元の現在のクラスローダーを使用します
これは元の質問には答えませんが、質問はContextClassLoader
クエリに対して上位にランク付けされリンクされているため、コンテキストクラスローダーをいつ使用するかという関連する質問に答えることが重要だと思います。短い答え:コンテキストクラスローダーを使用しない!ただし、ClassLoader
パラメーターが欠落しているメソッドを呼び出す必要がある場合は、getClass().getClassLoader()
に設定します。
あるクラスのコードが別のクラスをロードするように要求する場合、使用する正しいクラスローダーは呼び出し側クラスと同じクラスローダーです(つまり、getClass().getClassLoader()
)。 JVM自体が行うこと 初めて新しいクラスのインスタンスを構築する、静的メソッドを呼び出す、または静的フィールドにアクセスするので、これは物事が99.9%の時間で動作する方法です。
リフレクションを使用してクラスを作成する場合(設定可能な名前付きクラスをデシリアライズまたはロードする場合など)、リフレクションを行うライブラリは常にアプリケーションに問い合わせるどのクラスローダーを使用するか、ClassLoader
アプリケーションからのパラメーターとして。アプリケーション(構築が必要なすべてのクラスを知っている)は、getClass().getClassLoader()
を渡す必要があります。
クラスローダーを取得するその他の方法は正しくありません。ライブラリが Thread.getContextClassLoader()
、 Sun.misc.VM.latestUserDefinedLoader()
、または Sun.reflect.Reflection.getCallerClass()
などのハックを使用している場合、APIの不足が原因のバグです。基本的に、Thread.getContextClassLoader()
は、ObjectInputStream
APIを設計した人がClassLoader
をパラメーターとして受け入れるのを忘れたためにのみ存在し、この間違いはJavaコミュニティに今日まで出没しています。
ただし、多くのJDKクラスでは、使用するクラスローダーを推測するために、いくつかのハックの1つを使用しています。一部のユーザーはContextClassLoader
(共有スレッドプールで別のアプリを実行するとき、またはContextClassLoader null
を離れると失敗します)を使用し、一部はクラスの直接呼び出し元がそれ自体のとき失敗しますライブラリ)、システムクラスローダー(CLASSPATH
のクラスのみを使用することが文書化されている限り)またはbootstrapクラスローダーを使用するものと、予測不可能な組み合わせを使用するもの上記のテクニックのうち、(混乱を招くだけです)。これは、歯の多くの泣きと歯ぎしりをもたらしました。
このようなAPIを使用する場合、最初に、クラスローダーをパラメーターとして受け入れるメソッドのオーバーロードを見つけてみてください。賢明なメソッドがない場合は、API呼び出しの前にContextClassLoader
を設定(および後でリセット)してみてください。
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
@David Rousselの回答に加えて、クラスは複数のクラスローダーによってロードされる場合があります。
class loader の仕組みを理解しましょう。
Javarevisitedのjavin paulブログから:
ClassLoader
は3つの原則に従います。
クラスは、必要なときにJavaにロードされます。 Abc.classというアプリケーション固有のクラスがあるとします。このクラスをロードする最初の要求は、Application ClassLoaderに送られ、親Extension ClassLoaderに委任され、さらにPrimordialまたはBootstrapクラスローダーに委任されます。
Bootstrap ClassLoaderは、rt.jarから標準JDKクラスファイルをロードする役割を果たし、Javaのすべてのクラスローダーの親です。 Bootstrapクラスローダーには親がありません。
Extension ClassLoaderクラスの読み込み要求をその親Bootstrapに委任し、失敗した場合は、jre/lib/extディレクトリまたはJava.ext.dirsシステムプロパティが指すその他のディレクトリからクラスを読み込みます
システムまたはアプリケーションクラスローダー。CLASSPATH環境変数、-classpathまたは-cpコマンドラインオプション、JAR内のマニフェストファイルのClass-Path属性からアプリケーション固有のクラスをロードします。
アプリケーションクラスローダーは拡張クラスローダーの子であり、Sun.misc.Launcher$AppClassLoader
クラスによって実装されます。
注:Bootstrap class loaderを除きます。これはほとんどがC言語のネイティブ言語で実装され、すべてJavaクラスローダーは、Java.lang.ClassLoader
を使用して実装されます。
可視性の原則に従って、Child ClassLoaderは、Parent ClassLoaderによってロードされたクラスを見ることができますしかし、その逆は真実ではありません。
この原則によれば、Parentによってロードされたクラスは、Child ClassLoaderによって再びロードされるべきではありません。