" />

亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java類加載器與雙親委派機(jī)制和線程上下文類加載器專項(xiàng)解讀分析

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

一、類加載器

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

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

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

基本上所有的類加載器都是java.lang.ClassLoader 類的一個(gè)實(shí)例

類加載器3大分類

類加載器加載類說明
啟動(dòng)類加載器(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類的實(shí)例
findClass(String name)查找名字為name的類,返回的結(jié)果是java.lang.Class類的實(shí)例
findLoadedClass(String name)查找名字為name的已經(jīng)被加載過的類,返回的結(jié)果是java.lang.Class類的實(shí)例
defineClass(String name,byte[] b,int off,int len)根據(jù)字節(jié)數(shù)組b中的數(shù)據(jù)轉(zhuǎn)化成Java類,返回的結(jié)果是java.lang.Class類的實(shí)例

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

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

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

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

1.啟動(dòng)類加載器

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

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

啟動(dòng)類加載器不是java原生代碼編寫的,所以其也不是java.lang.ClassLoader類的實(shí)例,其沒有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 之后

用這個(gè)辦法替換核心類

java -Xbootclasspath:<new bootclasspath>

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

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

2.拓展類加載器

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

此路徑可以通過獲取"java.ext.dirs"的系統(tǒng)屬性獲得。拓展類加載器就是java.lang.ClassLoader類的一個(gè)實(shí)例,其getParent方法返回的是引導(dǎo)類加載器(在 HotSpot虛擬機(jī)中用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()方法獲得此類加載器的實(shí)例,系統(tǒng)類加載器也因此得名。其主要負(fù)責(zé)加載程序開發(fā)者自己編寫的java類

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

4.類的命名空間

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

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

類似Test cannot be cast to Test的java.lang.ClassCastException 的奇怪錯(cuò)誤很多情況下都是類的二進(jìn)制名相同,而定義加載器不同造成的

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一個(gè)新的類加載器
        System.out.println(classLoader);
        /*
        這是因?yàn)?1處獲取的應(yīng)用類加載器a和jvm用來加載Load6.class對象的應(yīng)用類加載器b不是同一個(gè)實(shí)例,
        那么構(gòu)成這兩個(gè)類的run-time package也就是不同的。所以即使它們的二進(jìn)制名字相同,
        但是由a定義的Load6類所創(chuàng)建的對象顯然不能轉(zhuǎn)化為由b定義的Load6類的實(shí)例。
        這種情況下jvm就會(huì)拋出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
    }
}

報(bào)出異常:

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

這是因?yàn)?1處獲取的應(yīng)用類加載器a和jvm用來加載Load6.class對象的應(yīng)用類加載器b不是同一個(gè)實(shí)例, 那么構(gòu)成這兩個(gè)類的run-time package也就是不同的。

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

相同二進(jìn)制名字的類,如果其定義加載器不同,也算是不同的兩個(gè)類

二、雙親委派機(jī)制

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

1.類加載機(jī)制流程

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

2.類加載器加載順序

3.雙親委派機(jī)制流程

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

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

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

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

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

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

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

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

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

3、如果應(yīng)用類加載器也找不到那就報(bào)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 方法(每個(gè)類加載器自己擴(kuò)展)來加載
                    c = findClass(name);
                    // 5. 記錄耗時(shí)
                    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.雙親委派機(jī)制優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

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

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

缺點(diǎn):

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

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

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

三、線程上下文類加載器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MySQL的Driver驅(qū)動(dòng)類

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

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ū)動(dòng),而是使用com.mysql.cj.jdbc.Driver取而代之,因?yàn)楹笳卟恍枰僮约和ㄟ^Class.forName("com.mysql.jdbc.Driver")這種方式手動(dòng)注冊驅(qū)動(dòng),全部都可以交由給SPI機(jī)制處理

在使用 JDBC,MySQL的com.mysql.cj.jdbc.Driver的驅(qū)動(dòng)類,主要就是用了Java中SPI定義的一個(gè)核心類:DriverManager,該類位于rt.jar包中,是Java中用于管理不同數(shù)據(jù)庫廠商實(shí)現(xiàn)的驅(qū)動(dòng),同時(shí)這些各廠商實(shí)現(xiàn)的Driver驅(qū)動(dòng)類,都繼承自Java的核心類java.sql.Driver

DriverManager 的類加載器

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

打印 null,表示它的類加載器是 Bootstrap ClassLoader,會(huì)到 JAVA_HOME/jre/lib 下搜索類,但 JAVA_HOME/jre/lib 下顯然沒有 mysql-connector-java-xx.xx.xx.jar 包

public class DriverManager {
    // 注冊驅(qū)動(dòng)的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驅(qū)動(dòng)
    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 機(jī)制加載驅(qū)動(dòng),即 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ū)動(dòng)名加載驅(qū)動(dòng)
        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ū)動(dòng)也能被加載,這是因?yàn)楹笃趈dk使用了ServiceLoader

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

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

2.ServiceLoader

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

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

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

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

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

5、ServiceLoader會(huì)返回一個(gè)iterator迭代器,會(huì)返回所有已經(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、通過接口來使用實(shí)現(xiàn),希望解耦時(shí)(常用在框架設(shè)計(jì))

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

2、自定義類加載器步驟

1、繼承 ClassLoader 父類

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

注:不是重寫 loadClass 方法,否則不會(huì)走雙親委派機(jī)制

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");
        //雖然相同類名,但不是同一個(gè)類加載器加載的
        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類加載器與雙親委派機(jī)制和線程上下文類加載器專項(xiàng)解讀分析的文章就介紹到這了,更多相關(guān)Java類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 29個(gè)要點(diǎn)幫你完成java代碼優(yōu)化

    29個(gè)要點(diǎn)幫你完成java代碼優(yōu)化

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

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

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

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

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

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

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

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

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

    Spring如何正確注入集合類型

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

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

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

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

    Druid 可以很好的監(jiān)控 DB 池連接和 SQL 的執(zhí)行情況,天生就是針對監(jiān)控而生的 DB 連接池。本文講述了Spring Boot集成Druid項(xiàng)目中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)訪問配置方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java使用Apache POI操作Excel詳解

    Java使用Apache POI操作Excel詳解

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

最新評論