在分析dubbo源碼的過程當中,發現dubbo對於擴展點的加載實現的是很是巧妙的,能夠達到用時才動態實例化對象,靈活且節約資源。其實Dubbo 的擴展點加載是從 JDK 標準的 SPI (Service Provider Interface) 擴展點發現機制增強而來。它優化了JDK必須一次性實例化擴展點全部實現的缺點。java
一個接口能夠有多個不一樣的實現類,可是在一些業務場景裏面,咱們須要根據不一樣的業務類型去選擇具體的可以知足我當前需求的實現類。大多數時候,咱們都是在內存裏面維護一個Map,這樣能夠很高效的實現我們的目的。可是這樣擴展性太差了,每次增長一種實現類,都得去修改原來的代碼,風險太大了。因而,JDK給我們提供了SPI技術,用來去解決這一塊的短板。具體的操做方式以下:git
package github.com.crazyStrongboy.inter;
github
package github.com.crazyStrongboy.jdk_api;
api
在resources資源文件夾下面創建目錄META-INF/services,創建接口全路徑的文件,例如:
緩存
這裏drivers
中會加載到我們默認給的兩個實現類,若是要增長一個實現,我們只要新建一個模塊,在配置文件中加上我們的實現github.com.crazyStrongboy.jdk_api.xxx
便可,不會入侵原來的老代碼。可是這種實現的弊端也很明顯,一次性加載出來了全部的擴展類,浪費資源。相關代碼在github中。app
本身去定義一個SPI的實現,主要分三步走:ide
我這邊用的路徑爲META-INF/mars_jun/
優化
其實這個不算太完整,能夠把每次初始化後的對象根據相應的name存儲到另外一個Map中,這樣就不會每次調用getExtension
都會去生成一個新的實例。可是在上面這段代碼當中,我們能夠觀察到,我並無在一開始就將全部的擴展類都初始化出來,而是先保存擴展類的Class
到Map中,直到我們須要使用的時候再去初始化實例對象。解決了JDK中SPI會一次性實例化擴展點全部實現的這個缺陷。相關代碼在github中。spa
我們直接從下面這段代碼開始:code
1 private static final Protocol protocol = ExtensionLoader.
2 getExtensionLoader(Protocol.class).getAdaptiveExtension();
複製代碼
首先先普及下兩個註解@Adaptive與@SPI
:
@Adaptive
註解,在方法上不限,註解在類上意思是標記該類爲默認擴展類,標記在方法上則可支持動態的建立擴展器。@SPI
可指定默認動態生成的擴展類。ExtensionLoader.getExtensionLoader(Protocol.class)
這一句代碼只是簡單的構建了一個ExtensionLoader
擴展器加載器對象,代碼不太複雜。後面的getAdaptiveExtension
纔是重點。順着鏈路調用,會到下圖所示的方法:
關注上面圈紅的標記處,主要分爲了兩步走:
是否是感受似曾相識~,這一塊代碼也就讀取了dubbo指定的幾個資源目錄的配置,包括"META-INF/services/" "META-INF/dubbo/" "META-INF/dubbo/internal/"
這三個目錄,而後一個個解析出來,丟到指定的Map集合中,緩存起來供後期相似的操做使用。固然其中還包括一些註解的解析,是不是包裝類等等一些操做,這些你們能夠自行點進去了解。
在沒有指定自適應的cachedAdaptiveClass
的狀況下(也就是實現類沒有一個上面有@Adaptive註解),會調用createAdaptiveExtensionClass
方法生成一個xx$Adaptive
對象。
Dubbo的SPI機制的核心點也在這裏,重點關注xx$Adaptive 對象
。Protocol
對應的是Protocol$Adaptive
。我們簡單看一下它生成的代碼段:
它能夠根據URL中的protocol
字段的值去動態獲取相應的擴展類,例如"dubbo"對應DubboProtocol
,"registry"對應RegistryProtocol
,這樣是否是更加的靈活~。
這一塊雖然寫的很少,但核心思想點也就差很少都在這一塊,順着上面的思路一步步往下讀,這個東西應該不難理解~