三歪問我Dubbo的SPI機制是啥?

前言

上一篇 Dubbo 文章敖丙已經帶了你們過了一遍總體的架構,也提到了 Dubbo 的成功離不開它採用微內核設計+SPI擴展,使得有特殊需求的接入方能夠自定義擴展,作定製的二次開發。java

良好的擴展性對於一個框架而言尤爲重要,框架顧名思義就是搭好核心架子,給予用戶簡單便捷的使用,同時也須要知足他們定製化的需求。sql

Dubbo 就依靠 SPI 機制實現了插件化功能,幾乎將全部的功能組件作成基於 SPI 實現,而且默認提供了不少能夠直接使用的擴展點,實現了面向功能進行拆分的對擴展開放的架構。
三歪問我Dubbo的SPI機制是啥?數據庫

什麼是 SPI

首先咱們得先知道什麼叫 SPI。apache

SPI (Service Provider Interface),主要是用來在框架中使用的,最多見和莫過於咱們在訪問數據庫時候用到的java.sql.Driver接口了。編程

你想一下首先市面上的數據庫五花八門,不一樣的數據庫底層協議的大不相同,因此首先須要定製一個接口,來約束一下這些數據庫,使得 Java 語言的使用者在調用數據庫的時候能夠方便、統一的面向接口編程。緩存

數據庫廠商們須要根據接口來開發他們對應的實現,那麼問題來了,真正使用的時候到底用哪一個實現呢?從哪裏找到實現類呢?架構

這時候 Java SPI 機制就派上用場了,不知道到底用哪一個實現類和找不到實現類,咱們告訴它不就完事了唄。app

你們都約定好將實現類的配置寫在一個地方,而後到時候都去哪一個地方查一下不就知道了嗎?

三歪問我Dubbo的SPI機制是啥?

Java SPI 就是這樣作的,約定在 Classpath 下的 META-INF/services/ 目錄裏建立一個以服務接口命名的文件,而後文件裏面記錄的是此 jar 包提供的具體實現類的全限定名。框架

這樣當咱們引用了某個 jar 包的時候就能夠去找這個 jar 包的 META-INF/services/ 目錄,再根據接口名找到文件,而後讀取文件裏面的內容去進行實現類的加載與實例化。ide

好比咱們看下 MySQL 是怎麼作的。
三歪問我Dubbo的SPI機制是啥?

再來看一下文件裏面的內容。
三歪問我Dubbo的SPI機制是啥?

MySQL 就是這樣作的,爲了讓你們更加深入的理解我再簡單的寫一個示例。

Java SPI 示例

三歪問我Dubbo的SPI機制是啥?

而後我在 META-INF/services/ 目錄下建了個以接口全限定名命名的文件,內容以下

com.demo.spi.NuanNanAobing
com.demo.spi.ShuaiAobing

運行以後的結果以下

三歪問我Dubbo的SPI機制是啥?

Java SPI 源碼分析

以前的文章我也提到了 Dubbo 並無用 Java 實現的 SPI,而是自定義 SPI,那確定是 Java SPI 有什麼不方便的地方或者劣勢。

所以丙帶着你們先深刻了解一下 Java SPI,這樣才能知道哪裏很差,進而再和 Dubbo SPI 進行對比的時候會更加的清晰其優點。

你們看到源碼不要怕,丙已經給你們作了註釋,而且邏輯也不難的,想要變強源碼不可或缺。爲了讓你們更好的理解,丙在源碼分析完了以後還會畫個圖,幫你們再理一下思路。

從上面個人示例中能夠看到ServiceLoader.load()其實就是 Java SPI 入口,咱們來看看到底作了什麼操做。
三歪問我Dubbo的SPI機制是啥?

我用一句話歸納一下,簡單的說就是先找當前線程綁定的 ClassLoader,若是沒有就用 SystemClassLoader,而後清除一下緩存,再建立一個 LazyIterator。

那如今重點就是 LazyIterator了,從上面代碼能夠看到咱們調用了 hasNext() 來作實例循環,經過 next() 獲得一個實例。而 LazyIterator 其實就是 Iterator 的實現類。咱們來看看它到底幹了啥。
三歪問我Dubbo的SPI機制是啥?

無論進入 if 分支仍是 else 分支,重點都在我框出來的代碼,接下來就進入重要時刻了!
三歪問我Dubbo的SPI機制是啥?

能夠看到這個方法其實就是在約定好的地方找到接口對應的文件,而後加載文件而且解析文件裏面的內容。

咱們再來看一下 nextService()。

三歪問我Dubbo的SPI機制是啥?
因此就是經過文件裏填寫的全限定名加載類,而且建立其實例放入緩存以後返回實例。

總體的 Java SPI 的源碼解析已經完畢,是否是很簡單?就是約定一個目錄,根據接口名去那個目錄找到文件,文件解析獲得實現類的全限定名,而後循環加載實現類和建立其實例。

我再用一張圖來帶你們過一遍。
三歪問我Dubbo的SPI機制是啥?

想一下 Java SPI 哪裏很差

相信你們一眼就能看出來,Java SPI 在查找擴展實現類的時候遍歷 SPI 的配置文件而且將實現類所有實例化,假設一個實現類初始化過程比較消耗資源且耗時,可是你的代碼裏面又用不上它,這就產生了資源的浪費。

因此說 Java SPI 沒法按需加載實現類。

Dubbo SPI

所以 Dubbo 就本身實現了一個 SPI,讓咱們想一下按需加載的話首先你得給個名字,經過名字去文件裏面找到對應的實現類全限定名而後加載實例化便可。

Dubbo 就是這樣設計的,配置文件裏面存放的是鍵值對,我截一個 Cluster 的配置。
三歪問我Dubbo的SPI機制是啥?

而且 Dubbo SPI 除了能夠按需加載實現類以外,增長了 IOC 和 AOP 的特性,還有個自適應擴展機制。

咱們先來看一下 Dubbo 對配置文件目錄的約定,不一樣於 Java SPI ,Dubbo 分爲了三類目錄。

  • META-INF/services/ 目錄:該目錄下的 SPI 配置文件是爲了用來兼容 Java SPI 。

  • META-INF/dubbo/ 目錄:該目錄存放用戶自定義的 SPI 配置文件。

  • META-INF/dubbo/internal/ 目錄:該目錄存放 Dubbo 內部使用的 SPI 配置文件。

Dubbo SPI 簡單實例

用法非常簡單,我就拿官網上的例子來展現一下。

首先在 META-INF/dubbo 目錄下按接口全限定名創建一個文件,內容以下:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

而後在接口上標註@SPI 註解,以代表它要用SPI機制,相似下面這個圖(我就是拿 Cluster 的圖舉個例子,和這個示例代碼定義的接口不同)。
三歪問我Dubbo的SPI機制是啥?

接着經過下面的示例代碼便可加載指定的實現類。
三歪問我Dubbo的SPI機制是啥?

再來看一下運行的結果。
三歪問我Dubbo的SPI機制是啥?

Dubbo 源碼分析

這次分析的源碼版本是 2.6.5

相信經過上面的描述你們已經對 Dubbo SPI 已經有了必定的認識,接下來咱們來看看它的實現。

從上面的示例代碼咱們知道 ExtensionLoader 好像就是重點,它是相似 Java SPI 中 ServiceLoader 的存在。

咱們能夠看到大體流程就是先經過接口類找到一個 ExtensionLoader ,而後再經過 ExtensionLoader.getExtension(name) 獲得指定名字的實現類實例。

咱們就先看下 getExtensionLoader() 作了什麼。
三歪問我Dubbo的SPI機制是啥?

很簡單,作了一些判斷而後從緩存裏面找是否已經存在這個類型的 ExtensionLoader ,若是沒有就新建一個塞入緩存。最後返回接口類對應的 ExtensionLoader 。

咱們再來看一下 getExtension() 方法,從現象咱們能夠知道這個方法就是從類對應的 ExtensionLoader 中經過名字找到實例化完的實現類。
三歪問我Dubbo的SPI機制是啥?

能夠看到重點就是 createExtension(),咱們再來看下這個方法幹了啥。
三歪問我Dubbo的SPI機制是啥?

總體邏輯很清晰,先找實現類,判斷緩存是否有實例,沒有就反射建個實例,而後執行 set 方法依賴注入。若是有找到包裝類的話,再包一層。

到這步爲止我先畫個圖,你們理一理,仍是很簡單的。
三歪問我Dubbo的SPI機制是啥?

那麼問題來了 getExtensionClasses() 是怎麼找的呢?injectExtension() 如何注入的呢(其實我已經說了set方法注入)?爲何須要包裝類呢?

getExtensionClasses

這個方法進去也是先去緩存中找,若是緩存是空的,那麼調用 loadExtensionClasses,咱們就來看下這個方法。

三歪問我Dubbo的SPI機制是啥?
而 loadDirectory裏面就是根據類名和指定的目錄,找到文件先獲取全部的資源,而後一個一個去加載類,而後再經過loadClass去作一下緩存操做。
三歪問我Dubbo的SPI機制是啥?

能夠看到,loadClass 以前已經加載了類,loadClass 只是根據類上面的狀況作不一樣的緩存。分別有 Adaptive 、WrapperClass 和普通類這三種,普通類又將Activate記錄了一下。至此對於普通的類來講整個 SPI 過程完結了。
三歪問我Dubbo的SPI機制是啥?

接下來咱們分別看不是普通類的幾種東西是幹啥用的。

Adaptive 註解 - 自適應擴展

在進入這個註解分析以前,咱們須要知道 Dubbo 的自適應擴展機制。

咱們先來看一個場景,首先咱們根據配置來進行 SPI 擴展的加載,可是我不想在啓動的時候讓擴展被加載,我想根據請求時候的參數來動態選擇對應的擴展。

怎麼作呢?

Dubbo 經過一個代理機制實現了自適應擴展,簡單的說就是爲你想擴展的接口生成一個代理類,能夠經過JDK 或者 javassist 編譯你生成的代理類代碼,而後經過反射建立實例。

這個實例裏面的實現會根據原本方法的請求參數得知須要的擴展類,而後經過 ExtensionLoader.getExtensionLoader(type.class).getExtension(從參數得來的name),來獲取真正的實例來調用。

我從官網搞了個例子,你們來看下。
三歪問我Dubbo的SPI機制是啥?

如今你們應該對自適應擴展有了必定的認識了,咱們再來看下源碼,到底怎麼作的。
三歪問我Dubbo的SPI機制是啥?

這個註解就是自適應擴展相關的註解,能夠修飾類和方法上,在修飾類的時候不會生成代理類,由於這個類就是代理類,修飾在方法上的時候會生成代理類。

Adaptive 註解在類上

好比這個 ExtensionFactory 有三個實現類,其中一個實現類就被標註了 Adaptive 註解。
三歪問我Dubbo的SPI機制是啥?
三歪問我Dubbo的SPI機制是啥?

在 ExtensionLoader 構造的時候就會去經過getAdaptiveExtension 獲取指定的擴展類的 ExtensionFactory。
三歪問我Dubbo的SPI機制是啥?

咱們再來看下 AdaptiveExtensionFactory 的實現。
三歪問我Dubbo的SPI機制是啥?

能夠看到先緩存了全部實現類,而後在獲取的時候經過遍歷找到對應的 Extension。

咱們再來深刻分析一波 getAdaptiveExtension 裏面到底幹了什麼。
三歪問我Dubbo的SPI機制是啥?

到這裏其實已經和上文分析的 getExtensionClasses中loadClass 對 Adaptive 特殊緩存相呼應上了。
三歪問我Dubbo的SPI機制是啥?

Adaptive 註解在方法上

註解在方法上則須要動態拼接代碼,而後動態生成類,咱們以 Protocol 爲例子來看一下。
三歪問我Dubbo的SPI機制是啥?

Protocol 沒有實現類註釋了 Adaptive ,可是接口上有兩個方法註解了 Adaptive ,有兩個方法沒有。

所以它走的邏輯應該應該是 createAdaptiveExtensionClass,
三歪問我Dubbo的SPI機制是啥?

具體在裏面如何生成代碼的我就再也不深刻了,有興趣的本身去看吧,我就把成品解析一下,就差很少了。
三歪問我Dubbo的SPI機制是啥?

我美化一下給你們看看。
三歪問我Dubbo的SPI機制是啥?

能夠看到會生成包,也會生成 import 語句,類名就是接口加個$Adaptive,而且實現這接口,沒有標記 Adaptive 註解的方法調用的話直接拋錯。

咱們再來看一下標註了註解的方法,我就拿 export 舉例。
三歪問我Dubbo的SPI機制是啥?

就像我前面說的那樣,根據請求的參數,即 URL 獲得具體要調用的實現類名,而後再調用 getExtension 獲取。

整個自適應擴展流程以下。
三歪問我Dubbo的SPI機制是啥?

WrapperClass - AOP

包裝類是由於一個擴展接口可能有多個擴展實現類,而這些擴展實現類會有一個相同的或者公共的邏輯,若是每一個實現類都寫一遍代碼就重複了,而且比較很差維護。

所以就搞了個包裝類,Dubbo 裏幫你自動包裝,只須要某個擴展類的構造函數只有一個參數,而且是擴展接口類型,就會被斷定爲包裝類,而後記錄下來,用來包裝別的實現類。
三歪問我Dubbo的SPI機制是啥?

簡單又巧妙,這就是 AOP 了。

injectExtension - IOC

直接看代碼,很簡單,就是查找 set 方法,根據參數找到依賴對象則注入。
三歪問我Dubbo的SPI機制是啥?

這就是 IOC。

Activate 註解

這個註解我就簡單的說下,拿 Filter 舉例,Filter 有不少實現類,在某些場景下須要其中的幾個實現類,而某些場景下須要另外幾個,而 Activate 註解就是標記這個用的。

它有三個屬性,group 表示修飾在哪一個端,是 provider 仍是 consumer,value 表示在 URL參數中出現纔會被激活,order 表示實現類的順序。

總結

先放個上述過程完整的圖。
三歪問我Dubbo的SPI機制是啥?

而後咱們再來總結一下,今天丙先帶你們瞭解了下什麼是 SPI,寫了個簡單示例,而且進行了 Java SPI 源碼分析。

得知了 Java SPI 會一次加載和實例化全部的實現類。

而 Dubbo SPI 則本身實現了 SPI,能夠經過名字實例化指定的實現類,而且實現了 IOC 、AOP 與 自適應擴展 SPI 。

總體而言不是很難,也不會很繞,你們看了文章以後若是本身再過一遍收穫會更大。

我是敖丙,你知道的越多,你不知道的越多,咱們下期見!

相關文章
相關標籤/搜索