JVM -- クラス ローダー、親委任メカニズム、スレッド コンテキスト クラス ローダー (8)

1. クラスローダー

クラスローダは、java コンパイラがコンパイルしたバイトコードファイル (.class ファイル) をクラスのバイナリ名に従って読み込み、java.lang.Class クラスのインスタンスに変換します。

各インスタンスは Java クラスを表すために使用され、jvm はこれらのインスタンスを使用して Java オブジェクトを生成します。

new などの String オブジェクト; リフレクションは String オブジェクトを生成し、java.lang.Class クラスの String.class オブジェクトを使用します。

基本的に、すべてのクラスローダーは java.lang.ClassLoader クラスのインスタンスです

クラスローダーの 3 つのカテゴリ

クラスローダー 負荷クラス 例証する

クラスローダの起動 (Bootstrap ClassLoader)

JAVA_HOME/jre/lib 直接アクセス不可

拡張 ClassLoader

JAVA_HOME/jre/lib/ext 親は Bootstrap で、null を表示します

アプリケーションクラスローダー

クラスパス 上位者はエクステンション
カスタムクラスローダー カスタマイズ 上位はアプリケーション

  

クラスローダのロード順は以下の通り

  

クラスローダのコアメソッド

メソッド名 例証する
getParent() このクラスローダーの親クラスローダーを返します
loadClass(文字列名) name という名前のクラスをロードし、java.lang.Class クラスのインスタンスを返します。
findClass(文字列名) name という名前のクラスを検索すると、返される結果は java.lang.Class クラスのインスタンスです
findLoadedClass(文字列名) ロードされた name という名前のクラスを見つけ、返された結果は java.lang.Class クラスのインスタンスです
defineClass(String name,byte[] b,int off,int len) バイト配列 b のデータに従って Java クラスに変換され、返される結果は java.lang.Class クラスのインスタンスです。

上記のメソッドの名前パラメーターは、次のようなバイナリ名 (クラスのバイナリ名) です。

java.lang.String <パッケージ名>.<クラス名>

java.concurrent.locks.AbstractQueuedSynchronizer$Node <パッケージ名>.<クラス名>$<内部クラス名>

java.net.URLClassLoader$1 <パッケージ名>.<クラス名>.<匿名内部クラス名>

(1) クラスローダ起動

スタートアップ クラス ローダーは、jvm の実行中に Java コア クラス ライブラリをロードするために jvm に埋め込まれた特別なC++ コードです。

String.class オブジェクトは、ブート クラス ローダーによってロードされます。ブート クラス ローダーによってロードされる特定のコア コードは、値が「sun.boot.class.path」であるシステム プロパティを取得することによって取得できます。

スタートアップ クラス ローダーは Java ネイティブ コードで記述されていないため、java.lang.ClassLoader クラスのインスタンスではなく、getParent メソッドはありません。

public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.mycompany.load.F");
        System.out.println(aClass.getClassLoader()); // AppClassLoader  ExtClassLoader
    }

出力

cd D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>java -Xbootclasspath/a:. com.mycompany.load.Load4
bootstrap F init
null

null を出力し、そのクラス ローダーが Bootstrap ClassLoader であることを示します 

 -Xbootclasspath は、ブートクラスパスを設定することを意味します

その中で /a:. は、現在のディレクトリをブートクラスパスに追加することを意味します

コアクラスをこのメソッドに置き換えます

java -Xbootclasspath:<新しいブートクラスパス>

java -Xbootclasspath/a:<追加パス>

java -Xbootclasspath/p:<追加前のパス>

(2) 拡張クラスローダ

拡張クラス・ローダーは、jvm によって実装された拡張ディレクトリーをロードするために使用され、このディレクトリー内のすべての Java クラスは、このタイプのローダーによってロードされます。

このパスは、 「java.ext.dirs」システム プロパティを取得することで取得できます拡張クラス ローダーは java.lang.ClassLoader クラスのインスタンスであり、その getParent メソッドはブート クラス ローダーを返します (HotSpot 仮想マシンでのブート クラスのロードを示すために null が使用されます)。

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>jar -cvf my.jar com/mycompany/load/F.class
已添加清单
正在添加: com/mycompany/load/F.class(输入 = 481) (输出 = 322)(压缩了 33%)

jar パッケージを JAVA_HOME/jre/lib/ext にコピーし、コードを再度実行します。

(3) アプリケーションクラスローダ

アプリケーション クラス ローダーはシステム クラス ローダーとも呼ばれ、開発者は java.lang.ClassLoader.getSystemClassLoader() メソッドを使用して、このタイプのローダーのインスタンスを取得できるため、システム クラス ローダーの名前が付けられました。主に、プログラム開発者自身が作成した Java クラスのロードを担当します。

一般的に、Java アプリケーションはこのタイプのローダーでロードされ、アプリケーション クラス ローダーによってロードされるクラス パスは、「java.class.path」のシステム プロパティ (つまり、クラスパスと呼ばれることが多い) を取得することによって取得できます。アプリケーション クラス ローダーは java.lang.ClassLoader クラスのインスタンスであり、その getParent メソッドは拡張クラス ローダーを返します。

(4) クラスの名前空間

プログラムの実行中、クラスはバイナリ名 (バイナリ名) によって単純に定義されるのではなく、そのバイナリ名とその定義ローダーによって決定される名前空間 (ランタイム パッケージ) によって共同で決定されます。

同じバイナリ名を持つクラスが異なる定義ローダーによってロードされた場合、返される Class オブジェクトは同じではないため、異なる Class オブジェクトによって作成されたオブジェクトは異なる型になります。

Test cannot be cast to Test 's java.lang.ClassCastExceptionのような 奇妙なエラー は、多くの場合、クラスの同じバイナリ名が異なる定義ローダーによって引き起こされます。

package com.mycompany.load;

import sun.misc.Launcher;

public class Load6 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = new Launcher().getClassLoader(); //1 new一个新的类加载器
        System.out.println(classLoader);

        /*
        这是因为 1处获取的应用类加载器a和jvm用来加载Load6.class对象的应用类加载器b不是同一个实例,
        那么构成这两个类的run-time package也就是不同的。所以即使它们的二进制名字相同,
        但是由a定义的Load6类所创建的对象显然不能转化为由b定义的Load6类的实例。
        这种情况下jvm就会抛出ClassCastException
        * */
        Class<?> aClass = classLoader.loadClass("com.mycompany.load.Load6");
        Load6 load6  = (Load6)aClass.newInstance(); //2
        //Exception in thread "main" java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6
    }
}

例外が報告されます:

java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6

これは、1で取得したアプリケーションクラスローダーaと、jvmがLoad6.classオブジェクトをロードするために使用するアプリケーションクラスローダーbが同一インスタンスではないため、2つのクラスを構成するランタイムパッケージも異なるためです。

したがって、バイナリ名が同じであっても、a で定義された Load6 クラスによって作成されたオブジェクトは、明らかに b で定義された Load6 クラスのインスタンスに変換できません。この場合、jvm は ClassCastException をスローします。

同じバイナリ名を持つクラスは、定義ローダーが異なる場合、2 つの異なるクラスと見なされます

2. 親の委任メカニズム

親委任メカニズム 親委任モデル。親委任モデルとも呼ばれます。いわゆる親デリゲーションとは、クラスローダーの loadClass メソッドを呼び出す際にクラスを見つけるための規則を指します (親、それらの間に継承関係がないため、それらを上位として理解する方が適切です)。

(1) クラスローディングメカニズムのプロセス

Java コンパイラは、Java ソース ファイルを .class ファイルにコンパイルし、JVM によって .class ファイルをメモリにロードします。JVM のロード後、Class オブジェクトのバイトコードが取得されます。バイトコード オブジェクトを使用すると、次を使用してインスタンス化できます。

(2) クラスローダのロード順

  

(3)保護者指定制度のプロセス

1. ローディング クラス MyClass.class は、低レベルから高レベルにレベルごとに委譲されます. 最初に、アプリケーション レイヤ ローダーが拡張クラス ローダーに委譲され、次に拡張クラスがスタートアップ クラス ローダーに委譲されます。

(1) アプリケーションクラスローダにカスタムローダを搭載している場合

(2) アプリケーションクラスローダがクラスローディング要求を拡張クラスローダに委譲

(3) 拡張クラスローダがクラスローディング要求を起動クラスローダに委譲

2. スタートアップ クラス ローダーのロードに失敗し、次に拡張クラス ローダーがロードされ、拡張クラス ローダーのロードに失敗し、最後にアプリケーション クラス ローダーがロードされます。

(1) 起動クラスローダがローディングパス配下のクラスファイルを探してロードする 目的のクラスファイルが見つからない場合は拡張クラスローダでロードする

(2) 拡張クラスローダがローディングパス配下のクラスファイルを探してロードする 目的のクラスファイルが見つからない場合は、アプリケーションクラスローダによってロードされます

(3) アプリケーションのクラスローダーは、ロードパス配下のクラスファイルを探してロードする 目的のクラスファイルが見つからない場合は、カスタムローダーによってロードされる

(4) カスタムローダー配下のユーザーが指定したディレクトリにある Class ファイルを検索してロードする (カスタムロードパスに目的の Class ファイルが見つからない場合は、ClassNotFoud 例外がスローされます)

3. アプリケーション クラス ローダーが見つからない場合、ClassNotFound 例外が報告されます。

(4) ソースコード解析

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查该类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 2. 有上级的话,委派上级 loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        // 3. 如果没有上级了(ExtClassLoader),则委派
                        BootstrapClassLoader
                                c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
                    c = findClass(name);
                    // 5. 记录耗时
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

実行フローは次のとおりです。

1. sun.misc.Launcher$AppClassLoader //1、ロードされたクラスのチェックを開始します。

2. sun.misc.Launcher$AppClassLoader // 2 か所、上位に委任する sun.misc.Launcher$ExtClassLoader.loadClass()

3. sun.misc.Launcher$ExtClassLoader // 1、ロードされたクラスをチェックします。

4. sun.misc.Launcher$ExtClassLoader // 3箇所、上位が無ければBootstrapClassLoaderに委譲して検索

5. BootstrapClassLoader は JAVA_HOME/jre/lib の下のクラスを探しますが、クラスはありません。

6. sun.misc.Launcher$ExtClassLoader // 4、独自の findClass メソッドを呼び出して、JAVA_HOME/jre/lib/ext の下のクラスを検索します。明らかにそうではありません。sun.misc.Launcher$AppClassLoader に戻ります // 2

7. sun.misc.Launcher$AppClassLoader への実行を続行します // 4、独自の findClass メソッドを呼び出し、クラスパスの下を検索して見つけます 

(5) 親権委任の仕組みのメリットとデメリット

アドバンテージ:

1. セキュリティを確保するために、階層関係は優先順位を表します。つまり、すべてのクラスのロードがスタートアップ クラス ローダーに優先され、コア クラス ライブラリ クラスが保証されます。

2. クラスのロードの繰り返しを避ける: 親クラス・ローダーがクラスをロードした場合、子クラス・ローダーが再度ロードする必要がないため、クラスのグローバルな一意性が保証されます。

欠点:

クラスがロードされているかどうかをチェックする委任プロセスは一方向です. この方法は構造的に明確であり、各 ClassLoader の責任を非常に明確にしますが、問題も発生します。最下位の ClassLoader によってロードされたクラス。

通常、スタートアップ クラス ローダー内のクラスは、いくつかの重要なシステム インターフェイスを含むシステム コア クラスですが、アプリケーション クラス ローダー内のクラスはアプリケーション クラスです。このモードによれば、アプリケーション クラスからシステム クラスへのアクセスには問題はありませんが、システム クラスからアプリケーション クラスへのアクセスには問題が生じます

システム クラスでインターフェイスが提供される場合、インターフェイスはアプリケーション クラスで実装する必要があり、インターフェイスはインターフェイスのインスタンスを作成するためのファクトリ メソッドにもバインドされ、インターフェイスとファクトリ メソッドの両方がスタートアップ クラス ローダー。現時点では、ファクトリ メソッドは、アプリケーション クラス ローダーによってロードされたアプリケーション インスタンスを作成できないようです。

3. スレッド コンテキスト クラス ローダー

スレッド コンテキスト クラス ローダーは、クラスの親委任モデルの欠陥を解決するために使用されます。

Java では、公式にJDBC、JBI、JNDI などの多くのSPIインターフェイスが提供されています。このタイプの SPI インターフェースは、公式が仕様を定義するだけであることが多く、特定の実装は JDBC などのサード パーティによって完成されます。JDBC インターフェースの定義に従って、さまざまなデータベース ベンダーが実装する必要があります。

rt.jarこれらの SPI インターフェイスは、通常はパッケージ内にあるJava コア ライブラリによって直接提供され、サード パーティによって実装された特定のコード ライブラリは通常classpath、パスの下に配置されます。そして、ここに質問があります:

パッケージ内にあるSPI インターフェイスrt.jarは Bootstrap クラス ローダーによってロードされ、classpathパスの下の SPI 実装クラスはAppクラス ローダーによってロードされます。

ただ、SPIインターフェースでは実装者のコードが呼ばれることが多いので、一般的には自分の実装クラスを先にロードする必要があるのですが、実装クラスはBootstrapクラスローダのロード範囲に入っていません。親の委任メカニズム、子クラスローダーがクラスの読み込み要求を親クラスローダーに委任して読み込みできることは既に学習しましたが、このプロセスは元に戻すことができませんつまり、親クラス ローダーは、クラスの読み込み要求を独自のサブクラス ローダーに委任して読み込みを行うことはできません。

この時点で、次の疑問が生じます: SPI インターフェースの実装クラスをロードする方法は? それは親の委任モデルを壊しています

SPI (Service Provider Interface): Java の SPI メカニズムは、実際にはプラグ可能なメカニズムです。システムでは、ログモジュール、JDBCモジュールなど、異なるモジュールに分割されることが多く、各モジュールには一般的に複数の実装ソリューションがあります.Javaのコアライブラリに直接ハードコーディングされている場合、ロジックを実装するには、別の実装を置き換える場合は、コア ライブラリ コードを変更する必要があります。これは、プラグ可能なメカニズムの原則に違反します。このような問題を回避するには、プログラムの起動プロセス中に実装者を動的に検出できる動的なサービス検出メカニズムが必要です。SPI は、インターフェイスのサービス実装メカニズムを見つけるためのメカニズムを提供します。次のように:

サードパーティの実装者がサービス インターフェイスの実装を提供する場合、同時に jar パッケージのMETA-INF/services/ディレクトリにサービス インターフェイスにちなんで名付けられたファイルが作成され、このファイルは、サービス インターフェイスを実装する実装クラスです。サービス インターフェイス。外部プログラムがこのモジュールをアセンブルすると、特定の実装クラス名が jar パッケージ META-INF/services/ の構成ファイルから検出され、モジュールの挿入を完了するためにロードおよびインスタンス化されます。

このような契約に基づいて、サービス インターフェイスの実装クラスは、コードで定式化する必要なく、非常にうまく見つけることができます。

同時に、JDK は公式にサービス実装者を見つけるためのツール クラスを提供します: java.util.ServiceLoader

スレッド コンテキスト クラス ローダーは、親の委任モデルの破壊者であり、実行スレッドで親の委任メカニズムのローディング チェーン関係を壊すことができるため、プログラムはクラス ローダーを逆に使用できます。

(1) スレッドコンテキストクラスローダ (Context Classloader)

スレッド コンテキスト クラス ローダー (Context Classloader) は JDK1.2 から導入されました. オンライン クラス ローダーの取得と設定には、クラス Thread の getContextClassLoader() と setContextClassLoader(ClassLoader cl) を使用します。 

setContextClassLoader(ClassLoader cl) によって設定されていない場合、スレッドはその親スレッドのコンテキスト クラス ローダーを継承します。
Java アプリケーション ランタイムの初期スレッドのコンテキスト クラス ローダーは、システム クラス ローダーです。スレッドで実行されるコードは、このクラス ローダーを介してクラスとリソースをロードできます。

親の委譲メカニズムを壊す可能性があります. 親 ClassLoader は、現在のスレッドの Thread.currentThread().getContextClassLoader() によって指定された classLoader を使用してクラスをロードできます.これにより、親 ClassLoader が子 ClassLoader または他の ClassLoader を使用できないことが変更される可能性があります.直接の親子関係を持たないもの 親の委譲モデルを変更するロードされたクラスのケース

SPI の場合、一部のインターフェースは Java コア ライブラリによって提供され、Java コア ライブラリはスタートアップ クラス ローダーによってロードされますが、これらのインターフェースの実装は、異なる jar パッケージ (メーカー提供) と Java スタートアップ クラス ローダーによって提供されます。他のソースから jar パッケージをロードしないため、従来の親委任モデルは SPI の要件を満たすことができません。そして、カレントスレッドにコンテキストクラスローダを設定することで、設定したオンラインクラスローダでインターフェース実装クラスのローディングを実現できます。

Java は多くのコア インターフェイスの定義を提供します。これらのインターフェイスは SPI インターフェイスと呼ばれ、サードパーティの実装クラスのロードを容易にするために、SPI は動的なサービス検出メカニズム (合意) を提供します。クラス、プロジェクトに新しいディレクトリを作成しMETA-INF/services/、そのディレクトリにサービスインターフェイスと同じ名前のファイルを作成すると、プログラムが起動すると、契約に従って仕様を満たすすべての実装クラスが検索され、渡されますロード処理を処理するためのスレッド コンテキスト クラス ローダーに渡す

MySQLDriver驱动类

JDBC を使用する場合、Driver ドライバーをロードする必要があります。

Class.forName("com.mysql.jdbc.Driver")
或
Class.forName("com.mysql.cj.jdbc.Driver")

com.mysql.jdbc.Driver を正しくロードすることも可能です。 

Class.forName("com.mysql.jdbc.Driver")MySQL6.0 以降の jar パッケージでは、以前の com.mysql.jdbc.Driver ドライバーは破棄されていますが、後者はこのように手動で登録する必要がないため、代わりに com.mysql.cj.jdbc.Driver が使用されています。、すべてを処理のためにSPIメカニズムに渡すことができます

JDBC を使用する場合、com.mysql.cj.jdbc.DriverMySQL のドライバー クラスは主に SPI in Java: で定義されたコア クラスを使用し、パッケージ内にあり、さまざまなデータベース ベンダーによって実装されたドライバーを Java で管理するために使用されDriverManagerます。クラスはすべて Java のコア クラスから継承されます。rt.jarDriverjava.sql.Driver

DriverManager のクラスローダー

System.out.println(DriverManager.class.getClassLoader());

null の出力は、そのクラス ローダーが Bootstrap ClassLoader であることを意味し、JAVA_HOME/jre/lib の下でクラスを検索しますが、JAVA_HOME/jre/lib の下には明らかに mysql-connector-java-xx.xx.xx.jar パッケージがありません。 

public class DriverManager {
    // 注册驱动的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驱动
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

loadInitialDrivers() メソッド

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        // 1、使用 ServiceLoader 机制加载驱动,即 SPI
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        // 2、使用 jdbc.drivers 定义的驱动名加载驱动
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                // 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

DriverManager の loadInitialDrivers から、Class.forName("com.mysql.cj.jdbc.Driver") を使用しなくても、後で jdk が ServiceLoader を使用するため、mysql ドライバーもロードできることがわかります。

2 は Class.forName を使用して、アプリケーション クラス ローダーに関連付けられているクラスのロードと初期化を完了するため、クラスのロードを正常に完了することができます。

1 は Service Provider Interface (SPI) です. 協定は次のとおりです. jar パッケージの META-INF/services パッケージの下で, ファイルはインターフェイスの完全修飾名にちなんで名付けられており, ファイルの内容は.実装クラスの名前。

(二)ServiceLoader

ServiceLoader は、サービス プロバイダーをロードするための単純なメカニズムです。通常、サービス プロバイダーは、サービスで定義されたインターフェイスを実装します。サービス プロバイダーは、Java プラットフォームの拡張ディレクトリに拡張 jar パッケージの形式でインストールでき、アプリケーションのクラスパスに追加することもできます。

1. サービス プロバイダーは、パラメーターなしの構築メソッドを提供する必要があります。

2. サービス プロバイダーは、META-INF/services ディレクトリ内の対応するプロバイダー構成ファイルを介しており、構成ファイルのファイル名は、サービス インターフェイスのパッケージ名で構成されています。

3. プロバイダー構成ファイルには、サービス・インターフェースを実装するためのクラスパスが含まれており、各サービス・プロバイダーは 1 行を占有します。

4. ServiceLoader は、プロバイダーをオンデマンドでロードしてインスタンス化します。

5. ServiceLoader は、読み込まれたすべてのサービス プロバイダを返す iterator イテレータを返します。

6. ServiceLoader はスレッドセーフではない

 ServiceLoader を使用する

ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {
    iter.next();
}

例:

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
   Driver dirver = iterator.next();
   System.out.println(dirver.getClass()+", 类加载器:"+dirver.getClass().getClassLoader());
}
System.out.println("当前线程上线文类加载器:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader类加载器:"+loader.getClass().getClassLoader());

ServiceLoader.load メソッド

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

スレッド コンテキスト クラス ローダーは、現在のスレッドで使用されるクラス ローダーです. デフォルトはアプリケーション クラス ローダーです. その中で、Class.forName はスレッド コンテキスト クラス ローダーを呼び出してクラスのロードを完了します. 特定のコードは内部クラス LazyIterator にありますServiceLoaderの

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                        "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                        "Provider " + cn + " could not be instantiated",
                        x);
            }
            throw new Error(); // This cannot happen
        }

参照できます

 Java クラス ローディング サブシステム、ペアレント デリゲーション メカニズム、およびスレッド コンテキスト クラス ローダーの分析

Java でのスレッド コンテキスト クラス ローダーの説明

4. カスタムクラスローダー

1. カスタム クラス ローダーのシナリオ

1.クラスパス以外の任意のパスにクラスファイルをロードする

2. デカップリングが必要な場合は、インターフェイスを介して実装されます (フレームワーク設計で一般的に使用されます)。

3. 異なるアプリケーション内の同じ名前のクラスは、Tomcat コンテナーで一般的な競合なしでロードできます。

2. カスタム クラス ローダーの手順

1. ClassLoader 親クラスを継承する

2.親委任メカニズムに準拠し、findClass メソッドを書き直します

注: loadClass メソッドを書き換えないでください。そうしないと、親委任メカニズムが使用されません。

3. クラスファイルのバイトコードを読む

4. 親クラスの defineClass メソッドを呼び出して、クラスをロードします。

5. ユーザーがクラスローダーの loadClass メソッドを呼び出す

public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("TestServiceImpl");
        Class<?> c2 = classLoader.loadClass("TestServiceImpl");
        System.out.println(c1 == c2);//true

        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("TestServiceImpl");
        //虽然相同类名,但不是同一个类加载器加载的
        System.out.println(c1 == c3);//false

        c1.newInstance();
    }
}

class MyClassLoader extends ClassLoader {

    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "D:\\myclasspath\\" + name + ".class";

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            // 得到字节数组
            byte[] bytes = os.toByteArray();

            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/127230676