Dubbo設(shè)計(jì)之ExtensionLoader

導(dǎo)讀

  • 想要搞懂Dubbo底層實(shí)現(xiàn),ExtensionLoader是不可繞過得門檻,不能深刻理解其擴(kuò)展點(diǎn)設(shè)計(jì),源碼閱讀部分會(huì)很懵逼?。?!(當(dāng)然,即使擴(kuò)展點(diǎn)懂了,源碼也不一定能看懂,哈哈。玩笑話,意思就是Dubbo得源碼還是比較難讀的,因?yàn)橛泻芏喔拍钆c設(shè)計(jì)如果不弄清楚,基本上會(huì)被繞暈。)
  • 關(guān)鍵字Dubbo 擴(kuò)展點(diǎn)設(shè)計(jì)(SPI)、ExtensionLoader

Dubbo 擴(kuò)展點(diǎn)定義

  • 從 ExtensionLoader的源碼中,我們可以找到 Dubbo SPI加載的目錄有三個(gè):1. META-INF/services/ (標(biāo)準(zhǔn)的SPI路徑)2. META-INF/dubbo/ 3. META-INF/dubbo/internal/ (內(nèi)部實(shí)現(xiàn))
  • 擴(kuò)展點(diǎn)文件定義格式:目錄 / 接口全路徑
  • 文件內(nèi)容(常用格式)
       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
    
       # 沒有等號(hào)情況
       com.apache.dubbo.registry.integration.XxxProtocol
       com.apache.dubbo.registry.integration.YyyProtocol
    
       # 等號(hào)前面有多個(gè)值得情況
       zzz,default=com.apache.dubbo.registry.integration.CustomProtocol
    
    

以下解讀,作者根據(jù)具體的某個(gè)擴(kuò)展點(diǎn)去分析加載過程

Ⅰ. ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtion()
    1. 先判斷出當(dāng)前 type(Protocol.class) 是否有擴(kuò)展類加載器,沒有就創(chuàng)建一個(gè)
    1. 然后從緩存的 cachedAdaptiveInstance 適配實(shí)例Holder中查找,沒有的話就創(chuàng)建一個(gè)適配類
      ExtensionLoader#getExtensionLoader
    1. 查找適配類:加載當(dāng)前擴(kuò)展點(diǎn)對(duì)應(yīng)的全部擴(kuò)展實(shí)現(xiàn), META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個(gè)路徑下,擴(kuò)展點(diǎn)上一定要有@SPI 注解,并且注解的值只能有一個(gè)(默認(rèn)擴(kuò)展點(diǎn)
    1. 如果擴(kuò)展實(shí)現(xiàn)上 帶有 @Adaptive 注解,則當(dāng)作默認(rèn)的適配類擴(kuò)展(只能定義一個(gè)擴(kuò)展適配類實(shí)現(xiàn))
    1. 否則嘗試獲取包裝類擴(kuò)展(帶有擴(kuò)展點(diǎn)接口的構(gòu)造器),如果有則添加到 wrappers 集合中。

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

    1. 如果沒有包裝類,則直接獲取無參構(gòu)造器(此處目的是確保擴(kuò)展實(shí)現(xiàn)可以被實(shí)例化)
    1. 判斷 = 號(hào)前面的擴(kuò)展實(shí)現(xiàn)名稱,若為空(等號(hào)前面沒有值),則取擴(kuò)展實(shí)現(xiàn)類除去后綴的部分并且全部小寫(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作為名稱name
    1. 再將 name 根據(jù)逗號(hào)拆分,若擴(kuò)展實(shí)現(xiàn)類上有 @Activate 激活注解,則取name[0]作為key,注解作為value,放入cachedActivates 緩存map中。然后邊遍歷 name ,依次添加到 cachedNames(key = 擴(kuò)展實(shí)現(xiàn)class實(shí)例,value = name)中
    1. 最后都放入到 當(dāng)前類型對(duì)應(yīng)得擴(kuò)展實(shí)現(xiàn)中 cachedClasses
    1. 如果緩存得適配類cachedAdaptiveClass 為空(表示 擴(kuò)展實(shí)現(xiàn)類上有標(biāo)注 @Adaptive),則需要?jiǎng)?chuàng)建適配類( Protocol 擴(kuò)展點(diǎn)沒有適配類 )
      ExtensionLoader#getAdaptiveExtension

      ExtensionLoader#createAdaptiveExtension
    1. 獲取擴(kuò)展點(diǎn)Protocol 所有方法,判斷方法上是否存在 @Adaptive 注解,若不存在則直接拋異常,否則生成動(dòng)態(tài)得適配類。如下:
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中指定得協(xié)議,默認(rèn)是 dubbo協(xié)議
        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); // 調(diào)用真正得協(xié)議擴(kuò)展實(shí)現(xiàn)得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中指定得協(xié)議,默認(rèn)是 dubbo協(xié)議
            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])");
        
            // 獲取真實(shí)得協(xié)議擴(kuò)展實(shí)現(xiàn)
            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); // 調(diào)用真正得協(xié)議擴(kuò)展實(shí)現(xiàn)得refer方法
        }
}  
    1. 創(chuàng)建完適配類之后,接著再獲取 Compiler 得適配類,用于動(dòng)態(tài)生成 Protocol 得適配類(其他動(dòng)態(tài)生成得適配類也是用 Compiler 來生成得)。由于 Compiler存在適配類,即擴(kuò)展點(diǎn)類加載器對(duì)應(yīng)得緩存cachedAdaptiveClass不為空,直接返回。此處 cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
    1. 實(shí)例化并調(diào)用injectExtension 方法注入其他擴(kuò)展實(shí)例(不適合框架實(shí)現(xiàn)得擴(kuò)展適配類,因?yàn)閯?dòng)態(tài)生成得適配類基本上不會(huì)依賴其他擴(kuò)展點(diǎn)):判斷生成得適配類中 是否存在類似方法:1. set開頭得 2. 只有一個(gè)參數(shù)類型 3. public訪問修飾符,若有,則截取set 之后得首字母小姐得名稱將其作為擴(kuò)展名,然后通過 ExtensionFactory工廠獲取此擴(kuò)展名得實(shí)例,調(diào)用當(dāng)前setXXX方法,叫之為IOC。由于 AdaptiveCompiler 只有一個(gè)set方法但是沒有對(duì)應(yīng)得 String.class類型得 defaultCompiler擴(kuò)展點(diǎn),所以此處不會(huì)進(jìn)行注入:
      public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }
ExtensionLoader#injectExtension
    1. 接著調(diào)用適配擴(kuò)展實(shí)現(xiàn) AdaptiveCompilercompile方法,方法內(nèi)會(huì)獲取默認(rèn)得擴(kuò)展實(shí)現(xiàn)(此處是javassist擴(kuò)展名)JavassistCompiler,然后調(diào)用其compile方法動(dòng)態(tài)生成適配class實(shí)例。
Ⅱ. ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
上面生成得適配類中,有一步是獲取真正調(diào)用擴(kuò)展實(shí)現(xiàn) :

com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);

  • 首先還是獲取 擴(kuò)展點(diǎn)Protocol對(duì)應(yīng)得擴(kuò)展類加載器,接著調(diào)用 getExtension 方法獲取指定得擴(kuò)展實(shí)現(xiàn),先查詢是否已經(jīng)緩存了相應(yīng)得擴(kuò)展實(shí)例,若沒查到會(huì)調(diào)用 createExtension 方法去創(chuàng)建當(dāng)前指定得擴(kuò)展實(shí)現(xiàn)。
  • 調(diào)用createExtension 方法時(shí),先從當(dāng)前擴(kuò)展點(diǎn)實(shí)現(xiàn)class實(shí)例緩存中查找,若是沒有這個(gè)擴(kuò)展名,說明傳入得參數(shù)有問題,會(huì)直接拋出異常。然后再從對(duì)象緩存實(shí)例cachedClasses 中查找是否已經(jīng)實(shí)例化了,沒找到,實(shí)例化之后放入對(duì)象緩存EXTENSION_INSTANCES中。
  • 調(diào)用 injectExtension 方法注入其他得擴(kuò)展點(diǎn)實(shí)現(xiàn)(IOC),同上面Step13
  • 獲取當(dāng)前擴(kuò)展點(diǎn)得所有包裝類cachedWrapperClasses ,循環(huán)調(diào)用包裝類得有參構(gòu)造器(參數(shù)就是擴(kuò)展點(diǎn)類型)實(shí)例化,之后也會(huì)調(diào)用injectExtension 方法為包裝類也注入其他得擴(kuò)展點(diǎn)實(shí)現(xiàn),并返回最后一個(gè)包裝類得實(shí)例(每個(gè)Wrapper類都會(huì)包裝一個(gè)之前得擴(kuò)展實(shí)現(xiàn) 即: A -> B(A) -> C(B), 最后返回 C這個(gè)包裝類)(AOP)。而當(dāng)前擴(kuò)展點(diǎn) Protocol 有兩個(gè)包裝類實(shí)現(xiàn):ProtocolFilterWrapperProtocolListenerWrapper

    所以,當(dāng)獲取適配擴(kuò)展實(shí)現(xiàn)時(shí):

    1. 若是動(dòng)態(tài)生成得,則
      getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
      -> injectExtension(T instance) (真正執(zhí)行調(diào)用的擴(kuò)展實(shí)現(xiàn))
      -> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
      ( A -> B(A) -> C(B) 最后返回 C這個(gè)包裝類)
    2. 若是定義了適配類實(shí)現(xiàn),則直接走適配類的邏輯
ExtensionLoader#createExtension
Ⅲ. ExtensionLoader#addExtension(String name, Class<?> clazz)
  • 首先加載所有的擴(kuò)展類實(shí)現(xiàn),若 clazz 沒有實(shí)現(xiàn) type擴(kuò)展點(diǎn),拋出異常。 (必須要實(shí)現(xiàn)擴(kuò)展點(diǎn),才可以添加)
  • clazz 是個(gè)接口,拋出異常。(擴(kuò)展實(shí)現(xiàn)不能是接口,否則不能實(shí)例化)
  • clazz上沒有帶有 @Adaptive注解
    • 若 擴(kuò)展名name 為空,拋出異常。 (擴(kuò)展名不能為空)
    • 若 緩存擴(kuò)展實(shí)現(xiàn)cachedClasses中已經(jīng)存在當(dāng)前擴(kuò)展名,拋出異常。(擴(kuò)展名不能重復(fù))
    • 若以上兩個(gè)條件都不滿足,則將擴(kuò)展名與擴(kuò)展點(diǎn)建立映射關(guān)系 , 緩存到cachedNames,cachedClasses中。
  • clazz上帶有@Adaptive注解
    • 若適配類cachedAdaptiveClass 不為空,拋出異常。(擴(kuò)展點(diǎn)的適配類只能有一個(gè))
    • 為空的話,將當(dāng)前 clazz作為擴(kuò)展點(diǎn)的適配類,賦值給 cachedAdaptiveClass

    添加擴(kuò)展實(shí)現(xiàn)的邏輯還是比較簡單的。通過API的方式動(dòng)態(tài)添加擴(kuò)展實(shí)現(xiàn),可以不通過配置文件的方式(挺實(shí)用的)

ExtensionLoader#addExtension
Ⅳ ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, key, group) :
  • 找到 ExtensionLoader的 getActivateExtension方法:
//  ExtensionLoader
public List<T> getActivateExtension(URL url, String key, String group = consumer) {
    // 獲取 URL 中指定 key對(duì)應(yīng)的值
    // 以 ProtocolFilterWrappe r的 buildInvokerChain 方法為例(任何一個(gè)擴(kuò)展點(diǎn)都可)
    // key = REFERENCE_FILTER_KEY,group = consumer
    // 可以進(jìn)行如下改造 ( Filter自定義編排化):
    // 1. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default");
    // 2. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-cache,actives");
    // 3. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default,actives");
    // 你知道上面幾種擴(kuò)展方式,最后返回的 Filter擴(kuò)展實(shí)現(xiàn)有哪些么 ?
    String value = url.getParameter(key);
    // value值用于控制激活擴(kuò)展點(diǎn)是否加載,為空表示不指定激活擴(kuò)展點(diǎn)名稱,從所有的激活擴(kuò)展實(shí)現(xiàn)中根據(jù) @Activate 注解跟指定的組進(jìn)行篩選,若是需要指定,定義的擴(kuò)展點(diǎn)名稱需要以 "," 分隔開
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}

//  ExtensionLoader
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
        // 如果指定的擴(kuò)展的名稱不包括 -default(排除SPI中的實(shí)現(xiàn))標(biāo)識(shí)符,則從所有已經(jīng)加載的激活擴(kuò)展點(diǎn)中找到滿足條件的,否則只加載指定的激活擴(kuò)展點(diǎn)
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {// 遍歷激活擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup) // 如果當(dāng)前需要加載的組在激活擴(kuò)展點(diǎn)實(shí)現(xiàn)指定的組中才算匹配
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) { // URL中至少要有 activateValue[] 中的其中一個(gè)key,當(dāng)前激活擴(kuò)展點(diǎn)實(shí)現(xiàn)才會(huì)被使用
                    exts.add(getExtension(name));
                }
            }
            exts.sort(ActivateComparator.COMPARATOR); // 按照自然序排序
        }
        List<T> usrs = new ArrayList<>();
        // 若names不為空,表示指定了擴(kuò)展點(diǎn)名稱,找到不是以排除標(biāo)識(shí)符'-'開頭并且不包括要排除(-name)的擴(kuò)展點(diǎn)名稱,獲取其擴(kuò)展實(shí)現(xiàn)
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    usrs.add(getExtension(name));
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }
  • 上述就是Dubbo源碼中經(jīng)常出現(xiàn)的獲取擴(kuò)展實(shí)現(xiàn)的代碼,了解了其原理之后,后面我們解讀源碼時(shí),會(huì)更清晰自然了。
  1. ? 文章要是勘誤或者知識(shí)點(diǎn)說的不正確,歡迎評(píng)論,畢竟這也是作者通過閱讀源碼獲得的知識(shí),難免會(huì)有疏忽!
  2. ? 要是感覺文章對(duì)你有所幫助,不妨點(diǎn)個(gè)關(guān)注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容