混乱
過去には、ソースコードを見ると、Thread.currentThread.getContextClassLoader()を使用してフレームワークのコードに常に遭遇し、現在のスレッドのコンテキストクラスローダーを取得し、このコンテキストクラスローダーを使用してクラスをロードします。
通常、プログラムにコードを記述する場合、クラスを動的にロードする場合、通常はclass.forname()を使用して必要なクラスをロードします。たとえば、最も一般的なことは、JDBCをプログラミングするときに、class.forname()を使用してJDBCドライバーをロードすることです。
try {return class.forname( "oracle.jdbc.driver.oracledriver");} catch(classNotFoundException e){// Skip}クラスをロードしてclass.forname()を使用した場合、クラスが見つからない場合は、thread.currentthread.getContextLoader()で取得したクラスローダーを使用してクラスをロードしようとします。たとえば、次のコードに遭遇する場合があります。
try {return class.forname(classname);} catch(classNotFoundException e){// skip} classloader ctxclassolasser = shood.currentthread()。getContextClassLoader(); if(ctxclassloader!= null){try {clazz = ctxclassloader.loadclass(className); } catch(classNotFoundException e){// Skip}}ここでの大胆な部分は、thread.currentthread.getContextLoader()で取得したローダーを使用してクラスをロードすることです。明らかに、class.forname()のロード時に使用されるクラスローダーは、クラスをthread.currentthread.getContextLoader()で取得したクラスローダーとは異なる場合があります。では、なぜ違いが発生するのでしょうか?
Javaクラスローダー
thread.currentthread.getContextLoader()で取得したこのクラスローダーが使用される理由を理解する前に、まずJVMで使用されているクラスローダー(クラスローダー)を理解しましょう。
デフォルトでは、JVMには3種類のクラスローダーがあります。
ブートストラップクラスローダー
ブートストラップクラスローダークラスローダーは、JDKに組み込まれたクラスローダーで、JDK内のクラスをロードするために使用されます。ブートストラップクラスローダーは、Rt.jarパッケージのクラスなど、JDKの$ JAVA_HOME/JRE/LIB未満のクラスをロードするために使用されます。ブートストラップクラスローダーはJVMの一部であり、一般にネイティブコードで記述されています。
拡張クラスローダー
拡張クラスローダークラスローダーは、主にJDK拡張機能パッケージにクラスをロードするために使用されます。一般に、$ java_home/lib/extの下のパッケージはこのクラスローダーを介してロードされ、このパッケージの下のクラスは基本的にJavaxで始まります。
システムクラスローダー
システムクラスローダークラスローダーは、アプリケーションクラスローダー(AppClassLoader)とも呼ばれます。名前が示すように、このクラスローダーは、開発者が通常書くアプリケーションコードをロードするために使用されます。システムクラスローダーは、クラスパスに保存されているアプリケーションレベルのクラスをロードするために使用されます。
次のコードには、これら3つのクラスローダーがリストされています。
public class mainclass {public static void main(string [] args){system.out.println(integer.class.getClassLoader()); System.out.println(logging.class.getClassLoader()); System.out.println(mainclass.class.getClassLoader()); }}ブートストラップクラスローダーを取得する場所null値は永遠にnull値を返します
null#bootstrapクラスローダーsun.misc.launcher$texclassloader@5e2de80c
親委任モデル
上記で紹介した3つのタイプローダーは分離されておらず、階層的な関係があります。
3つのクラスローダーは、この階層的な関係を介して連携し、クラスのロードを担当します。上記の階層モデルは、クラスローダーの「親代表団」モデルと呼ばれます。親の委任モデルでは、すべてのクラスローダーには、トップレベルのブートストラップクラスローダーを除き、親ローダーが必要です。クラスローダーがクラスにロードされたら、最初にキャッシュにロードされたクラスがあるかどうかを確認します。そうでない場合は、親ローダーが最初にクラスをロードするために委任されます。親ローダーは、リクエストがトップレベルのブートストラップクラスローダーに到達するまで、以前の子ローダーと同じ作業を実行します。親ローダーが必要なクラスをロードできない場合、チャイルドローダーはクラスを単独でロードしようとします。以下と同様に機能します。
JDKのクラスローダーのコードを使用して、親委任メカニズムの実装を確認できます。コードはclassloader.loadclass()に実装されています
回転クラス<?> loadclass(string name、boolean resolve)classNotFoundException同期(getClassLoadingLock(name)){//最初に、クラスがすでにロードされているかどうかを確認します<?> c = findloadedclass(name); if(c == null){long t0 = system.nanotime(); try {if(parent!= null){c = parent.loadclass(name、false); } else {c = findbootstrapclassornull(name); }} catch(classNotFoundException e){// classNotFoundException classが見つかった場合はスローされます。 long t1 = system.nanotime(); c = findclass(name); //これは定義するクラスローダーです。 stats sun.misc.perfcounter.getParentDelegationTime()を記録します。 sun.misc.perfcounter.getfindclasstime()。addelapsedtimefrom(T1); sun.misc.perfcounter.getFindclasses()。increment(); }} if(resolve){resolveclass(c); } return c; }親代表団を使用してクラスローダーを整理することの利点の1つは、安全であることです。文字列クラスを自分で定義する場合、この文字列クラスをデフォルトのJavaでJava.lang.Stringの実装に置き換えたいと考えています。
ClassPathパスに実装した文字列クラスのクラスファイルを配置します。クラスローダーを使用して実装した文字列クラスをロードすると、まずクラスローダーがリクエストを親ローダーに委任し、レイヤーごとにレイヤーごとに削除します。ブートストラップクラスローダーは、最終的にRt.jarパッケージに文字列タイプをロードし、それを完全に返します。このプロセスでは、クラスローダーはクラスパスに配置した文字列クラスを無視します。
親の委任メカニズムが採用されていない場合、システムクラスローダーはクラスパスで文字列クラスファイルを見つけてプログラムにロードし、JDKの文字列実装を上書きします。したがって、クラスローダーのこの作業方法により、Javaプログラムがある程度安全かつ安定して実行できるようになります。
スレッドコンテキストクラスローダー
上記は、非常に多くのクラスローダーに関連するコンテンツについて語っていますが、それでも今日のトピックであるスレッドコンテキストクラスローダーについては説明していません。
この時点で、Javaは3つのタイプのローダーを提供し、厳格な親委任メカニズムに従ってコンサートで機能することがすでにわかっています。表面的には完璧に思えますが、クラスをロードするときにいくつかの制限を引き起こすのは、この厳格な親代表団のメカニズムです。
より基本的なフレームワークがアプリケーションレベルのクラスを使用する必要がある場合、現在のフレームワークで使用されているクラスローダーをロードできる場合、このクラスがロードされた場合にのみこれらのクラスを使用できます。つまり、現在のクラスローダーのクラスローダーを使用することはできません。この制限は、クラスロード要求の委任は一方向であるため、親の委任メカニズムによって引き起こされます。
多くの場合はありませんが、そのような需要はまだあります。典型的なJNDIサービス。 JNDIは、リソースをクエリするためのインターフェイスを提供しますが、特定の実装はさまざまなメーカーによって実装されています。現時点では、JNDIのコードはJVMのブートストラップクラスローダーによってロードされますが、特定の実装はユーザーが提供するJDK以外のコードであるため、システムクラスローダーまたは他のユーザー定義クラスローダーによってのみロードできます。親の委任メカニズムでは、JNDIはJNDIのSPIの実装を取得できません。
この問題を解決するために、スレッドコンテキストクラスローダーが導入されます。 java.lang.thread class setContextClassLoader()のsetContextClassLoader()()(設定されていない場合、デフォルトでは親スレッドから継承されます。プログラムが設定していない場合、システムクラスローダーにデフォルトになります)。スレッドコンテキストクラスローダーを使用すると、アプリケーションで使用されているクラスローダーを、java.lang.thread.setcontextclassoloader()を介してトップレベルのクラスローダーを使用するコードに渡すことができます。たとえば、上記のJNDIサービスは、この方法を使用して、SPI実装をロードして必要なSPI実装クラスを取得できるクラスローダーを取得できます。
スレッドクラスローダーを導入することは、実際には親の委任メカニズムの破壊であることがわかりますが、クラスの負荷に柔軟性を提供します。
疑問を解決します
最初に戻って、フレームワーク以外のユーザーが実装したクラスをロードするために、これらのクラスは、フレームワークで使用されるクラスローダーを介してロードされない場合があります。クラスローダーの親委任モデルをバイパスするために、thread.getContextClassLoader()を使用してこれらのクラスをロードします。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。