" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java類加載器與雙親委派機制和線程上下文類加載器專項解讀分析

 更新時間:2022年12月22日 15:29:05   作者:MinggeQingchun  
類加載器負(fù)責(zé)讀取Java字節(jié)代碼,并轉(zhuǎn)換成java.lang.Class類的一個實例的代碼模塊。本文主要和大家聊聊JVM類加載器ClassLoader的使用,需要的可以了解一下

一、類加載器

類加載器就是根據(jù)類的二進制名(binary name)讀取java編譯器編譯好的字節(jié)碼文件(.class文件),并且轉(zhuǎn)化生成一個java.lang.Class類的一個實例。

每個實例用來表示一個Java類,jvm就是用這些實例來生成java對象的。

如new一個String對象;反射生成一個String對象,都會用到String.class 這個java.lang.Class類的對象。

基本上所有的類加載器都是java.lang.ClassLoader 類的一個實例

類加載器3大分類

類加載器加載類說明
啟動類加載器(Bootstrap ClassLoader)JAVA_HOME/jre/lib無法直接訪問
拓展類加載器(Extension ClassLoader)JAVA_HOME/jre/lib/ext上級為 Bootstrap,顯示為 null
應(yīng)用類加載器(Application ClassLoader)classpath上級為 Extension
自定義類加載器自定義上級為 Application

類加載器加載順序如下

類加載器的核心方法

方法名說明
getParent()返回該類加載器的父類加載器
loadClass(String name)加載名為name的類,返回java.lang.Class類的實例
findClass(String name)查找名字為name的類,返回的結(jié)果是java.lang.Class類的實例
findLoadedClass(String name)查找名字為name的已經(jīng)被加載過的類,返回的結(jié)果是java.lang.Class類的實例
defineClass(String name,byte[] b,int off,int len)根據(jù)字節(jié)數(shù)組b中的數(shù)據(jù)轉(zhuǎn)化成Java類,返回的結(jié)果是java.lang.Class類的實例

上述方法的name參數(shù)都是binary name(類的二進制名字),如

java.lang.String <包名>.<類名>

java.concurrent.locks.AbstractQueuedSynchronizer$Node <包名>.<類名>$<內(nèi)部類名>

java.net.URLClassLoader$1 <包名>.<類名>.<匿名內(nèi)部類名>

1.啟動類加載器

啟動類加載器是jvm在運行時,內(nèi)嵌在jvm中的一段特殊的用來加載java核心類庫的C++代碼

String.class 對象就是由啟動類加載器加載的,啟動類加載器具體加載哪些核心代碼可以通過獲取值為 "sun.boot.class.path" 的系統(tǒng)屬性獲得。

啟動類加載器不是java原生代碼編寫的,所以其也不是java.lang.ClassLoader類的實例,其沒有g(shù)etParent方法

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 表示設(shè)置 bootclasspath

其中 /a:. 表示將當(dāng)前目錄追加至 bootclasspath 之后

用這個辦法替換核心類

java -Xbootclasspath:<new bootclasspath>

java -Xbootclasspath/a:<后追加路徑>

java -Xbootclasspath/p:<前追加路徑>

2.拓展類加載器

拓展類加載器用來加載jvm實現(xiàn)的一個拓展目錄,該目錄下的所有java類都由此類加載器加載。

此路徑可以通過獲取"java.ext.dirs"的系統(tǒng)屬性獲得。拓展類加載器就是java.lang.ClassLoader類的一個實例,其getParent方法返回的是引導(dǎo)類加載器(在 HotSpot虛擬機中用null表示引導(dǎo)類加載)

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,重新執(zhí)行代碼即可

3.應(yīng)用類加載器

應(yīng)用類加載器又稱為系統(tǒng)類加載器,開發(fā)者可用通過 java.lang.ClassLoader.getSystemClassLoader()方法獲得此類加載器的實例,系統(tǒng)類加載器也因此得名。其主要負(fù)責(zé)加載程序開發(fā)者自己編寫的java類

一般來說,java應(yīng)用都是用此類加載器完成加載的,可以通過獲取"java.class.path"的系統(tǒng)屬性(也就是我們常說的classpath)來獲取應(yīng)用類加載器加載的類路徑。應(yīng)用類加載器是java.lang.ClassLoader類的一個實例,其getParent方法返回的是拓展類加載器

4.類的命名空間

在程序運行過程中,一個類并不是簡單由其二進制名字(binary name)定義的,而是通過其二進制名和其定義加載器所確定的命名空間(run-time package)所共同確定的。

同一個二進制名的類由不同的定義加載器加載時,其返回的Class對象不是同一個,那么由不同的Class對象所創(chuàng)建的對象,其類型也不是相同的。

類似Test cannot be cast to Test的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處獲取的應(yīng)用類加載器a和jvm用來加載Load6.class對象的應(yīng)用類加載器b不是同一個實例,
        那么構(gòu)成這兩個類的run-time package也就是不同的。所以即使它們的二進制名字相同,
        但是由a定義的Load6類所創(chuàng)建的對象顯然不能轉(zhuǎn)化為由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處獲取的應(yīng)用類加載器a和jvm用來加載Load6.class對象的應(yīng)用類加載器b不是同一個實例, 那么構(gòu)成這兩個類的run-time package也就是不同的。

所以即使它們的二進制名字相同, 但是由a定義的Load6類所創(chuàng)建的對象顯然不能轉(zhuǎn)化為由b定義的Load6類的實例。 這種情況下jvm就會拋出ClassCastException

相同二進制名字的類,如果其定義加載器不同,也算是不同的兩個類

二、雙親委派機制

雙親委派機制 Parent Delegation Model,又稱為父級委托模型。所謂的雙親委派,就是指調(diào)用類加載器的 loadClass 方法時,查找類的規(guī)則(雙親,理解為上級更為合適,因為它們之間并沒有繼承關(guān)系)

1.類加載機制流程

Java編譯器把Java源文件編譯成.class文件,再由JVM裝載.class文件到內(nèi)存中,JVM裝載完成后得到一個Class對象字節(jié)碼。有了字節(jié)碼對象,就可以實例化使用

2.類加載器加載順序

3.雙親委派機制流程

1、加載類MyClass.class,從低層級到高層級一級一級委派,先由應(yīng)用層加載器委派給擴展類加載器,再由擴展類委派給啟動類加載器

(1)如果是自定義加載器掛載到應(yīng)用程序類加載器

(2)應(yīng)用程序類加載器將類加載請求委托給擴展類加載器

(3)擴展類加載器將類加載請求委托給啟動類加載器

2、啟動類加載器載入失敗,再由擴展類加載器載入,擴展類加載器載入失敗,最后由應(yīng)用類加載器載入

(1)啟動類加載器在加載路徑下查找并加載Class文件,如果未找到目標(biāo)Class文件,則交由擴展類加載器加載

(2)擴展類加載器在加載路徑下查找并加載Class文件,如果未找到目標(biāo)Class文件,則交由應(yīng)用程序類加載器加載

(3)應(yīng)用程序類加載器在加載路徑下查找并加載Class文件,如果未找到目標(biāo)Class文件,則交由自定義加載器加載

(4)在自定義加載器下查找并加載用戶指定目錄下的Class文件,如果在自定義加載路徑下未找到目標(biāo)Class文件,則拋出ClassNotFoud異常

3、如果應(yīng)用類加載器也找不到那就報ClassNotFound異常了

4.源碼分析

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 檢查該類是否已經(jīng)加載
            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. 每一層找不到,調(diào)用 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;
        }
    }

執(zhí)行流程為:

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 處,調(diào)用自己的 findClass 方法,是在 JAVA_HOME/jre/lib/ext 下找 類,顯然沒有,回到 sun.misc.Launcher$AppClassLoader 的 // 2 處

7、繼續(xù)執(zhí)行到 sun.misc.Launcher$AppClassLoader // 4 處,調(diào)用它自己的 findClass 方法,在 classpath 下查找,找到了

5.雙親委派機制優(yōu)缺點

優(yōu)點:

1、保證安全性,層級關(guān)系代表優(yōu)先級,也就是所有類的加載,優(yōu)先給啟動類加載器,這樣就保證了核心類庫類

2、避免類的重復(fù)加載,如果父類加載器加載過了,子類加載器就沒有必要再去加載了,確保一個類的全局唯一性

缺點:

檢查類是否加載的委派過程是單向的, 這個方式雖然從結(jié)構(gòu)上說比較清晰,使各個 ClassLoader 的職責(zé)非常明確, 但是同時會帶來一個問題, 即頂層的ClassLoader 無法訪問底層的ClassLoader 所加載的類

通常情況下, 啟動類加載器中的類為系統(tǒng)核心類, 包括一些重要的系統(tǒng)接口,而在應(yīng)用類加載器中, 為應(yīng)用類。 按照這種模式, 應(yīng)用類訪問系統(tǒng)類自然是沒有問題, 但是系統(tǒng)類訪問應(yīng)用類就會出現(xiàn)問題。

如在系統(tǒng)類中提供了一個接口, 該接口需要在應(yīng)用類中得以實現(xiàn), 該接口還綁定一個工廠方法, 用于創(chuàng)建該接口的實例, 而接口和工廠方法都在啟動類加載器中。 這時, 就會出現(xiàn)該工廠方法無法創(chuàng)建由應(yīng)用類加載器加載的應(yīng)用實例

三、線程上下文類加載器

線程上下文類加載器就是用來解決類的雙親委托模型的缺陷

在Java中,官方為我們提供了很多SPI接口,例如JDBC、JBI、JNDI等。這類SPI接口,官方往往只會定義規(guī)范,具體的實現(xiàn)則是由第三方來完成的,比如JDBC,不同的數(shù)據(jù)庫廠商都需自己根據(jù)JDBC接口的定義進行實現(xiàn)。

而這些SPI接口直接由Java核心庫來提供,一般位于rt.jar包中,而第三方實現(xiàn)的具體代碼庫則一般被放在classpath的路徑下。而此時問題來了:

位于rt.jar包中的SPI接口,是由Bootstrap類加載器完成加載的,而classpath路徑下的SPI實現(xiàn)類,則是App類加載器進行加載的。

但往往在SPI接口中,會經(jīng)常調(diào)用實現(xiàn)者的代碼,所以一般會需要先去加載自己的實現(xiàn)類,但實現(xiàn)類并不在Bootstrap類加載器的加載范圍內(nèi),經(jīng)過前面的雙親委派機制的分析,我們已經(jīng)得知:子類加載器可以將類加載請求委托給父類加載器進行加載,但這個過程是不可逆的。也就是父類加載器是不能將類加載請求委派給自己的子類加載器進行加載的,

此時就出現(xiàn)了這個問題:如何加載SPI接口的實現(xiàn)類?那就是打破雙親委派模型

SPI(Service Provider Interface):Java的SPI機制,其實就是可拔插機制。在一個系統(tǒng)中,往往會被分為不同的模塊,比如日志模塊、JDBC模塊等,而每個模塊一般都存在多種實現(xiàn)方案,如果在Java的核心庫中,直接以硬編碼的方式寫死實現(xiàn)邏輯,那么如果要更換另一種實現(xiàn)方案,就需要修改核心庫代碼,這就違反了可拔插機制的原則。為了避免這樣的問題出現(xiàn),就需要一種動態(tài)的服務(wù)發(fā)現(xiàn)機制,可以在程序啟動過程中,動態(tài)的檢測實現(xiàn)者。而SPI中就提供了這么一種機制,專門為某個接口尋找服務(wù)實現(xiàn)的機制。如下:

當(dāng)?shù)谌綄崿F(xiàn)者提供了服務(wù)接口的一種實現(xiàn)之后,在jar包的 META-INF/services/ 目錄里同時創(chuàng)建一個以服務(wù)接口命名的文件,該文件就是實現(xiàn)該服務(wù)接口的實現(xiàn)類。而當(dāng)外部程序裝配這個模塊的時候,就能通過該jar包 META-INF/services/ 里的配置文件找到具體的實現(xiàn)類名,并裝載實例化,完成模塊的注入。

基于這樣一個約定就能很好的找到服務(wù)接口的實現(xiàn)類,而不需要在代碼里制定。

同時,JDK官方也提供了一個查找服務(wù)實現(xiàn)者的工具類:java.util.ServiceLoader

線程上下文類加載器就是雙親委派模型的破壞者,可以在執(zhí)行線程中打破雙親委派機制的加載鏈關(guān)系,從而使得程序可以逆向使用類加載器

1.線程上下文類加載器(Context Classloader)

線程上下文類加載器(Context Classloader)是從JDK1.2開始引入的,類Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分別用來獲取和設(shè)置上線文類加載器

如果沒有通過setContextClassLoader(ClassLoader cl)進行設(shè)置的話,線程將繼承其父線程的上下文類加載器。

Java應(yīng)用運行時的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運行的代碼可以通過該類加載器來加載類與資源

它可以打破雙親委托機制,父ClassLoader可以使用當(dāng)前線程的Thread.currentThread().getContextClassLoader()所指定的classLoader來加載類,這就可以改變父ClassLoader不能使用子ClassLoader或是其他沒有直接父子關(guān)系的ClassLoader加載的類的情況,即改變了雙親委托模型

對于SPI來說,有些接口是Java核心庫所提供的,而Java核心庫是由啟動類加載器加載的,而這些接口的實現(xiàn)卻是來自于不同jar包(廠商提供),Java的啟動類加載是不會加載其他來源的jar包,這樣傳統(tǒng)的雙親委托模型就無法滿足SPI的要求。而通過給當(dāng)前線程設(shè)置上下文類加載器,就可以由設(shè)置的上線文類加載器來實現(xiàn)與接口實現(xiàn)類的加載

Java提供了很多核心接口的定義,這些接口被稱為SPI接口,同時為了方便加載第三方的實現(xiàn)類,SPI提供了一種動態(tài)的服務(wù)發(fā)現(xiàn)機制(約定),只要第三方在編寫實現(xiàn)類時,在工程內(nèi)新建一個META-INF/services/目錄并在該目錄下創(chuàng)建一個與服務(wù)接口名稱同名的文件,那么在程序啟動的時候,就會根據(jù)約定去找到所有符合規(guī)范的實現(xiàn)類,然后交給線程上下文類加載器進行加載處理

MySQL的Driver驅(qū)動類

在使用 JDBC 時,都需要加載 Driver 驅(qū)動,不寫

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

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

也可以讓 com.mysql.jdbc.Driver 正確加載

在MySQL6.0之后的jar包中,遺棄了之前的com.mysql.jdbc.Driver驅(qū)動,而是使用com.mysql.cj.jdbc.Driver取而代之,因為后者不需要再自己通過Class.forName("com.mysql.jdbc.Driver")這種方式手動注冊驅(qū)動,全部都可以交由給SPI機制處理

在使用 JDBC,MySQL的com.mysql.cj.jdbc.Driver的驅(qū)動類,主要就是用了Java中SPI定義的一個核心類:DriverManager,該類位于rt.jar包中,是Java中用于管理不同數(shù)據(jù)庫廠商實現(xiàn)的驅(qū)動,同時這些各廠商實現(xiàn)的Driver驅(qū)動類,都繼承自Java的核心類java.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 {
    // 注冊驅(qū)動的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驅(qū)動
    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 機制加載驅(qū)動,即 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 定義的驅(qū)動名加載驅(qū)動
        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() 就是應(yīng)用程序類加載器
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

從DriverManager中的loadInitialDrivers我們可以得知,我們即使不使用Class.forName(“com.mysql.cj.jdbc.Driver”),mysql的驅(qū)動也能被加載,這是因為后期jdk使用了ServiceLoader

2 是使用 Class.forName 完成類的加載和初始化,關(guān)聯(lián)的是應(yīng)用程序類加載器,因此 可以順利完成類加載

1 就是 Service Provider Interface (SPI) 約定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名為文件,文件內(nèi)容是實現(xiàn)類名稱

2.ServiceLoader

ServiceLoader 是一個簡單的加載服務(wù)提供者的機制。通常服務(wù)提供者會實現(xiàn)服務(wù)當(dāng)中所定義的接口。服務(wù)提供者可以以一種擴展的jar包的形式安裝到j(luò)ava平臺上擴展目錄中,也可以添加到應(yīng)用的classpath中。

1、服務(wù)提供者需要提供一個無參數(shù)的構(gòu)造方法

2、服務(wù)提供者是通過在META-INF/services目錄下相應(yīng)的提供者配置文件,該配置文件的文件名由服務(wù)接口的包名組成。

3、提供者配置文件里面就是實現(xiàn)這個服務(wù)接口的類路徑,每個服務(wù)提供者占一行。

4、ServiceLoader是按需加載和實例化提供者的,就是懶加載,ServiceLoader其中還包含一個服務(wù)提供者緩存,里面存放著已經(jīng)加載的服務(wù)提供者。

5、ServiceLoader會返回一個iterator迭代器,會返回所有已經(jīng)加載了的服務(wù)提供者

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("當(dāng)前線程上線文類加載器:"+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);
}

線程上下文類加載器是當(dāng)前線程使用的類加載器,默認(rèn)就是應(yīng)用程序類加載器,它內(nèi)部又是由 Class.forName 調(diào)用了線程上下文類加載器完成類加載,具體代碼在 ServiceLoader 的內(nèi)部類 LazyIterator 中

        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中線程上下文類加載器的講解

四、自定義類加載器

1、自定義類加載器場景

1、加載非 classpath 隨意路徑中的類文件

2、通過接口來使用實現(xiàn),希望解耦時(常用在框架設(shè)計)

3、不同應(yīng)用的同名類都可以加載,不沖突,常見于 tomcat 容器

2、自定義類加載器步驟

1、繼承 ClassLoader 父類

2、遵從雙親委派機制,重寫 findClass 方法

注:不是重寫 loadClass 方法,否則不會走雙親委派機制

3、讀取類文件的字節(jié)碼

4、調(diào)用父類的 defineClass 方法來加載類

5、使用者調(diào)用該類加載器的 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);
            // 得到字節(jié)數(shù)組
            byte[] bytes = os.toByteArray();
            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("類文件未找到", e);
        }
    }
}

到此這篇關(guān)于Java類加載器與雙親委派機制和線程上下文類加載器專項解讀分析的文章就介紹到這了,更多相關(guān)Java類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 29個要點幫你完成java代碼優(yōu)化

    29個要點幫你完成java代碼優(yōu)化

    本文給大家分享的是個人總結(jié)的29個java優(yōu)化需要注意的地方,非常的全面細(xì)致,推薦給大家,有需要的小伙伴可以參考下
    2015-03-03
  • java中Hibernate緩存形式總結(jié)

    java中Hibernate緩存形式總結(jié)

    在本篇文章里小編給大家整理的是一篇關(guān)于java中Hibernate緩存形式總結(jié)內(nèi)容,有興趣的朋友們可以參考下。
    2021-01-01
  • java正則表達式簡單應(yīng)用

    java正則表達式簡單應(yīng)用

    這篇文章主要介紹了java正則表達式簡單應(yīng)用,在之前幾篇文章中已經(jīng)深入學(xué)習(xí)了java正則表達式基礎(chǔ)知識,本文對java正則表達式應(yīng)用進行研究,感興趣的小伙伴們可以參考一下
    2015-12-12
  • Java excel數(shù)據(jù)導(dǎo)入mysql的實現(xiàn)示例詳解

    Java excel數(shù)據(jù)導(dǎo)入mysql的實現(xiàn)示例詳解

    今天教大家如何使用Java將excel數(shù)據(jù)導(dǎo)入MySQL,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴呢很有幫助,需要的朋友可以參考下
    2022-08-08
  • Java?任務(wù)調(diào)度框架?Quartz實操

    Java?任務(wù)調(diào)度框架?Quartz實操

    這篇文章主要介紹了Java?任務(wù)調(diào)度框架?Quartz,Quartz是OpenSymphony開源組織在Job?scheduling領(lǐng)域又一個開源項目,完全由Java開發(fā),可以用來執(zhí)行定時任務(wù),類似于java.util.Timer。,下面我們來學(xué)習(xí)一下關(guān)于?Quartz更多的詳細(xì)內(nèi)容,需要的朋友可以參考一下
    2021-12-12
  • Spring如何正確注入集合類型

    Spring如何正確注入集合類型

    這篇文章主要介紹了Spring如何正確注入集合類型,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • Spring中配置和讀取多個Properties文件的方式方法

    Spring中配置和讀取多個Properties文件的方式方法

    本篇文章主要介紹了Spring中配置和讀取多個Properties文件的方式方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • Spring Boot集成Druid出現(xiàn)異常報錯的原因及解決

    Spring Boot集成Druid出現(xiàn)異常報錯的原因及解決

    Druid 可以很好的監(jiān)控 DB 池連接和 SQL 的執(zhí)行情況,天生就是針對監(jiān)控而生的 DB 連接池。本文講述了Spring Boot集成Druid項目中discard long time none received connection異常的解決方法,出現(xiàn)此問題的同學(xué)可以參考下
    2021-05-05
  • kafka內(nèi)外網(wǎng)訪問配置方式

    kafka內(nèi)外網(wǎng)訪問配置方式

    這篇文章主要介紹了kafka內(nèi)外網(wǎng)訪問配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java使用Apache POI操作Excel詳解

    Java使用Apache POI操作Excel詳解

    在Java中操作Excel是日常工作中經(jīng)常遇到的問題,而Apache Poi是一種流行且廣泛使用的方式,它提供了各種庫和工具,所以本文就來詳細(xì)如何使用Apache Poi來進行Excel文件操作吧
    2023-06-06

最新評論