Java虛擬機會建立三類ClassLoader,分別以下html
名稱 | 加載 | 加載路徑 | 父加載器 | 實現 |
---|---|---|---|---|
BootStrap | 虛擬機的核心類庫 | sun.boot.class.path | 無 | 系統 |
Extension | 擴展類庫 | java.ext.dirs、jre/lib/ext | BootStrap | Java |
System | 應用類庫 | classpath、java.class.path | Extension | Java |
注:父子加載器並不是繼承關係,也就是說子加載器不必定是繼承了父加載器
java
其實我以爲把「雙親委託模式」稱爲「父加載委託模式」更好理解,「雙」字把我給弄混了。mysql
「雙親委託模式」指的就是某個特定的類加載器在接到加載類的請求時,
首先將加載任務委託給父類加載器
,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,本身才去加載。android
下面是一段ClassLoader的源碼,很容易能夠看出上述規則:sql
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 首先檢查該name指定的class是否有被加載 Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //若是parent不爲null,則調用parent的loadClass進行加載 c = parent.loadClass(name, false); }else{ //parent爲null,則調用BootstrapClassLoader進行加載 c = findBootstrapClass0(name); } }catch(ClassNotFoundException e) { //若是仍然沒法加載成功,則調用自身的findClass進行加載 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
委託永遠是子加載器去請求父加載器,是單向的,即上層的類加載器沒法訪問下層的類加載器所加載的類
:數據庫
舉個例子,假設「BootStrap」中提供了一個接口,及一個建立其實例的工廠方法,可是該接口的實現類在「System」中,那麼就會出現工廠方法沒法建立在「System」加載的類的實例的問題。擁有這樣問題的組件有不少,好比JDBC、Xml parser等。安全
如今引入一個新的名詞「SPI」。框架
「SPI」 全稱爲 (Service Provider Interface) ,是JDK內置的一種
服務提供發現機制
。 目前有很多框架用它來作服務的擴展發現, 簡單來講,它就是一種動態替換髮現的機制。ide
JDBC自己是Java鏈接數據庫的一個標準,是進行數據庫鏈接的抽象層,由Java編寫的一組類和接口組成,接口的實現由各個數據庫廠商來完成,不一樣廠商能夠針對同一接口作出不一樣的實現,MySQL和PostgreSQL都有不一樣的實現提供給用戶,而Java的「SPI」機制能夠爲某個接口尋找服務實現。函數
下面以JDBC爲例,介紹「SPI」機制。
在JDBC4.0以前,咱們開發有鏈接數據庫的時候,一般會用Class.forName("com.mysql.jdbc.Driver")這句先加載數據庫相關的驅動,而後再進行獲取鏈接等的操做。而JDBC4.0以後不須要用Class.forName("com.mysql.jdbc.Driver")來加載驅動,直接獲取鏈接就能夠了,如今這種方式就是使用了Java的「SPI」擴展機制來實現。
JDBC在java.sql.Driver
只定義了接口。
這裏以MySQL爲例,在mysql-connector-java-6.0.6.jar
包裏的META-INF/services
目錄下能夠找到一個java.sql.Driver
文件,文件內容是一個類名,這個名叫com.mysql.cj.jdbc.Driver
的類就是MySQL針對JDBC中定義的接口的實現。
在咱們的應用裏面,咱們就能夠直接鏈接MySQL了。
Connection conn = DriverManager.getConnection(url,username,password);
顯然語句並無加載實現類,這裏就涉及到使用「SPI」擴展機制來查找相關驅動了,接下來,咱們結合源碼探究一下這是如何實現的。
關於驅動的查找其實都在DriverManager中,DriverManager位於java.sql
包裏,用來獲取數據庫鏈接,在DriverManager中有一個靜態代碼塊以下:
loadInitialDrivers
方法用於實例化驅動,由3部分構成:
兩個比較關鍵的地方是ServiceLoader.load
, 還有loadedDrivers.iterator
,下面結合源碼介紹一下:
ServiceLoader
封裝了一個自定義加載器loader
,還應留意一下下面2個成員,以後會用到:
PREFIX
lookupIterator
ServiceLoader.load(Driver.class)
最後會調用構造函數,返回ServiceLoader
實例
每個線程都有本身的ContextClassLoader,默認以
SystemClassLoader
爲ContextClassLoader。經過Thread.currentThread().getContextClassLoader()
,能夠把一個ClassLoader置於一個線程的實例之中,使該ClassLoader成爲一個相對共享的實例,這樣即便是啓動類加載器中的代碼也能夠經過這種方式訪問應用類加載器中的類了。

多個加載器經過上下文加載器共享
loadedDrivers.iterator
方法返回一個迭代器,這個迭代器是「SPI」機制加載實現類的關鍵,迭代器在iterator()
方法內定義:
「SPI」加載代碼的是這樣的:
執行driversIterator.hasNext
時,會調用lookupIterator.hasNext
去找的實現類的名字。
接着會調用lookupIterator.next()
去加載這個類:
至此,已經將實現類成功加載。
如今就能夠根據第1步獲取到的驅動列表來加載實現類了:
「SPI」經過循環加載實現類,顯而易見,它會把全部的類一同加載,不管有沒有用到,這形成了必定的資源浪費:
android classloader雙親委託模式
dubbo源碼解析-spi(一)
Java中SPI機制深刻及源碼解析
走出ClassLoader的迷宮