java 類加載器,SPI機制,spring factories的原理

一、java類加載器原理:

1、java自帶的類加載器有三種:bootstrap classLoader,extension classLoader,App classLoader。

bootstrap ClassLoader: 啓動類加載器,負責加載系統的核心類庫,如lib下的jar包。

extension ClassLoader:擴展類加載器,主要負責加載lib下的ext擴展類。

app ClassLoader:應用類加載器,主要負責加載應用的classpath目錄下的class文件。

2、雙親委派加載的機制:系統啓動後,應用類加載器加載應用類,發起雙親委派機制,讓上級擴展類加載器去加載,擴展類加載器讓上級的啓動類加載器加載,當啓動類加載器加載不到,再有擴展類加載器加載,若擴展類加載器也加載不了,則讓應用類加載器加載。

當加載某一個class類時,這個class類中有調用的其他類也應一次加載,對於應用的這些類也通過上面雙親委派機制加載。

3、類加載器具有傳遞性,當開啓子線程時,會使用主線程的類加載器加載類文件。所有加載的類都存於JVM中,jvm中每一個class對象都綁定有對應的類加載器,所以在jvm中一樣的class全限名的class對象不一定相等,因爲類加載器不一樣。(爲什麼在系統中自定義一個java.lang.Integer,加載會報錯,因爲加載類時,雙親委派機制以及使用了啓動類加載器加載了integer到jvm中,所以自定義的Integer在加載會報錯,如果這個自定義的integer沒有在classPath裏,在其他地方,應用類加載器都沒有加載到,使用自定義的類加載器,通過字節碼的方式加載這個文件,這裏就需要分爲兩種情況,第一種你自定義的類加載器中如果沒有使用雙親委派機制,則可以加載,如果使用了雙親委派機制,則返回的是系統類加載器加載的class)

4、打破雙親委派:

   (1)自定義類加載器,自定義一個類,實現ClassLoader接口。覆蓋loadClass方法。通過文件流的方式獲取文件字節碼,再通過父級的 defineClass生成Class對象。就沒有走父級類加載器去加載。
   如果不需要打破雙親委派,則覆蓋findClass即可。

   (2)SPI機制,第三方jar包通過ServiceLoader.load進行加載,首先通過雙親委派機制,bootstrap類加載器加載到了ServiceLoader類,然後在Spring boot中通過ServiceLoader去加載第三方jar中的類時,由於類加載器的傳遞性,只能通過bootStrap去加載第三方jar,然後頂級類加載器肯定加載不了這個jar,而且當前的起始點已經是頂級,不能再往下傳遞,所以就只能通過當前線程池中的類加載器去加載第三方jar包的類(如果沒有設置,則使用父級線程的類加載器),這裏就打破了雙親委派。

 (3)使用OSGI熱部署,對jar包進行更新,卸載打破雙親委派。

 

二、java的SPI機制打破雙親委派進行加載類。

  1、Java的SPI實現原理:使用ServiceLoader.load(接口.class);  獲取這些實現類對象。ServiceLoader的實現原理是:

第三方jar包下的META-INFO/services目錄下創建一個以接口全限名爲命名文件,內容爲這個接口的實現類,實現類放於該jar包中。在啓動的系統中掃描所有jar下的META-INFO/services目錄,提取裏面的實現類。然後創建自定義類加載器加載(覆寫findClass方法,用流的方式加載這個類文件)。使用 Class.forName(實現類全限名, false, classLoader); 加載這個實現類到jvm中。然後再進行實例化。所以實現類必須有無參構造方法。

2、使用SPI的條件:
      a、第三方jar裏需要有指定接口的實現類。並在第三方打的jar包裏有META-INFO/services目錄下創建一個以接口全限名命名的文件,文件裏的內容是這個接口的實現類的全限名。
      b、這個第三方jar包需要放入到classPath目錄裏。
      c、主程序通過java.util.ServiceLoder動態裝載實現模塊,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;
       d、實現類必須有無參構造方法。

3、應用場景:解耦,可插拔。

     a、數據庫驅動加載接口實現類的加載 :JDBC加載不同類型數據庫的驅動

系統通過加載java自身的DirverManager類,並加載裏面的static靜態塊的init方法,在通過SPI機制,ServiceLoader.load(Dirver.class)去加載所有jar中有配置META-INFO/service下java.sql.Driver文件裏面的類。
這裏如果引入了mysql驅動包,就會把mysql的META-INFO/service/java.sql.Driver裏的mysql的Driver類加載起來,在這個mysql的Driver裏會執行一個靜態塊裏的DriverManager.registerDriver(new com.mysql.jdbc.Driver());這樣就將mysql的驅動注入到java的driver集合中。程序裏面通過DriverManager獲取getConnection(url)時,就會遍歷driver集合driver.connect獲取連接,哪一個能獲取到就返回哪個connection連接。


     b、日誌門面接口實現類加載 : SLF4J加載不同提供商的日誌實現類

三、spring boot的SPI機制,即spring factories 實現原理:

    在spring-core包裏定義了SpringFactoryLoader類,這個類會檢索所有jar包下的META-INFO/factories文件。然後在程序中讀取這些配置文件加載到jvm中,如有spring的一些註解,則進行解析。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章