Dubbo設計之ExtensionLoader

導讀

  • 想要搞懂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()
    1. 先判斷出當前 type(Protocol.class) 是否有擴展類加載器,沒有就創建一個
    1. 然後從緩存的 cachedAdaptiveInstance 適配實例Holder中查找,沒有的話就創建一個適配類
    1. 查找適配類:加載當前擴展點對應的全部擴展實現, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個路徑下,擴展點上一定要有@SPI 註解,並且註解的值只能有一個(默認擴展點
    1. 如果擴展實現上 帶有 @Adaptive 註解,則當作默認的適配類擴展(只能定義一個擴展適配類實現)
    1. 否則嘗試獲取包裝類擴展(帶有擴展點接口的構造器),如果有則添加到 wrappers 集合中。

      此處可以得知(適配類即使是包裝類的約束格式,也不會被當作包裝類)

    1. 如果沒有包裝類,則直接獲取無參構造器(此處目的是確保擴展實現可以被實例化)
    1. 判斷 = 號前面的擴展實現名稱,若爲空(等號前面沒有值),則取擴展實現類除去後綴的部分並且全部小寫(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作爲名稱name
    1. 再將 name 根據逗號拆分,若擴展實現類上有 @Activate 激活註解,則取name[0]作爲key,註解作爲value,放入cachedActivates 緩存map中。然後邊遍歷 name ,依次添加到 cachedNames(key = 擴展實現class實例,value = name)中
    1. 最後都放入到 當前類型對應得擴展實現中 cachedClasses
    1. 如果緩存得適配類cachedAdaptiveClass 爲空(表示 擴展實現類上有標註 @Adaptive),則需要創建適配類( Protocol 擴展點沒有適配類 )

    1. 獲取擴展點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方法
        }
}  
    1. 創建完適配類之後,接着再獲取 Compiler 得適配類,用於動態生成 Protocol 得適配類(其他動態生成得適配類也是用 Compiler 來生成得)。由於 Compiler存在適配類,即擴展點類加載器對應得緩存cachedAdaptiveClass不爲空,直接返回。此處 cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
    1. 實例化並調用injectExtension 方法注入其他擴展實例(不適合框架實現得擴展適配類,因爲動態生成得適配類基本上不會依賴其他擴展點):判斷生成得適配類中 是否存在類似方法:1. set開頭得 2. 只有一個參數類型 3. public訪問修飾符,若有,則截取set 之後得首字母小姐得名稱將其作爲擴展名,然後通過 ExtensionFactory工廠獲取此擴展名得實例,調用當前setXXX方法,叫之爲IOC。由於 AdaptiveCompiler 只有一個set方法但是沒有對應得 String.class類型得 defaultCompiler擴展點,所以此處不會進行注入:
      public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }
    1. 接着調用適配擴展實現 AdaptiveCompilercompile方法,方法內會獲取默認得擴展實現(此處是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 有兩個包裝類實現:ProtocolFilterWrapperProtocolListenerWrapper

    所以,當獲取適配擴展實現時:

    1. 若是動態生成得,則
      getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
      -> injectExtension(T instance) (真正執行調用的擴展實現)
      -> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
      ( A -> B(A) -> C(B) 最後返回 C這個包裝類)
    2. 若是定義了適配類實現,則直接走適配類的邏輯
Ⅲ. ExtensionLoader#addExtension(String name, Class<?> clazz)
  • 首先加載所有的擴展類實現,若 clazz 沒有實現 type擴展點,拋出異常。 (必須要實現擴展點,纔可以添加)
  • clazz 是個接口,拋出異常。(擴展實現不能是接口,否則不能實例化)
  • clazz上沒有帶有 @Adaptive註解
    • 若 擴展名name 爲空,拋出異常。 (擴展名不能爲空)
    • 若 緩存擴展實現cachedClasses中已經存在當前擴展名,拋出異常。(擴展名不能重複)
    • 若以上兩個條件都不滿足,則將擴展名與擴展點建立映射關係 , 緩存到cachedNamescachedClasses中。
  • clazz上帶有@Adaptive註解
    • 若適配類cachedAdaptiveClass 不爲空,拋出異常。(擴展點的適配類只能有一個)
    • 爲空的話,將當前 clazz作爲擴展點的適配類,賦值給 cachedAdaptiveClass

    添加擴展實現的邏輯還是比較簡單的。通過API的方式動態添加擴展實現,可以不通過配置文件的方式(挺實用的)

  1. ☛ 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
  2. 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是幹活多多哦,文章也在全力更新中。
  3. 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章