導讀
- 想要搞懂Dubbo底層實現,ExtensionLoader是不可繞過得門檻,不能深刻理解其擴展點設計,源碼閱讀部分會很懵逼!!!(當然,即使擴展點懂了,源碼也不一定能看懂,哈哈。玩笑話,意思就是Dubbo得源碼還是比較難讀的,因爲有很多概念與設計如果不弄清楚,基本上會被繞暈。)
- 關鍵字 :Dubbo 擴展點設計(SPI)、ExtensionLoader
Dubbo 擴展點定義
- 從 ExtensionLoader的源碼中,我們可以找到 Dubbo SPI加載的目錄有三個:1. META-INF/services/ (標準的SPI路徑)2. META-INF/dubbo/ 3. META-INF/dubbo/internal/ (內部實現)
- 擴展點文件定義格式:目錄 / 接口全路徑
- 文件內容(常用格式)
registry=com.apache.dubbo.registry.integration.RegistryProtocol dubbo=com.apache.dubbo.rpc.protocol.dubbo.DubboProtocol filter=com.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.apache.dubbo.rpc.protocol.ProtocolListenerWrapper # 沒有等號情況 com.apache.dubbo.registry.integration.XxxProtocol com.apache.dubbo.registry.integration.YyyProtocol # 等號前面有多個值得情況 zzz,default=com.apache.dubbo.registry.integration.CustomProtocol
以下解讀,作者根據具體的某個擴展點去分析加載過程
Ⅰ. ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtion()
- 先判斷出當前
type(Protocol.class)
是否有擴展類加載器,沒有就創建一個
- 先判斷出當前
- 然後從緩存的
cachedAdaptiveInstance
適配實例Holder中查找,沒有的話就創建一個適配類
- 然後從緩存的
- 查找適配類:加載當前擴展點對應的全部擴展實現, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個路徑下,擴展點上一定要有
@SPI
註解,並且註解的值只能有一個(默認擴展點
)
- 查找適配類:加載當前擴展點對應的全部擴展實現, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個路徑下,擴展點上一定要有
- 如果擴展實現上 帶有
@Adaptive
註解,則當作默認的適配類擴展(只能定義一個擴展適配類實現)
- 如果擴展實現上 帶有
-
否則嘗試獲取包裝類擴展(帶有擴展點接口的構造器),如果有則添加到
wrappers
集合中。此處可以得知(適配類即使是包裝類的約束格式,也不會被當作包裝類)
-
- 如果沒有包裝類,則直接獲取無參構造器(此處目的是確保擴展實現可以被實例化)
- 判斷
=
號前面的擴展實現名稱,若爲空(等號前面沒有值),則取擴展實現類除去後綴的部分並且全部小寫(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作爲名稱name
- 判斷
- 再將
name
根據逗號拆分,若擴展實現類上有@Activate
激活註解,則取name[0]作爲key,註解作爲value,放入cachedActivates
緩存map中。然後邊遍歷name
,依次添加到cachedNames
(key = 擴展實現class實例,value = name)中
- 再將
- 最後都放入到 當前類型對應得擴展實現中
cachedClasses
- 最後都放入到 當前類型對應得擴展實現中
- 如果緩存得適配類
cachedAdaptiveClass
爲空(表示 擴展實現類上有標註 @Adaptive),則需要創建適配類( Protocol 擴展點沒有適配類 )
- 如果緩存得適配類
- 獲取擴展點
Protocol
所有方法,判斷方法上是否存在@Adaptive
註解,若不存在則直接拋異常,否則生成動態得適配類。如下:
- 獲取擴展點
package com.apache.dubbo.rpc;
import com.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
public void destroy()
{
throw new UnsupportedOperationException(
"method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public int getDefaultPort()
{
throw new UnsupportedOperationException(
"method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public com.apache.dubbo.rpc.Exporter export(com.apache.dubbo.rpc.Invoker arg0) throws com.apache.dubbo.rpc.RpcException
{
if(arg0 == null) throw new IllegalArgumentException("com.apache.dubbo.rpc.Invoker argument == null");
if(arg0.getUrl() == null) throw new IllegalArgumentException(
"com.apache.dubbo.rpc.Invoker argument getUrl() == null");
com.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 獲取url中指定得協議,默認是 dubbo協議
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0); // 調用真正得協議擴展實現得export方法
}
public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException{
if(arg1 == null) throw new IllegalArgumentException("url == null");
com.apache.dubbo.common.URL url = arg1;
// 獲取url中指定得協議,默認是 dubbo協議
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 獲取真實得協議擴展實現
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1); // 調用真正得協議擴展實現得refer方法
}
}
- 創建完適配類之後,接着再獲取
Compiler
得適配類,用於動態生成Protocol
得適配類(其他動態生成得適配類也是用Compiler
來生成得)。由於Compiler
存在適配類,即擴展點類加載器對應得緩存cachedAdaptiveClass
不爲空,直接返回。此處cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
- 創建完適配類之後,接着再獲取
- 實例化並調用
injectExtension
方法注入其他擴展實例(不適合框架實現得擴展適配類,因爲動態生成得適配類基本上不會依賴其他擴展點
):判斷生成得適配類中 是否存在類似方法:1. set開頭得 2. 只有一個參數類型 3. public訪問修飾符,若有,則截取set
之後得首字母小姐得名稱將其作爲擴展名,然後通過ExtensionFactory
工廠獲取此擴展名得實例,調用當前setXXX
方法,叫之爲IOC
。由於AdaptiveCompiler
只有一個set方法但是沒有對應得 String.class類型得defaultCompiler擴展點
,所以此處不會進行注入:
- 實例化並調用
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
- 接着調用適配擴展實現
AdaptiveCompiler
得compile
方法,方法內會獲取默認得擴展實現(此處是javassist
擴展名)JavassistCompiler
,然後調用其compile方法動態生成適配class實例。
- 接着調用適配擴展實現
Ⅱ. ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
上面生成得適配類中,有一步是獲取真正調用擴展實現 :
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
- 首先還是獲取 擴展點
Protocol
對應得擴展類加載器,接着調用getExtension
方法獲取指定得擴展實現,先查詢是否已經緩存了相應得擴展實例,若沒查到會調用createExtension
方法去創建當前指定得擴展實現。 - 調用
createExtension
方法時,先從當前擴展點實現class實例緩存中查找,若是沒有這個擴展名,說明傳入得參數有問題,會直接拋出異常。然後再從對象緩存實例cachedClasses
中查找是否已經實例化了,沒找到,實例化之後放入對象緩存EXTENSION_INSTANCES
中。 - 調用
injectExtension
方法注入其他得擴展點實現(IOC),同上面Step13 - 獲取當前擴展點得所有包裝類
cachedWrapperClasses
,循環調用包裝類得有參構造器(參數就是擴展點類型)實例化,之後也會調用injectExtension
方法爲包裝類也注入其他得擴展點實現,並返回最後一個包裝類得實例(每個Wrapper類都會包裝一個之前得擴展實現 即: A -> B(A) -> C(B), 最後返回 C這個包裝類)(AOP)。而當前擴展點Protocol
有兩個包裝類實現:ProtocolFilterWrapper
和ProtocolListenerWrapper
。所以,當獲取適配擴展實現時:
- 若是動態生成得,則
getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
-> injectExtension(T instance) (真正執行調用的擴展實現)
-> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
( A -> B(A) -> C(B) 最後返回 C這個包裝類) - 若是定義了適配類實現,則直接走適配類的邏輯
- 若是動態生成得,則
Ⅲ. ExtensionLoader#addExtension(String name, Class<?> clazz)
- 首先加載所有的擴展類實現,若
clazz
沒有實現type
擴展點,拋出異常。 (必須要實現擴展點,纔可以添加) - 若
clazz
是個接口,拋出異常。(擴展實現不能是接口,否則不能實例化) - 若
clazz
上沒有帶有@Adaptive
註解- 若 擴展名
name
爲空,拋出異常。 (擴展名不能爲空) - 若 緩存擴展實現
cachedClasses
中已經存在當前擴展名,拋出異常。(擴展名不能重複) - 若以上兩個條件都不滿足,則將擴展名與擴展點建立映射關係 , 緩存到
cachedNames
,cachedClasses
中。
- 若 擴展名
- 若
clazz
上帶有@Adaptive
註解- 若適配類
cachedAdaptiveClass
不爲空,拋出異常。(擴展點的適配類只能有一個) - 爲空的話,將當前
clazz
作爲擴展點的適配類,賦值給cachedAdaptiveClass
添加擴展實現的邏輯還是比較簡單的。通過API的方式動態添加擴展實現,可以不通過配置文件的方式(挺實用的)
- 若適配類
- ☛ 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
- ☛ 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是幹活多多哦,文章也在全力更新中。
- ☛ 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處!