從Dubbo內核-SPI聊聊雙親委派機制

前言

談到Dubbo老是避不開SPI思想,由於這個是Dubbo內核中很是重要的一部分,可是SPI是個很大的話題,本篇和以前的dubbo源碼解析-簡單原理、與spring融合同樣,爲Dubbo源碼解析專題的知識預熱篇.咱們公司實際項目就用到了Dubbo的SPI.後面會給你們分享,咱們實際項目中,是如何使用SPI,以及SPI後續咱們又是如何進優化的.java

插播面試題

  • 你是否瞭解spi,講一講什麼是spi,爲何要使用spi?面試

  • 對類加載機制瞭解嗎,說一下什麼是雙親委託模式,他有什麼弊端,這個弊端有沒有什麼咱們熟悉的案例,解決這個弊端的原理又是怎麼樣的?spring

spi的簡單介紹

若是提到api相信你們都知道,spi的話,知道的人就相對少一些.數據庫

簡單的說,api是給使用者使用的,spi是給拓展者使用的.一個好的開源框架,必需要留一些拓展點.讓參與者儘可能黑盒拓展,而不是白盒修改代碼,不然分支,質量,合併,衝突都會很難管理.而且框架做者能作到的功能,拓展者也必定能作到.編程

若是從使用層面來講,就是運行時,動態給接口添加實現類.其實這有有點像IoC的思想,將裝配的控制權移到程序以外api

若是從生活中的例子講,就是好比瀏覽器插件,好比牆上的插頭不夠咱們就接個排插,而不是傷筋動骨改插頭(感受不是很貼切,前期你暫且這麼不規範的粗略理解)瀏覽器

再多的言語都是抽象的,那麼咱們用代碼來簡單實現一下spi併發

spi的簡單實現

接口和具體實現類框架

public interface ISayName {
    void say();
}
複製代碼
public class SayEnglishName implements ISayName{
    @Override
    public void say() {
        System.out.println("Toby");
    }
}
複製代碼
public class SayChineseName implements ISayName{
    @Override
    public void say() {
        System.out.println("肥朝");
    }
}
複製代碼

配置文件,需放置在META-INF/services/接口全限定名ide

com.toby.spi.impl.SayChineseName
com.toby.spi.impl.SayEnglishName
複製代碼

demo目錄結構

測試結果以下

經過改變配置文件,咱們就能動態的改變一個接口的實現類.

細心的小夥伴可能發現,好比我想新增一個實現類SayFranceNameImpl,這樣的話光改配置文件也仍是不行,還要預先包裏面就有這個實現類才行啊.

這個先別急,後面會介紹javassist,也就是動態字節碼技術.這樣能夠在運行時動態生成Java類,就不存在要預先把接口的實現類先在包裏放好.更多內容,關注肥朝便可.

固然細心的小夥伴可能還發現了,這個我就算不用spi,我用spring的ioc也能經過配置文件動態的注入不一樣的實現類啊

好比dubbo的設計中,就不想強依賴Spring的IoC容器,可是自已造一個小的IoC容器,也以爲有點過分設計.另外dubbo是不須要依賴任何第三方庫的,引用官方文檔原話以下

理論上 Dubbo 能夠只依賴 JDK,不依賴於任何三方庫運行,只需配置使用 JDK 相關實現策略

敲黑板劃重點

常常看到有人問兩類問題

  • java人這麼多,是否飽和了?
  • 爲何老是要面試造火箭,進去擰螺絲?

你能夠問一下你同事,你知道什麼是spi嗎,若是他不知道的話,那你以爲他把上面的這個簡單的例子實現要多久?若是從使用這個層面作區分的話,很難作到有效的區分.不管是作什麼,要想在競爭中脫穎而出,就必須作到三個字.差別化.

Java基礎中比較容易產生差別化的兩個區域就在於JVM併發編程.若是隻是停留在使用層面,那麼關注肥朝的博客意義並不大,所以,本篇的spi還須要與ClassLoader結合.

學習JVM併發編程買本書是必不可少的,如下內容參考了實戰Java虛擬機.若是你看的是深刻Java虛擬機也不要緊,不要糾結於獲取知識的渠道,沒人在乎你作的是五年高考三年模擬仍是王后雄學案. 如下內容截取了該書中的部分核心內容,很是感謝做者的辛勤奉獻(但願你們支持正版書籍).

從ClassLoader引出spi

ClassLoader的簡單介紹

Class裝載大致上能夠分爲加載類鏈接類初始化三個階段,在這三個階段中,全部的Class都是由ClassLoader進行加載的,而後Java虛擬機負責鏈接、初始化等操做.也就是說,沒法經過ClassLoader去改變類的鏈接和初始化行爲.

Java虛擬機會建立三類ClassLoader,分別是

  • BootStrap ClassLoader(啓動類加載器)
  • Extension ClassLoader(擴展類加載器)
  • APP ClassLoader(應用類加載器,也稱爲系統類加載器)

此外,每一個應用還能夠自定義ClassLoader

ClassLoader的雙親委託模式

ClassLoader的結構中,還有一個重要的字段parent,它也是一個ClassLoader的實例,這個字段字段表示的ClassLoader也成爲這個ClassLoader的雙親.在類加載的過程當中,可能會將某些請求交於本身的雙親處理.

如圖,應用類加載器的雙親爲擴展類加載器,擴展類加載器的雙親爲啓動類加載器.

系統中的ClassLoader在協同工做時,默認會使用雙親委託模式.即在類加載的時候,系統會判斷當前類是否已經被加載,若是被加載,就會直接返回可用的類,不然就會嘗試加載,在嘗試加載時,會先請求雙親處理,若是雙親請求失敗,則會本身加載.

雙親委託模式的弊端

判斷類是否加載的時候,應用類加載器會順着雙親路徑往上判斷,直到啓動類加載器.可是啓動類加載器不會往下詢問,這個委託路線是單向的,即頂層的類加載器,沒法訪問底層的類加載器所加載的類,如圖

啓動類加載器中的類爲系統的核心類,好比,在系統類中,提供了一個接口,而且該接口還提供了一個工廠方法用於建立該接口的實例,可是該接口的實現類在應用層中,接口和工廠方法在啓動類加載器中,就會出現工廠方法沒法建立由應用類加載器加載的應用實例問題.

擁有這樣問題的組件有不少,好比JDBCXml parser等.JDBC自己是java鏈接數據庫的一個標準,是進行數據庫鏈接的抽象層,由java編寫的一組類和接口組成,接口的實現由各個數據庫廠商來完成

雙親委託模式的補充

在Java中,把核心類(rt.jar)中提供外部服務,可由應用層自行實現的接口,這種方式成爲spi.那咱們看一下,在啓動類加載器中,訪問由應用類加載器實現spi接口的原理

Thread類中有兩個方法

public ClassLoader getContextClassLoader()//獲取線程中的上下文加載器 public void setContextClassLoader(ClassLoader cl)//設置線程中的上下文加載器 複製代碼

經過這兩個方法,能夠把一個ClassLoader置於一個線程的實例之中,使該ClassLoader成爲一個相對共享的實例.這樣即便是啓動類加載器中的代碼也能夠經過這種方式訪問應用類加載器中的類了.以下圖

寫在最後

肥朝 是一個專一於 原理、源碼、開發技巧的技術公衆號,號內原創專題式源碼解析、真實場景源碼原理實戰(重點)。掃描下面二維碼關注肥朝,讓本該造火箭的你,再也不擰螺絲!

相關文章
相關標籤/搜索