1. 程式人生 > >Sring系列之SpringFactoriesLoader詳解

Sring系列之SpringFactoriesLoader詳解

        JVM提供了3種類載入器: BootstrapClassLoader、 ExtClassLoader、 AppClassLoader分別載入Java核心類庫、擴充套件類庫以及應用的類路徑( CLASSPATH)下的類庫。JVM通過雙親委派模型進行類的載入,我們也可以通過繼承 java.lang.classloader實現自己的類載入器。

        何為雙親委派模型?當一個類載入器收到類載入任務時,會先交給自己的父載入器去完成,因此最終載入任務都會傳遞到最頂層的BootstrapClassLoader,只有當父載入器無法完成載入任務時,才會嘗試自己來載入。

       採用雙親委派模型的一個好處是保證使用不同類載入器最終得到的都是同一個物件,這樣就可以保證Java 核心庫的型別安全,比如,載入位於rt.jar包中的 java.lang.Object類,不管是哪個載入器載入這個類,最終都是委託給頂層的BootstrapClassLoader來載入的,這樣就可以保證任何的類載入器最終得到的都是同樣一個Object物件。檢視ClassLoader的原始碼,對雙親委派模型會有更直觀的認識:

       但雙親委派模型並不能解決所有的類載入器問題,比如,Java 提供了很多服務提供者介面( ServiceProviderInterface,SPI),允許第三方為這些介面提供實現。常見的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的介面由核心類庫提供,卻由第三方實現,這樣就存在一個問題:SPI 的介面是 Java 核心庫的一部分,是由BootstrapClassLoader載入的;SPI實現的Java類一般是由AppClassLoader來載入的。BootstrapClassLoader是無法找到 SPI 的實現類的,因為它只加載Java的核心庫。它也不能代理給AppClassLoader,因為它是最頂層的類載入器。也就是說,雙親委派模型並不能解決這個問題。

        執行緒上下文類載入器( ContextClassLoader)正好解決了這個問題。從名稱上看,可能會誤解為它是一種新的類載入器,實際上,它僅僅是Thread類的一個變數而已,可以通過 setContextClassLoader(ClassLoadercl)和 getContextClassLoader()來設定和獲取該物件。如果不做任何的設定,Java應用的執行緒的上下文類載入器預設就是AppClassLoader。在核心類庫使用SPI介面時,傳遞的類載入器使用執行緒上下文類載入器,就可以成功的載入到SPI實現的類。執行緒上下文類載入器在很多SPI的實現中都會用到。但在JDBC中,你可能會看到一種更直接的實現方式,比如,JDBC驅動管理 java.sql.Driver中的 loadInitialDrivers()方法中,你可以直接看到JDK是如何載入驅動的:

        其實講解執行緒上下文類載入器,最主要是讓大家在看到 Thread.currentThread().getClassLoader()和 Thread.currentThread().getContextClassLoader()時不會一臉懵逼,這兩者除了在許多底層框架中取得的ClassLoader可能會有所不同外,其他大多數業務場景下都是一樣的,大家只要知道它是為了解決什麼問題而存在的即可。

        類載入器除了載入class外,還有一個非常重要功能,就是載入資源,它可以從jar包中讀取任何資原始檔,比如, ClassLoader.getResources(Stringname)方法就是用於讀取jar包中的資原始檔,其程式碼如下:

        是不是覺得有點眼熟,不錯,它的邏輯其實跟類載入的邏輯是一樣的,首先判斷父類載入器是否為空,不為空則委託父類載入器執行資源查詢任務,直到BootstrapClassLoader,最後才輪到自己查詢。而不同的類載入器負責掃描不同路徑下的jar包,就如同載入class一樣,最後會掃描所有的jar包,找到符合條件的資原始檔。

        類載入器的 findResources(name)方法會遍歷其負責載入的所有jar包,找到jar包中名稱為name的資原始檔,這裡的資源可以是任何檔案,甚至是.class檔案,比如下面的示例,用於查詢Array.class檔案:

執行後可以得到如下結果:

根據資原始檔的URL,可以構造相應的檔案來讀取資源內容。

         看到這裡,你可能會感到挺奇怪的,你不是要詳解 SpringFactoriesLoader嗎?上來講了一堆ClassLoader是幾個意思?看下它的原始碼你就知道了:

        有了前面關於ClassLoader的知識,再來理解這段程式碼,是不是感覺豁然開朗:從 CLASSPATH下的每個Jar包中搜尋所有 META-INF/spring.factories配置檔案,然後將解析properties檔案,找到指定名稱的配置後返回。需要注意的是,其實這裡不僅僅是會去ClassPath路徑下查詢,會掃描所有路徑下的Jar包,只不過這個檔案只會在Classpath下的jar包中。來簡單看下 spring.factories檔案的內容吧:

        執行 loadFactoryNames(EnableAutoConfiguration.class,classLoader)後,得到對應的一組 @Configuration類,我們就可以通過反射例項化這些類然後注入到IOC容器中,最後容器裡就有了一系列標註了 @Configuration的JavaConfig形式的配置類。

        這就是 SpringFactoriesLoader,它本質上屬於Spring框架私有的一種擴充套件方案,類似於SPI,Spring Boot在Spring基礎上的很多核心功能都是基於此,希望大家可以理解。

來自: