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

肥朝發表於2019-03-23

前言

談到Dubbo總是避不開SPI思想,因為這個是Dubbo核心中非常重要的一部分,但是SPI是個很大的話題,本篇和之前的dubbo原始碼解析-簡單原理、與spring融合一樣,為Dubbo原始碼解析專題的知識預熱篇.我們公司實際專案就用到了Dubbo的SPI.後面會給大家分享,我們實際專案中,是如何使用SPI,以及SPI後續我們又是如何進優化的.

插播面試題

  • 你是否瞭解spi,講一講什麼是spi,為什麼要使用spi?

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

spi的簡單介紹

如果提到api相信大家都知道,spi的話,知道的人就相對少一些.

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

如果從使用層面來說,就是執行時,動態給介面新增實現類.其實這有有點像IoC的思想,將裝配的控制權移到程式之外

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

再多的言語都是抽象的,那麼我們用程式碼來簡單實現一下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/介面全限定名

com.toby.spi.impl.SayChineseName
com.toby.spi.impl.SayEnglishName
複製程式碼

demo目錄結構

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

測試結果如下

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

通過改變配置檔案,我們就能動態的改變一個介面的實現類.

細心的小夥伴可能發現,比如我想新增一個實現類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的雙親.在類載入的過程中,可能會將某些請求交於自己的雙親處理.

如圖,應用類載入器的雙親為擴充套件類載入器,擴充套件類載入器的雙親為啟動類載入器.

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

系統中的ClassLoader在協同工作時,預設會使用雙親委託模式.即在類載入的時候,系統會判斷當前類是否已經被載入,如果被載入,就會直接返回可用的類,否則就會嘗試載入,在嘗試載入時,會先請求雙親處理,如果雙親請求失敗,則會自己載入.

雙親委託模式的弊端

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

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

啟動類載入器中的類為系統的核心類,比如,在系統類中,提供了一個介面,並且該介面還提供了一個工廠方法用於建立該介面的例項,但是該介面的實現類在應用層中,介面和工廠方法在啟動類載入器中,就會出現工廠方法無法建立由應用類載入器載入的應用例項問題.

擁有這樣問題的元件有很多,比如JDBCXml parser等.JDBC本身是java連線資料庫的一個標準,是進行資料庫連線的抽象層,由java編寫的一組類和介面組成,介面的實現由各個資料庫廠商來完成

雙親委託模式的補充

在Java中,把核心類(rt.jar)中提供外部服務,可由應用層自行實現的介面,這種方式成為spi.那我們看一下,在啟動類載入器中,訪問由應用類載入器實現spi介面的原理

Thread類中有兩個方法

public ClassLoader getContextClassLoader()//獲取執行緒中的上下文載入器
public void setContextClassLoader(ClassLoader cl)//設定執行緒中的上下文載入器
複製程式碼

通過這兩個方法,可以把一個ClassLoader置於一個執行緒的例項之中,使該ClassLoader成為一個相對共享的例項.這樣即使是啟動類載入器中的程式碼也可以通過這種方式訪問應用類載入器中的類了.如下圖

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

寫在最後

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

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

相關文章