由淺入深理解Dubbo的SPI機制

mars_jun發表於2019-02-26

前言

在分析dubbo原始碼的過程中,發現dubbo對於擴充套件點的載入實現的是非常巧妙的,可以達到用時才動態例項化物件,靈活且節約資源。其實Dubbo 的擴充套件點載入是從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。它優化了JDK必須一次性例項化擴充套件點所有實現的缺點。

JDK標準的SPI

一個介面可以有多個不同的實現類,但是在一些業務場景裡面,我們需要根據不同的業務型別去選擇具體的能夠滿足我當前需求的實現類。大多數時候,我們都是在記憶體裡面維護一個Map,這樣可以很高效的實現我們們的目的。但是這樣擴充套件性太差了,每次增加一種實現類,都得去修改原來的程式碼,風險太大了。於是,JDK給我們們提供了SPI技術,用來去解決這一塊的短板。具體的操作方式如下:

要素一:介面類

package github.com.crazyStrongboy.inter;

要素二:不同的實現類

package github.com.crazyStrongboy.jdk_api;


要素三:配置檔案

在resources資原始檔夾下面建立目錄META-INF/services,建立介面全路徑的檔案,例如:

呼叫方式

這裡drivers中會載入到我們們預設給的兩個實現類,如果要增加一個實現,我們們只要新建一個模組,在配置檔案中加上我們們的實現github.com.crazyStrongboy.jdk_api.xxx即可,不會入侵原來的老程式碼。但是這種實現的弊端也很明顯,一次性載入出來了所有的擴充套件類,浪費資源。相關程式碼在github中。


自定義SPI實現

自己去定義一個SPI的實現,主要分三步走:

  1. 利用ClassLoader類載入器去讀取資原始檔夾指定名稱的檔案。
  2. 解析檔案內容,並存放到一個Map容器中。鍵為name,值為具體實現類的Class。
  3. 封裝一個對外提供的方法,引數為name值。
第一步:讀取資源
第二步:解析配置檔案內容
第三步:提供方法

我這邊用的路徑為META-INF/mars_jun/


配置檔案:
呼叫方式

其實這個不算太完整,可以把每次初始化後的物件根據相應的name儲存到另一個Map中,這樣就不會每次呼叫getExtension都會去生成一個新的例項。但是在上面這段程式碼當中,我們們可以觀察到,我並沒有在一開始就將所有的擴充套件類都初始化出來,而是先儲存擴充套件類的Class到Map中,直到我們們需要使用的時候再去初始化例項物件。解決了JDK中SPI會一次性例項化擴充套件點所有實現的這個缺陷。相關程式碼在github中。


Dubbo的SPI機制

我們們直接從下面這段程式碼開始:

1    private static final Protocol protocol = ExtensionLoader.
2            getExtensionLoader(Protocol.class).getAdaptiveExtension();
複製程式碼

首先先普及下兩個註解@Adaptive與@SPI

  1. 一個介面的實現類至多隻能有一個被@Adaptive註解,在方法上不限,註解在類上意思是標記該類為預設擴充套件類,標記在方法上則可支援動態的建立擴充套件器。
  2. @SPI可指定預設動態生成的擴充套件類。

ExtensionLoader.getExtensionLoader(Protocol.class)這一句程式碼只是簡單的構建了一個ExtensionLoader擴充套件器載入器物件,程式碼不太複雜。後面的getAdaptiveExtension才是重點。順著鏈路呼叫,會到下圖所示的方法:

關注上面圈紅的標記處,主要分為了兩步走:

第一步:載入所有配置檔案

是不是感覺似曾相識~,這一塊程式碼也就讀取了dubbo指定的幾個資源目錄的配置,包括"META-INF/services/" "META-INF/dubbo/" "META-INF/dubbo/internal/"這三個目錄,然後一個個解析出來,丟到指定的Map集合中,快取起來供後期類似的操作使用。當然其中還包括一些註解的解析,是否是包裝類等等一些操作,這些大家可以自行點進去了解。

第二步:動態編譯Protocol$Adaptive檔案

在沒有指定自適應的cachedAdaptiveClass的情況下(也就是實現類沒有一個上面有@Adaptive註解),會呼叫createAdaptiveExtensionClass方法生成一個xx$Adaptive 物件。

Dubbo的SPI機制的核心點也在這裡,重點關注xx$Adaptive 物件Protocol對應的是Protocol$Adaptive。我們們簡單看一下它生成的程式碼段:

它可以根據URL中的protocol欄位的值去動態獲取相應的擴充套件類,例如"dubbo"對應DubboProtocol,"registry"對應RegistryProtocol,這樣是不是更加的靈活~。

這一塊雖然寫的不多,但核心思想點也就差不多都在這一塊,順著上面的思路一步步往下讀,這個東西應該不難理解~


END

相關文章