dubbo源碼(四)-Adaptive源碼詳解

上篇我們簡單講了dubbo Adaptive的使用参萄,沒有太多代碼,一個(gè)接口和幾個(gè)實(shí)現(xiàn)類煎饼,還有一個(gè)配置文件和測試類讹挎。接口和實(shí)現(xiàn)類沒有什么講的,因此入口就從測試類開始吆玖。

測試代碼如下:

        // 1筒溃、獲取 extExtensionLoader
        ExtensionLoader<AdaptiveExt> extExtensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveExt.class);
        // 2、獲取自適應(yīng)擴(kuò)展點(diǎn)
        AdaptiveExt adaptiveExt = extExtensionLoader.getAdaptiveExtension();
        // 3衰伯、URL參數(shù)
        URL url = URL.valueOf("test://localhost/test?adaptive.ext=spring");
        // 4铡羡、調(diào)用
        System.out.println(adaptiveExt.echo("d", url));
  1. ExtensionLoader.getExtensionLoader已經(jīng)講解過积蔚,現(xiàn)在我們直接看getAdaptiveExtension方法意鲸,代碼如下:
    /**
     * 獲取自適應(yīng)擴(kuò)展的instance
     */
    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        // 從緩存中獲取自適應(yīng)擴(kuò)展instance
        Object instance = cachedAdaptiveInstance.get();
        // 沒有命中緩存
        if (instance == null) {
            // 創(chuàng)建自適應(yīng)擴(kuò)展instance異常為空
            if (createAdaptiveInstanceError == null) {
                // 加鎖,雙重檢查
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    // 繼續(xù)沒有命中緩存
                    if (instance == null) {
                        try {
                            // 創(chuàng)建自適應(yīng)擴(kuò)展instance
                            instance = createAdaptiveExtension();
                            // set
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
        // 返回自適應(yīng)擴(kuò)展instance
        return (T) instance;
    }

getAdaptiveExtension 方法首先會(huì)檢查緩存尽爆,緩存未命中怎顾,則調(diào)用 createAdaptiveExtension 方法創(chuàng)建自適應(yīng)拓展。createAdaptiveExtension 代碼如下:

 /**
     * 創(chuàng)建自適應(yīng)擴(kuò)展instance
     */
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            // 獲取自適應(yīng)拓展類漱贱,并通過反射實(shí)例化槐雾,然后就是dubbo IOC 調(diào)用 injectExtension 方法向拓展實(shí)例中注入依賴
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

createAdaptiveExtension 方法的代碼比較少,但卻包含了三個(gè)邏輯幅狮,分別如下:

  • 調(diào)用 getAdaptiveExtensionClass 方法獲取自適應(yīng)拓展 Class 對(duì)象
  • 通過反射進(jìn)行實(shí)例化
  • 調(diào)用 injectExtension 方法向拓展實(shí)例中注入依賴

前兩個(gè)邏輯比較好理解募强,第三個(gè)邏輯用于向自適應(yīng)拓展對(duì)象中注入依賴。這里調(diào)用 injectExtension 方法的目的是為手工編碼的自適應(yīng)拓展注入依賴崇摄,這一點(diǎn)需要大家注意一下擎值。關(guān)于 injectExtension 方法,前文已經(jīng)分析過了逐抑,這里不再贅述鸠儿。接下來,分析 getAdaptiveExtensionClass 方法的邏輯厕氨。

    /**
     * 獲取自適應(yīng)擴(kuò)展的類
     */
    private Class<?> getAdaptiveExtensionClass() {
        // 通過 SPI 獲取所有的拓展類进每,在這一步判斷所有的所有的擴(kuò)展類上有沒有 Adaptive 注解,如果有命斧,將 cachedAdaptiveClass 賦值
        getExtensionClasses();
        // 判斷 cachedAdaptiveClass 是否有值田晚,如果有直接返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 創(chuàng)建自適應(yīng)拓展類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

getAdaptiveExtensionClass 方法同樣包含了三個(gè)邏輯,如下:

  • 調(diào)用 getExtensionClasses 獲取所有的拓展類
  • 檢查緩存国葬,若緩存不為空贤徒,則返回緩存
  • 若緩存為空遭京,則調(diào)用createAdaptiveExtensionClass 創(chuàng)建自適應(yīng)拓展類

首先從第一個(gè)邏輯說起,getExtensionClasses 這個(gè)方法用于獲取某個(gè)接口的所有實(shí)現(xiàn)類泞莉。比如該方法可以獲取 Protocol 接口的 DubboProtocol哪雕、HttpProtocol、InjvmProtocol 等實(shí)現(xiàn)類鲫趁。在獲取實(shí)現(xiàn)類的過程中斯嚎,如果某個(gè)某個(gè)實(shí)現(xiàn)類被 Adaptive 注解修飾了,那么該類就會(huì)被賦值給 cachedAdaptiveClass 變量挨厚。如果所有的實(shí)現(xiàn)類均未被 Adaptive 注解修飾堡僻,那么執(zhí)行第三步邏輯,創(chuàng)建自適應(yīng)拓展類疫剃。相關(guān)代碼如下:

    /**
     * 創(chuàng)建自適應(yīng)拓展類
     */
    private Class<?> createAdaptiveExtensionClass() {
        // 構(gòu)建自適應(yīng)拓展代碼
        String code = createAdaptiveExtensionClassCode();
        // 獲取編譯器實(shí)現(xiàn)類
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        // 編譯代碼钉疫,生成 Class
        return compiler.compile(code, classLoader);
    }

createAdaptiveExtensionClass方法用于生成自適應(yīng)拓展類,該方法首先會(huì)生成自適應(yīng)拓展類的源碼巢价,然后通過Compiler實(shí)例(Dubbo 默認(rèn)使用 javassist 作為編譯器)編譯源碼牲阁,得到代理類 Class 實(shí)例。

  1. 接下來壤躲,我們把重點(diǎn)放在代理類代碼生成的邏輯上城菊,即createAdaptiveExtensionClassCode方法上。
  • 在生成代理類源碼之前碉克,createAdaptiveExtensionClassCode 方法首先會(huì)通過反射檢測接口方法是否包含 Adaptive 注解凌唬。對(duì)于要生成自適應(yīng)拓展的接口,Dubbo 要求該接口至少有一個(gè)方法被 Adaptive 注解修飾漏麦。若不滿足此條件客税,就會(huì)拋出運(yùn)行時(shí)異常。相關(guān)代碼如下:
        // type是擴(kuò)展點(diǎn)class
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for (Method m : methods) {
            // 檢測方法上是否有 Adaptive 注解
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveAnnotation) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }
  • 通過 Adaptive 注解檢測后撕贞,即可開始生成代碼更耻。代碼生成的順序與 Java 文件內(nèi)容順序一致,首先會(huì)生成 package 語句麻掸,然后生成 import 語句酥夭,緊接著生成類名等代碼。整個(gè)邏輯如下:
        // 生成 package 代碼:package + type 所在包
        codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
        // 生成 import 代碼:import + ExtensionLoader 全限定名
        codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
        // 生成類代碼:public class + type簡單名稱 + $Adaptive + implements + type全限定名 + {
        codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

這里以上篇的AdaptiveExt為例脊奋,生成的代碼如下:

package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
}
  • 接著開始生成對(duì)應(yīng)的方法熬北,一個(gè)方法可以被Adaptive 注解修飾,也可以不被修飾诚隙。這里將未被Adaptive注解修飾的方法稱為“無 Adaptive注解方法”讶隐。Dubbo 不會(huì)為沒有標(biāo)注 Adaptive 注解的方法生成代理邏輯,對(duì)于該種類型的方法久又,僅會(huì)生成一句拋出異常的代碼巫延。生成邏輯如下:
for (Method method : methods) {
    
    // 省略無關(guān)邏輯

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    // 如果方法上無 Adaptive 注解效五,則生成 throw new UnsupportedOperationException(...) 代碼
    if (adaptiveAnnotation == null) {
        // 生成的代碼格式如下:
        // throw new UnsupportedOperationException(
        //     "method " + 方法簽名 + of interface + 全限定接口名 + is not adaptive method!”)
        code.append("throw new UnsupportedOperationException(\"method ")
            .append(method.toString()).append(" of interface ")
            .append(type.getName()).append(" is not adaptive method!\");");
    } else {
        // 省略無關(guān)邏輯
    }
    
    // 省略無關(guān)邏輯
}

以 dubbo 中的 com.alibaba.dubbo.rpc.Protocol接口的destroy方法為例,上面代碼生成的內(nèi)容如下:

    public void destroy() {
        throw new UnsupportedOperationException(
            "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

前面說過方法代理邏輯會(huì)從 URL 中提取目標(biāo)拓展的名稱炉峰,因此代碼生成邏輯的一個(gè)重要的任務(wù)是從方法的參數(shù)列表或者其他參數(shù)中獲取 URL 數(shù)據(jù)畏妖。舉例說明一下,我們要為 com.alibaba.dubbo.rpc.Protocol接口的referexport 方法生成代理邏輯疼阔。referexport 方法方法簽名如下:

    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    <T> Invoker<T> refer(Class<T> aClass, URL url) throws RpcException;

對(duì)于 refer 方法戒劫,通過遍歷 refer 的參數(shù)列表即可獲取 URL 數(shù)據(jù),這個(gè)還比較簡單婆廊。對(duì)于 export 方法迅细,獲取 URL 數(shù)據(jù)則要麻煩一些。export 參數(shù)列表中沒有 URL 參數(shù)淘邻,因此需要從 Invoker 參數(shù)中獲取 URL 數(shù)據(jù)茵典。獲取方式是調(diào)用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl宾舅。如果 Invoker 中無相關(guān) getter 方法统阿,此時(shí)則會(huì)拋出異常。整個(gè)邏輯如下:

for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // ${無 Adaptive 注解方法代碼生成邏輯}
    } else {
        int urlTypeIndex = -1;
        // 遍歷參數(shù)列表贴浙,確定 URL 參數(shù)位置
        for (int i = 0; i < pts.length; ++i) {
            if (pts[i].equals(URL.class)) {
                urlTypeIndex = i;
                break;
            }
        }
        
        // urlTypeIndex != -1砂吞,表示參數(shù)列表中存在 URL 參數(shù)
        if (urlTypeIndex != -1) {
            // 為 URL 類型參數(shù)生成判空代碼,格式如下:
            // if (arg + urlTypeIndex == null) 
            //     throw new IllegalArgumentException("url == null");
            String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                     urlTypeIndex);
            code.append(s);

            // 為 URL 類型參數(shù)生成賦值代碼弓熏,形如 URL url = arg1
            s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
            code.append(s);
            
        // 參數(shù)列表中不存在 URL 類型參數(shù)
        } else {
            String attribMethod = null;

            LBL_PTS:
            // 遍歷方法的參數(shù)類型列表
            for (int i = 0; i < pts.length; ++i) {
                // 獲取某一類型參數(shù)的全部方法
                Method[] ms = pts[i].getMethods();
                // 遍歷方法列表挟冠,尋找可返回 URL 的 getter 方法
                for (Method m : ms) {
                    String name = m.getName();
                    // 1. 方法名以 get 開頭,或方法名大于3個(gè)字符
                    // 2. 方法的訪問權(quán)限為 public
                    // 3. 非靜態(tài)方法
                    // 4. 方法參數(shù)數(shù)量為0
                    // 5. 方法返回值類型為 URL
                    if ((name.startsWith("get") || name.length() > 3)
                        && Modifier.isPublic(m.getModifiers())
                        && !Modifier.isStatic(m.getModifiers())
                        && m.getParameterTypes().length == 0
                        && m.getReturnType() == URL.class) {
                        urlTypeIndex = i;
                        attribMethod = name;
                        
                        // 結(jié)束 for (int i = 0; i < pts.length; ++i) 循環(huán)
                        break LBL_PTS;
                    }
                }
            }
            if (attribMethod == null) {
                // 如果所有參數(shù)中均不包含可返回 URL 的 getter 方法簿姨,則拋出異常
                throw new IllegalStateException("fail to create adaptive class for interface ...");
            }

            // 為可返回 URL 的參數(shù)生成判空代碼,格式如下:
            // if (arg + urlTypeIndex == null) 
            //     throw new IllegalArgumentException("參數(shù)全限定名 + argument == null");
            String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                     urlTypeIndex, pts[urlTypeIndex].getName());
            code.append(s);

            // 為 getter 方法返回的 URL 生成判空代碼膘怕,格式如下:
            // if (argN.getter方法名() == null) 
            //     throw new IllegalArgumentException(參數(shù)全限定名 + argument getUrl() == null);
            s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                              urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
            code.append(s);

            // 生成賦值語句捞烟,格式如下:
            // URL全限定名 url = argN.getter方法名(),比如 
            // com.alibaba.dubbo.common.URL url = invoker.getUrl();
            s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
            code.append(s);
        }
        
        // 省略無關(guān)代碼
    }
    
    // 省略無關(guān)代碼
}

這段代碼主要目的是為了獲取 URL 數(shù)據(jù)王悍,并為之生成判空和賦值代碼破镰。以Protocol 的 refer 和 export 方法為例,上面的代碼為它們生成如下內(nèi)容(代碼已格式化):
refer:

        if (arg1 == null) {
            throw new IllegalArgumentException("url == null");
        }

        com.alibaba.dubbo.common.URL url = arg1;

export:

        if (arg0 == null) {
            throw new IllegalArgumentException(
                "com.alibaba.dubbo.rpc.Invoker argument == null");
        }

        if (arg0.getUrl() == null) {
            throw new IllegalArgumentException(
                "com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        }

        com.alibaba.dubbo.common.URL url = arg0.getUrl();
  • 接下來獲取 Adaptive注解值压储,Adaptive 注解值 value 類型為 String[]鲜漩,可填寫多個(gè)值,默認(rèn)情況下為空數(shù)組集惋。若 value 為非空數(shù)組孕似,直接獲取數(shù)組內(nèi)容即可。若 value 為空數(shù)組刮刑,則需進(jìn)行額外處理喉祭。處理過程是將類名轉(zhuǎn)換為字符數(shù)組养渴,然后遍歷字符數(shù)組,并將字符放入 StringBuilder 中泛烙。若字符為大寫字母理卑,則向 StringBuilder 中添加點(diǎn)號(hào),隨后將字符變?yōu)樾懘嫒?StringBuilder 中蔽氨。比如 LoadBalance 經(jīng)過處理后傻工,得到 load.balance。
for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // ${無 Adaptive 注解方法代碼生成邏輯}
    } else {
        // ${獲取 URL 數(shù)據(jù)}
        
        String[] value = adaptiveAnnotation.value();
        // value 為空數(shù)組
        if (value.length == 0) {
            // 獲取類名孵滞,并將類名轉(zhuǎn)換為字符數(shù)組
            String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
            value = new String[]{splitName};
        }
        
        // 省略無關(guān)代碼
    }
    
    // 省略無關(guān)邏輯
}
  • 檢測 Invocation 參數(shù)中捆,此段邏輯是檢測方法列表中是否存在 Invocation 類型的參數(shù),若存在坊饶,則為其生成判空代碼和其他一些代碼泄伪。相應(yīng)的邏輯如下:
for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();    // 獲取參數(shù)類型列表
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // ${無 Adaptive 注解方法代碼生成邏輯}
    } else {
        // ${獲取 URL 數(shù)據(jù)}
        
        // ${獲取 Adaptive 注解值}
        
        boolean hasInvocation = false;
        // 遍歷參數(shù)類型列表
        for (int i = 0; i < pts.length; ++i) {
            // 判斷當(dāng)前參數(shù)名稱是否等于 com.alibaba.dubbo.rpc.Invocation
            if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                // 為 Invocation 類型參數(shù)生成判空代碼
                String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                code.append(s);
                // 生成 getMethodName 方法調(diào)用代碼,格式為:
                //    String methodName = argN.getMethodName();
                s = String.format("\nString methodName = arg%d.getMethodName();", i);
                code.append(s);
                
                // 設(shè)置 hasInvocation 為 true
                hasInvocation = true;
                break;
            }
        }
    }
    
    // 省略無關(guān)邏輯
}

生成的代碼示例為:

        if (argN == null) {
            throw new IllegalArgumentException("invocation == null");
        }

        String methodName = argN.getMethodName();
  • 生成拓展名獲取邏輯匿级,下面代碼是根據(jù) SPI 和 Adaptive 注解值生成“獲取拓展名邏輯”蟋滴,同時(shí)生成邏輯也受 Invocation 類型參數(shù)影響,綜合因素導(dǎo)致本段邏輯相對(duì)復(fù)雜痘绎。
for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // $無 Adaptive 注解方法代碼生成邏輯}
    } else {
        // ${獲取 URL 數(shù)據(jù)}
        
        // ${獲取 Adaptive 注解值}
        
        // ${檢測 Invocation 參數(shù)}
        
        // 設(shè)置默認(rèn)拓展名津函,cachedDefaultName 源于 SPI 注解值,默認(rèn)情況下孤页,
        // SPI 注解值為空串尔苦,此時(shí) cachedDefaultName = null
        String defaultExtName = cachedDefaultName;
        String getNameCode = null;
        
        // 遍歷 value,這里的 value 是 Adaptive 的注解值行施,2.2.3.3 節(jié)分析過 value 變量的獲取過程允坚。
        // 此處循環(huán)目的是生成從 URL 中獲取拓展名的代碼,生成的代碼會(huì)賦值給 getNameCode 變量蛾号。注意這
        // 個(gè)循環(huán)的遍歷順序是由后向前遍歷的稠项。
        for (int i = value.length - 1; i >= 0; --i) {
            // 當(dāng) i 為最后一個(gè)元素的坐標(biāo)時(shí)
            if (i == value.length - 1) {
                // 默認(rèn)拓展名非空
                if (null != defaultExtName) {
                    // protocol 是 url 的一部分,可通過 getProtocol 方法獲取鲜结,其他的則是從
                    // URL 參數(shù)中獲取展运。因?yàn)楂@取方式不同,所以這里要判斷 value[i] 是否為 protocol
                    if (!"protocol".equals(value[i]))
                        // hasInvocation 用于標(biāo)識(shí)方法參數(shù)列表中是否有 Invocation 類型參數(shù)
                        if (hasInvocation)
                            // 生成的代碼功能等價(jià)于下面的代碼:
                            //   url.getMethodParameter(methodName, value[i], defaultExtName)
                            // 以 LoadBalance 接口的 select 方法為例精刷,最終生成的代碼如下:
                            //   url.getMethodParameter(methodName, "loadbalance", "random")
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        else
                            // 生成的代碼功能等價(jià)于下面的代碼:
                            //   url.getParameter(value[i], defaultExtName)
                            getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    else
                        // 生成的代碼功能等價(jià)于下面的代碼:
                        //   ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
                        getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    
                // 默認(rèn)拓展名為空
                } else {
                    if (!"protocol".equals(value[i]))
                        if (hasInvocation)
                            // 生成代碼格式同上
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        else
                            // 生成的代碼功能等價(jià)于下面的代碼:
                            //   url.getParameter(value[i])
                            getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                    else
                        // 生成從 url 中獲取協(xié)議的代碼拗胜,比如 "dubbo"
                        getNameCode = "url.getProtocol()";
                }
            } else {
                if (!"protocol".equals(value[i]))
                    if (hasInvocation)
                        // 生成代碼格式同上
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    else
                        // 生成的代碼功能等價(jià)于下面的代碼:
                        //   url.getParameter(value[i], getNameCode)
                        // 以 Transporter 接口的 connect 方法為例,最終生成的代碼如下:
                        //   url.getParameter("client", url.getParameter("transporter", "netty"))
                        getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                else
                    // 生成的代碼功能等價(jià)于下面的代碼:
                    //   url.getProtocol() == null ? getNameCode : url.getProtocol()
                    // 以 Protocol 接口的 connect 方法為例贬养,最終生成的代碼如下:
                    //   url.getProtocol() == null ? "dubbo" : url.getProtocol()
                    getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
            }
        }
        // 生成 extName 賦值代碼
        code.append("\nString extName = ").append(getNameCode).append(";");
        // 生成 extName 判空代碼
        String s = String.format("\nif(extName == null) " +
                                 "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                                 type.getName(), Arrays.toString(value));
        code.append(s);
    }
    
    // 省略無關(guān)邏輯
}

上面代碼比較復(fù)雜挤土,不是很好理解。對(duì)于這段代碼误算,建議大家寫點(diǎn)測試用例仰美,對(duì) Protocol迷殿、LoadBalance 以及 Transporter 等接口的自適應(yīng)拓展類代碼生成過程進(jìn)行調(diào)試庆寺。這里我以 Transporter 接口的自適應(yīng)拓展類代碼生成過程舉例說明。首先看一下 Transporter 接口的定義,如下:

@SPI("netty")
public interface Transporter {
    // @Adaptive({server, transporter})
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) 
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    // @Adaptive({client, transporter})
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

下面對(duì) connect 方法代理邏輯生成的過程進(jìn)行分析,此時(shí)生成代理邏輯所用到的變量如下:

String defaultExtName = "netty";
boolean hasInvocation = false;
String getNameCode = null;
String[] value = ["client", "transporter"];

根據(jù)上面的代碼邏輯,對(duì) value 數(shù)組進(jìn)行遍歷剩檀,此時(shí) i = 1, value[i] = "transporter",生成的代碼如下:

getNameCode = url.getParameter("transporter", "netty");

接下來字币,for 循環(huán)繼續(xù)執(zhí)行士复,此時(shí) i = 0, value[i] = "client",生成的代碼如下:

getNameCode = url.getParameter("client", url.getParameter("transporter", "netty"));

for 循環(huán)結(jié)束運(yùn)行便贵,現(xiàn)在為 extName 變量生成賦值和判空代碼冗荸,如下:

String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null) {
    throw new IllegalStateException(
        "Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString()
        + ") use keys([client, transporter])");
}
  • 生成拓展加載與目標(biāo)方法調(diào)用邏輯蚌本,下面代碼邏輯用于根據(jù)拓展名加載拓展實(shí)例轴猎,并調(diào)用拓展實(shí)例的目標(biāo)方法捻脖。
        // 生成拓展獲取代碼,格式如下:
        // type全限定名 extension = (type全限定名)ExtensionLoader全限定名
        //     .getExtensionLoader(type全限定名.class).getExtension(extName);
        // Tips: 格式化字符串中的 %<s 表示使用前一個(gè)轉(zhuǎn)換符所描述的參數(shù)矛渴,即 type 全限定名
        s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
        code.append(s);

        // 如果方法返回值類型非 void晶府,則生成 return 語句剂习。
        if (!rt.equals(void.class)) {
            code.append("\nreturn ");
        }

        // 生成目標(biāo)方法調(diào)用邏輯鳞绕,格式為:
        //     extension.方法名(arg0, arg2, ..., argN);
        s = String.format("extension.%s(", method.getName());
        code.append(s);
        for (int i = 0; i < pts.length; i++) {
            if (i != 0)
                code.append(", ");
            code.append("arg").append(i);
        }
        code.append(");");   
    }

以 Protocol 接口舉例說明,上面代碼生成的內(nèi)容如下:

com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
    .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
  • 生成完整的方法茬射,下面代碼生成的收尾工作钟病,主要用于生成方法定義的代碼票唆。相關(guān)邏輯如下:
// public + 返回值全限定名 + 方法名 + (
codeBuilder.append("\npublic ")
    .append(rt.getCanonicalName())
    .append(" ")
    .append(method.getName())
    .append("(");

// 添加參數(shù)列表代碼
for (int i = 0; i < pts.length; i++) {
    if (i > 0) {
        codeBuilder.append(", ");
    }
    codeBuilder.append(pts[i].getCanonicalName());
    codeBuilder.append(" ");
    codeBuilder.append("arg").append(i);
}
codeBuilder.append(")");

// 添加異常拋出代碼
if (ets.length > 0) {
    codeBuilder.append(" throws ");
    for (int i = 0; i < ets.length; i++) {
        if (i > 0) {
            codeBuilder.append(", ");
        }
        codeBuilder.append(ets[i].getCanonicalName());
    }
}
codeBuilder.append(" {");
codeBuilder.append(code.toString());
codeBuilder.append("\n}");

以 Protocol 的 refer 方法為例缘回,上面代碼生成的內(nèi)容如下:

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
    // 方法體
}
  1. 關(guān)于自適應(yīng)拓展的原理啦吧,實(shí)現(xiàn)就分析完了肆糕』此ぃ總的來說自適應(yīng)拓展整個(gè)邏輯還是很復(fù)雜的造垛,并不是很容易弄懂办斑。因此髓迎,大家在閱讀該部分源碼時(shí),耐心一些。同時(shí)多進(jìn)行調(diào)試凛忿,也可以通過生成好的代碼思考代碼的生成邏輯委乌。

下面貼出上篇中的AdaptiveExt接口生成的自適應(yīng)類的代碼:

package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
  
    public java.lang.String echo(java.lang.String arg0,
        com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) {
            throw new IllegalArgumentException("url == null");
        }

        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("myAdaptiveName");

        if (extName == null) {
            throw new IllegalStateException(
                "Fail to get extension(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) name from url(" +
                url.toString() + ") use keys([myAdaptiveName])");
        }

        com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt extension = (com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) ExtensionLoader.getExtensionLoader(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt.class)
                                                                                                                                             
        return extension.echo(arg0, arg1);
    }
}

看到這個(gè)生成的代碼是不是和上篇寫的AdaptiveExtProxy大致差不多著蛙。

參考示例

  1. 編寫一個(gè)測試樣例效拭,代碼如下挤渔,用于生成com.alibaba.dubbo.remoting.Transportercom.alibaba.dubbo.rpc.Protocol自適應(yīng)類。
    @Test
    public void testAdaptive() throws Exception{
        ExtensionLoader<Transporter> transporterExtensionLoader = ExtensionLoader.getExtensionLoader(Transporter.class);
        transporterExtensionLoader.getAdaptiveExtension();

        ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
        extensionLoader.getAdaptiveExtension();
    }
  1. 這里給大家推薦一個(gè)阿里的開源工具擂红,Arthas,直接將運(yùn)行的類反編譯成Java文件赎婚。

下面我貼出com.alibaba.dubbo.remoting.Transportercom.alibaba.dubbo.rpc.Protocol自適應(yīng)類的代碼:


public class Protocol$Adaptive implements Protocol {
    @Override
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    @Override
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public Invoker refer(Class class_, URL uRL) throws RpcException {
        String string;
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL2.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.refer(class_, uRL);
    }

    public Exporter export(Invoker invoker) throws RpcException {
        String string;
        if (invoker == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.export(invoker);
    }
}

public class Transporter$Adaptive implements Transporter {
    @Override
    public Client connect(URL uRL, ChannelHandler channelHandler) throws RemotingException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string = uRL2.getParameter("client", uRL2.getParameter("transporter", "netty"));
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([client, transporter])").toString());
        }
        Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
        return transporter.connect(uRL, channelHandler);
    }

    @Override
    public Server bind(URL uRL, ChannelHandler channelHandler) throws RemotingException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string = uRL2.getParameter("server", uRL2.getParameter("transporter", "netty"));
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([server, transporter])").toString());
        }
        Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
        return transporter.bind(uRL, channelHandler);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歧焦,一起剝皮案震驚了整個(gè)濱河市移斩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绢馍,老刑警劉巖向瓷,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舰涌,居然都是意外死亡猖任,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門瓷耙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朱躺,“玉大人,你說我怎么就攤上這事搁痛〕げ螅” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鸡典,是天一觀的道長源请。 經(jīng)常有香客問我,道長彻况,這世上最難降的妖魔是什么谁尸? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮纽甘,結(jié)果婚禮上良蛮,老公的妹妹穿的比我還像新娘。我一直安慰自己悍赢,他們只是感情好背镇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布咬展。 她就那樣靜靜地躺著,像睡著了一般瞒斩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涮总,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天胸囱,我揣著相機(jī)與錄音,去河邊找鬼瀑梗。 笑死烹笔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抛丽。 我是一名探鬼主播谤职,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亿鲜!你這毒婦竟也來了允蜈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤蒿柳,失蹤者是張志新(化名)和其女友劉穎饶套,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垒探,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妓蛮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了圾叼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛤克。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖夷蚊,靈堂內(nèi)的尸體忽然破棺而出构挤,到底是詐尸還是另有隱情,我是刑警寧澤撬码,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布儿倒,位于F島的核電站,受9級(jí)特大地震影響呜笑,放射性物質(zhì)發(fā)生泄漏夫否。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一叫胁、第九天 我趴在偏房一處隱蔽的房頂上張望凰慈。 院中可真熱鬧,春花似錦驼鹅、人聲如沸微谓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豺型。三九已至仲智,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姻氨,已是汗流浹背钓辆。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肴焊,地道東北人前联。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像娶眷,于是被迫代替她去往敵國和親似嗤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容