JVM-執行緒上下文類載入器

debug-LiXiwen發表於2020-09-25

SPI是什麼

Java提供了很多SPI,允許第三方為這些介面提供實現,最常見的SPI實現有JDBC、JNDI等等,根據類載入器的雙親委派模型,載入ServiceLoader的 BootstrapClassLoader 是不能載入SPI的實現類的,因為SPI的實現類是由 AppClassLoader 載入的,而 BootstrapClassLoader 是不能委派 AppClassLoader 來載入類的,那該怎麼辦呢?

SPI約定為:當服務的提供者提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。
SPI就是

在resources目錄下,新建META-INF/services目錄,然後新建檔案(檔名=介面包名.介面名)。內容就是實現類的包名.類名
image.png
image.png

SPI的介面是Java核心庫的一部分,按照雙親委派模式和類載入器搜尋路徑而言:它是由啟動類載入器來載入的;但是SPI的實現類在我們應用引入之後在應用Classpath下,BootstrapClassLoader不認識它載入不了,只能由系統類載入器來載入的。原因在於啟動類載入器是無法找到 SPI 的實現類的(因為它只載入 Java 的核心庫),按照雙親委派模型,啟動類載入器又無法委派系統類載入器去載入類。也就是說,類載入器的雙親委派模式無法解決這個問題
這時候執行緒上下文類載入器排上了用場。執行緒上下文類載入器破壞了“雙親委派模型”,可以在執行執行緒中拋棄雙親委派載入鏈模式,使程式可以逆向使用類載入器。

執行緒上下文類載入器(TCCL)

可使用ServiceLoader的load方法獲取到TCCL

private static final String PREFIX = "META-INF/services/";

public static <S> ServiceLoader<S> load(Class<S> service) {    
    // 獲取當前呼叫執行緒的類載入器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();    
    return ServiceLoader.load(service, cl);
}

Java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設定執行緒的上下文類載入器。如果沒有通過setContextClassLoader(ClassLoader cl)方法進行設定的話,執行緒將繼承其父執行緒的上下文類載入器。Java 應用執行的執行緒上下文類載入器初始是AppClassLoader,線上程中執行的程式碼可以通過此類載入器來載入類和資源。

ServiceLoader<Lxw> loader = ServiceLoader.load(Lxw.class);
Iterator<Lxw> iterator = loader.iterator();
while (iterator.hasNext()) {
    Lxw l = iterator.next();
    l.say();
}

應用程式啟動會走這個獲取到系統類載入器,是應用程式類載入器,然後執行第一行進入load放方法
在這裡插入圖片描述

獲取到執行緒上下文類載入器
,然後就是load了,new了一個serviceLoader
在這裡插入圖片描述

可以看到new了一個類出來
在這裡插入圖片描述

在這裡插入圖片描述

總結

  • 直白一點說就是,我(JDK)提供了一種幫你(第三方實現者)載入服務(如資料庫驅動、日誌庫)的便捷方式,只要你遵循約定(把類名寫在/META-INF裡),那當我啟動時我會去掃描所有jar包裡符合約定的類名,再呼叫forName載入,但我的ClassLoader是沒法載入的,那就把它載入到當前執行執行緒的TCCL裡,後續你想怎麼操作(驅動實現類的static程式碼塊)就是你的事了。

  • 執行緒上下文類載入器(它並不是一個真正的類載入器,而是通過當前執行緒拿到我們想要的類載入器->應用執行時它被放在了執行緒中,所以不管當前程式處於何處BootstrapClassLoader或ExtClassLoader等,在任何需要的時候都可以拿出去使用)。
    執行緒上下文類載入器打破了雙親委派機制,實現逆向呼叫類載入器來載入當前執行緒中類載入器載入不到的類

  • 當高層提供了統一介面讓低層去實現,同時又要是在高層載入(或例項化)低層的類時,比如上面spi的呼叫者ServiceLoader所在的BootstrapClassloader無法載入的時候,必須通過執行緒上下文類載入器來幫助高層的ClassLoader找到並載入該類。

  • 類/介面是有名稱空間之分的,不同的類載入器是不同的名稱空間。一個類是由A類載入器載入的,那麼這個類的依賴類也會由這個A類載入器載入,但是如果所依賴的類不在A類載入器載入的範圍內,那麼就會找不到這個類。可以使用執行緒上下文類載入器進行載入使用。這種操作就是破壞了雙親委派模式

參考

  • https://blog.csdn.net/yangcheng33/article/details/52631940
  • https://anthonyzero.github.io/2019/10/01/JVM-%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8/

相關文章