天天看點

dubbo源碼系列11-自适應拓展機制 Adaptive

一、前沿

調試 dubbo 源碼時,相信大家經常會遇到這樣一個問題,即調用 Protocol、Cluster、ProxyFactory、LoadBalance(這些類中都有 SPI 注解) 中定義的帶有 Adaptive 注解的方法 時總是按照以下流程調用:

步驟一: 調用 url 的 getXXX 方法擷取參數值

步驟二: 調用 ExtensionLoader 的 getExtensionLoader 擷取加載器

步驟三: 調用 ExtensionLoader 的 getExtension 根據從url擷取的參數作為類名稱加載實作類

如下圖所示:

dubbo源碼系列11-自适應拓展機制 Adaptive
dubbo源碼系列11-自适應拓展機制 Adaptive
dubbo源碼系列11-自适應拓展機制 Adaptive

debug 調試圖中可以,三個步驟的調用有一個共同點,就是具體的調用方法都是從 Protocol$Adaptive 這個類中調用的,這樣的類是随着定義類的執行個體化時加載的,如下圖:

dubbo源碼系列11-自适應拓展機制 Adaptive

ReferenceConfig 執行個體化時,Protocol、Cluster、ProxyFactory 分别會生成 Protocol$Adaptive、Cluster$Adaptive、ProxyFactory $Adaptive  代理類

下面開始介紹 dubbo 中的自适應拓展機制

二、自适應拓展機制原理

我們知道在 Dubbo 中,很多拓展都是通過 SPI 機制 進行加載的,比如 Protocol、Cluster、LoadBalance、ProxyFactory 等。有時,有些拓展并不想在架構啟動階段被加載,而是希望在拓展方法被調用時,根據運作時參數進行加載,即根據參數動态加載實作類。這聽起來有些沖突。拓展未被加載,那麼拓展方法就無法被調用(靜态方法除外)。拓展方法未被調用,拓展就無法被加載的。對于這個沖突的問題,Dubbo 通過自适應拓展機制很好的解決了

原理如下:

1)、Dubbo 會為拓展接口生成具有代理功能的代碼

2)、通過 javassist 或 jdk 編譯這段代碼,得到 Class 類

3)、通過反射建立代理類

自适應拓展機制的實作邏輯比較複雜,為了讓大家對自适應拓展有一個感性的認識,下面我們借用官網的一個示例進行示範。這是一個與汽車相關的例子,我們有一個車輪制造廠接口 WheelMaker:

public interface WheelMaker {
    Wheel makeWheel(URL url);
}


// WheelMaker 接口的自适應實作類
public class AdaptiveWheelMaker implements WheelMaker {
    public Wheel makeWheel(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        
    	// 1.從 URL 中擷取 WheelMaker 名稱
        String wheelMakerName = url.getParameter("Wheel.maker");
        if (wheelMakerName == null) {
            throw new IllegalArgumentException("wheelMakerName == null");
        }
        
        // 2.通過 SPI 加載具體的 WheelMaker
        WheelMaker wheelMaker = ExtensionLoader
            .getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);
        
        // 3.調用目标方法
        return wheelMaker.makeWheel(URL url);
    }
}
           

AdaptiveWheelMaker 是一個代理類,類似 Protocol$Adaptive,與傳統的代理邏輯不同,AdaptiveWheelMaker 所代理的對象是在 makeWheel 方法中通過 SPI 加載得到的。makeWheel 方法主要做了三件事情:

1)、從 URL 中擷取 WheelMaker 名稱

2)、通過 SPI 加載具體的 WheelMaker 實作類

3)、調用實作類的目标方法

接下來,我們來看看汽車制造廠 CarMaker 接口與其實作類,代碼如下:

public interface CarMaker {
    Car makeCar(URL url);
}

public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;
 
    // 通過 setter 注入 AdaptiveWheelMaker
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
 
    public Car makeCar(URL url) {
        Wheel wheel = wheelMaker.makeWheel(url);
        return new RaceCar(wheel, ...);
    }
}
           

RaceCarMaker 持有一個 WheelMaker 類型的成員變量,在程式啟動時,我們可以将 AdaptiveWheelMaker 通過 setter 方法注入到 RaceCarMaker 中。在運作時,假設有這樣一個 url 參數傳入:

dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker
           

RaceCarMaker 的 makeCar 方法将上面的 url 作為參數傳給 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法從 url 中提取 wheel.maker 參數,得到 MichelinWheelMaker。之後再通過 SPI 加載配置名為 MichelinWheelMaker 的實作類,得到具體的 WheelMaker 執行個體。

上面的示例展示了自适應拓展類的核心實作 ---- 在拓展接口的方法被調用時,通過 SPI 加載具體的拓展實作類,并調用拓展對象的同名方法。

接下來,我們深入到源碼中,探究自适應拓展類生成的過程

三、源碼

在了解自适應拓展源碼之前,我們必須先了解一個自定義注解 Adaptive,隻有帶有 Adaptive 注解的方法或者類才能使用自适應拓展機制,自定義注解 Adaptive 源碼如下:

/**
 * Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
 *
 * @see ExtensionLoader
 * @see URL
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
     * in the URL, and the parameter names are given by this method.
     * <p>
     * If the specified parameters are not found from {@link URL}, then the default extension will be used for
     * dependency injection (specified in its interface's {@link SPI}).
     * <p>
     * For example, given <code>String[] {"key1", "key2"}</code>:
     * <ol>
     * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
     * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
     * <li>use default extension if 'key2' doesn't exist either</li>
     * <li>otherwise, throw {@link IllegalStateException}</li>
     * </ol>
     * If the parameter names are empty, then a default parameter name is generated from interface's
     * class name with the rule: divide classname from capital char into several parts, and separate the parts with
     * dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
     * <code>String[] {"yyy.invoker.wrapper"}</code>.
     *
     * @return parameter names in URL
     */
    String[] value() default {};

}
           

從源碼中得知 Adaptive 可注解在類或方法上,Adaptive 注解在類上或者方法上有不同的實作邏輯,如下:

1)、Adaptive 注解在類上時,Dubbo 不會為該類生成代理類,Adaptive 注解在類上的情況很少,在 Dubbo 中,僅有兩個類被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加載邏輯由人工編碼完成

2)、Adaptive 注解在方法上時,Dubbo 則會為該方法生成代理邏輯,表示拓展的加載邏輯需由架構自動生成

下面正式開始分析自适應拓展源碼,探究 XXX$Adaptive 代理類的生成過程,下面分為兩個部分分析:3.1 擷取自适應拓展  3.2 自适應拓展類生成代碼

3.1 擷取自适應拓展

在前言中可知,擷取自适應拓展是從 ExtensionLoader 的 getAdaptiveExtension 方法開始的,源碼如下:

// 1、ExtensionLoader 的 getAdaptiveExtension 方法
    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        // 先從緩存中擷取自适應拓展執行個體
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                // 雙重校驗鎖方式擷取自适應拓展執行個體
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 調用 ExtensionLoader 的 createAdaptiveExtension 方法建立自适應拓展執行個體
                            instance = createAdaptiveExtension();
                            // 自适應拓展執行個體設定到緩存中
                            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);
            }
        }

        return (T) instance;
    }


    // 2、ExtensionLoader 的 createAdaptiveExtension 方法
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            // 先調用 ExtensionLoader 的 getAdaptiveExtensionClass 方法擷取自适應拓展類,然後通過反射執行個體化,最後向拓展執行個體中注入依賴
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

    // 3、ExtensionLoader 的 getAdaptiveExtensionClass 方法
    private Class<?> getAdaptiveExtensionClass() {
        // 通過 SPI 擷取所有的拓展類
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            // 若緩存中有拓展類執行個體,則直接傳回緩存
            return cachedAdaptiveClass;
        }
        // 調用 ExtensionLoader 的 createAdaptiveExtensionClass 方法建立自适應拓展類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    // 4、ExtensionLoader 的 createAdaptiveExtensionClass 方法
    private Class<?> createAdaptiveExtensionClass() {
        // 建立自适應拓展類的代碼
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        // 擷取類加載器
        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);
    }
           

擷取自适應拓展過程并不是特别複雜,但是有以下問題需要注意:

1)、Dubbo 中有兩種類型的自适應拓展,一種是手工編碼的,一種是自動生成的。手工編碼的自适應拓展中可能存在着一些依賴,而自動生成的 Adaptive 拓展則不會依賴其他類。這裡調用 injectExtension 方法的目的是為手工編碼的自适應拓展注入依賴

2)、getExtensionClasses 在 SPI 機制 中我們已經分析過了,這裡不在贅述。通過 SPI 擷取所有的拓展類,比如該方法可以擷取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等實作類。在擷取實作類的過程中,如果某個實作類被 Adaptive 注解了,那麼該類就會被指派給 cachedAdaptiveClass 變量,如果所有的類都沒有被 Adaptive 注解,則建立自适應拓展類

3.2  自适應拓展類生成代碼

從 3.1 擷取自适應拓展 中可知建立自适應拓展類的代碼從 AdaptiveClassCodeGenerator 類的 generate 方法開始,源碼如下:

/**
     * generate and return class code
     */
    public String generate() {
        // no need to generate adaptive class since there's no adaptive method found.
        // 1、檢查接口方法 adaptive 注解
        // 檢查接口的所有方法上是否有 adaptive 注解,如果所有方法都沒有 adaptive 注解的話,抛出異常
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        // 2、生成類代碼
        StringBuilder code = new StringBuilder();
        // 生成 package 代碼 ,例如:package + type 所在包名
        code.append(generatePackageInfo());
        // 生成 import 代碼 ,例如:import + ExtensionLoader 全限定名
        code.append(generateImports());
        // 生成 類 代碼 ,例如:public class + type簡單名稱 + $Adaptive implements + type全限定名 + {
        code.append(generateClassDeclaration());

        // 3、生成方法代碼
        // 擷取接口的所有方法
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }
           

建立自适應拓展類的代碼分為了三步:1、檢查接口方法的 Adaptive 注解,2、生成類代碼, 3、為接口所有方法生成方法代碼

1 和 2 的邏輯很簡單,這裡不在展開分析,我們看一下 2 中生成的類代碼以 Protocol 接口為例,如下:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    // 省略方法代碼
}
           

下面重點分析 3,即 生成方法代碼過程

3.2.1 生成方法代碼

上述代碼可知生成方法代碼邏輯在 AdaptiveClassCodeGenerator 的 generateMethod 方法中,源碼如下:

/**
     * generate method declaration
     */
    private String generateMethod(Method method) {
        // 擷取方法傳回類型
        String methodReturnType = method.getReturnType().getCanonicalName();
        // 擷取方法名
        String methodName = method.getName();
        // 擷取方法體内容
        String methodContent = generateMethodContent(method);
        // 擷取方法參數清單,格式代碼:參數全限定名 arg0,參數全限定名 arg1,參數全限定名 arg2......
        String methodArgs = generateMethodArguments(method);
        // 擷取方法抛出的異常清單,格式代碼:throws Exception1,Exception2......
        String methodThrows = generateMethodThrows(method);
        // 生成完整方法代碼,代碼格式:
        // public methodReturnType methodName(methodArgs) methodThrows { methodContent };
        // 以 Protocol 的 refer 方法為例,代碼:
        // public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        //    // 方法體
        //}
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
    }


    /**
     * generate method content
     */
    // 生成方法體内容
    private String generateMethodContent(Method method) {
        // 從方法上擷取 Adaptive 注解
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            // 無 Adaptive 注解的方法,則生成 throw new UnsupportedOperationException("The method +方法名+ of interface +全限定接口名+ is not adaptive method!"); 代碼
            // 以 Protocol 的 destroy 方法為例,将會生成如下代碼:
            // throw new UnsupportedOperationException("The method org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
            return generateUnsupported(method);
        } else {
            // 确定 URL 參數位置,之是以做這個是因為動态加載拓展類的名稱參數都是從 URL 中擷取來的
            int urlTypeIndex = getUrlTypeIndex(method);

            // found parameter in URL type
            // 存在 URL 參數
            if (urlTypeIndex != -1) {
                // Null Point check
                // 為 URL 參數生成判空代碼,例如:
                // if (arg + urlTypeIndex == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg + urlTypeIndex;
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // did not find parameter in URL type
                // 不存在 URL 參數
                // 從可以傳回 URL 對象的參數中擷取 URL生成代碼
                code.append(generateUrlAssignmentIndirectly(method));
            }

            // 擷取 Adaptive 注解值
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            // 檢測方法參數中是否存在 Invocation 類型的參數
            boolean hasInvocation = hasInvocationArgument(method);

            // 為 Invocation 類型的參數生成判空代碼 和 生成 getMethodName 方法調用代碼
            // if (argN == null) throw new IllegalArgumentException(\"invocation == null\");  String methodName = argN.getMethodName();\n
            code.append(generateInvocationArgumentNullCheck(method));

            // 根據 SPI 和 Adaptive 注解值生成拓展名代碼
            code.append(generateExtNameAssignment(value, hasInvocation));
            // check extName == null?
            // 為 extName 生成判空代碼,如下:
            // if(extName == null) throw new IllegalStateException(\"Failed to get extension +全限定接口名+ name from url (\" + url.toString() + \") use keys(Adaptive 注解值)\");\n
            code.append(generateExtNameNullCheck(value));

            // 生成拓展類加載代碼,格式代碼:type全限定名+ extension = ((type全限定名))ExtensionLoader全限定名.getExtensionLoader(type全限定名.class).getExtension(extName);
            // 例如:org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster)org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
            code.append(generateExtensionAssignment());

            // return statement
            // 生成 return 語句和調用目标方法邏輯代碼
            code.append(generateReturnAndInvocation(method));
        }

        return code.toString();
    }


    /**
     * get parameter with type <code>URL</code> from method parameter:
     * <p>
     * test if parameter has method which returns type <code>URL</code>
     * <p>
     * if not found, throws IllegalStateException
     */
    // 從可以傳回 URL 對象的參數中擷取 URL生成代碼
    private String generateUrlAssignmentIndirectly(Method method) {
        // 擷取方法的所有參數
        Class<?>[] pts = method.getParameterTypes();

        // find URL getter method
        for (int i = 0; i < pts.length; ++i) {
            // 周遊參數的所有的方法,找出 getUrl 方法
            for (Method m : pts[i].getMethods()) {
                String name = m.getName();
                // 1、方法名是 get 開頭的
                // 2、方法名長度 > 3
                // 3、方法是 public 類型
                // 4、方法是非 static 類型
                // 5、方法沒有參數
                // 6、方法傳回值類型是 URL 類型
                // 例如:Invoker 的 getUrl 方法 就符合要求
                if ((name.startsWith("get") || name.length() > 3)
                        && Modifier.isPublic(m.getModifiers())
                        && !Modifier.isStatic(m.getModifiers())
                        && m.getParameterTypes().length == 0
                        && m.getReturnType() == URL.class) {
                    // 生成參數判空、參數的getUrl方法判空以及指派語句代碼
                    return generateGetUrlNullCheck(i, pts[i], name);
                }
            }
        }

        // getter method not found, throw
        // getUrl 方法沒有找到的話,抛出異常
        throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
                        + ": not found url parameter or url attribute in parameters of method " + method.getName());

    }


    /**
     * 1, test if argi is null
     * 2, test if argi.getXX() returns null
     * 3, assign url with argi.getXX()
     */
    // 生成參數判空、參數的getUrl方法判空以及指派語句代碼
    private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
        // Null point check
        StringBuilder code = new StringBuilder();
        // 為可傳回 URL 的參數生成判空代碼,例如:
        // if (argN == null) throw new IllegalArgumentException("參數全限定名 + argument == null");
        code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
                index, type.getName()));
        // 為參數的getUrl方法生成判空代碼,例如:
        // if (argN.getUrl() == null) throw new IllegalArgumentException("參數全限定名 + argument + getUrl() == null");
        code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
                index, method, type.getName(), method));

        // 生成指派語句,格式:URL全限定名 url = argN.getUrl()
        // 例如: com.alibaba.dubbo.common.URL url = invoker.getUrl();
        code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
        return code.toString();
    }


    /**
     * get value of adaptive annotation or if empty return splitted simple name
     */
    // 擷取 Adaptive 注解值
    private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
        // 擷取 Adaptive 注解值數組
        String[] value = adaptiveAnnotation.value();
        // value is not set, use the value generated from class name as the key
        // 數組為空是,使用類名生成注解值
        if (value.length == 0) {
            // 周遊類名字元,如果字元是大寫字母,字元轉成小寫字母,若該字元不是第一個字元的話,則先向StringBuilder中添加".",
            // 然後将轉成的小寫字母添加到StringBuilder中,否則的話直接将字元添加到StringBuilder中
            // 例如:LoadBalance 經過處理後,得到 load.balance
            String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
            value = new String[]{splitName};
        }
        return value;
    }


    /**
     * generate extName assigment code
     */
    // 根據 SPI 和 Adaptive 注解值生成拓展名代碼
    private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
        // TODO: refactor it
        String getNameCode = null;
        // 周遊 Adaptive 的注解值,此處循環目的是生成從 URL 中擷取拓展名的代碼,生成的代碼會指派給 getNameCode 變量。
        // 注意這個循環的周遊順序是由後向前周遊的
        for (int i = value.length - 1; i >= 0; --i) {
            if (i == value.length - 1) {
                // defaultExtName 就是 SPI 注解值, SPI 注解值存在時
                if (null != defaultExtName) {
                    // protocol 是 url 的一部分,可通過 getProtocol 方法擷取,其他的則是從
                    // URL 參數中擷取。因為擷取方式不同,是以這裡要判斷 value[i] 是否為 protocol
                    if (!"protocol".equals(value[i])) {
                        // 方法參數清單中是否有 Invocation 類型的參數
                        if (hasInvocation) {
                            // 有 Invocation 類型的參數,生成如下代碼:
                            // 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 {
                            // 無 Invocation 類型的參數,生成如下代碼:
                            // url.getParameter(methodName, value[i], defaultExtName)
                            getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                        }
                    } else {
                        // 生成如下代碼:
                        // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
                        getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    }
                } else {
                    // SPI 注解值不存在時,即預設拓展名沒有
                    if (!"protocol".equals(value[i])) {
                        if (hasInvocation) {
                            // 生成代碼格式同上
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        } else {
                            // 生成代碼:url.getParameter(value[i])
                            getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                        }
                    } else {
                        // 生成代碼:url.getProtocol()
                        getNameCode = "url.getProtocol()";
                    }
                }
            } else {
                if (!"protocol".equals(value[i])) {
                    if (hasInvocation) {
                        // 生成代碼格式同上
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                        // 生成代碼: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 {
                    // 生成代碼: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 變量指派語句,代碼:String extName = getNameCode;
        return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
    }


    /**
     * generate method invocation statement and return it if necessary
     */
    // 生成 return 語句和調用目标方法邏輯代碼
    private String generateReturnAndInvocation(Method method) {
        // 建立傳回語句,void 類型是不用傳回
        String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";

        // 生成目标方法的參數清單,代碼:arg0,arg1,arg2......
        String args = IntStream.range(0, method.getParameters().length)
                .mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
                .collect(Collectors.joining(", "));

        // 生成目标方法調用代碼:return extension.方法名(arg0,arg1,arg2......);
        // 以 Protocol 的 refer 方法為例,代碼:return extension.refer(Class<T> type, URL url);
        return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
    }
           

生成方法代碼的源碼比較多,邏輯也相對來說複雜一些,配合代碼中的注釋和例子,相信大家不難了解整個過程。

以 Protocol 接口為例,下面給大家展示一下生成的 Protocol$Adaptive 的源碼,如下:

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of " +
                "interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of " +
                "interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name " +
                    "from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.
                getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from " +
                    "url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.
                getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}
           

結合生成的 Protocol$Adaptive 的源碼,單元測試debug一遍流程,你就可以完全了解上面生成拓展類代碼的源碼了

到這裡dubbo中的自适應拓展就講解完了,以調用 Protocol 的接口的 ref 方法為例,下面給大家看一下自适應拓展的整個過程:

1)、先通過 Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 生成了 Protocol$Adaptive 代理類

2)、傳參 Url 為:dubbo://192.168.1.247:20887/org.apache.dubbo.config.spring.api.DemoService,調用 Protocol 的 refer 方法,此時直接調用是 Protocol$Adaptive 代理類的 refer 方法

3)、在 Protocol$Adaptive 的 ref 方法中先調用 url 中的 getProtocol() 方法擷取拓展類名稱,指派給 extName 變量

4)、然後調用 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 語句擷取到具體的實作類的執行個體

5)、最後執行 extension.refer(arg0, arg1) 語句,調用 4 中擷取到的具體實作類的 ref 方法,最終傳回結果

四、總結

dubbo中的自适應拓展很好地實作了根據參數動态加載實作類,思想很贊,在閱讀源碼的時候,有些地方還是比較晦澀,希望大家多使用源碼的單元測試來debug調試,這樣子就對整個流程很清楚了。本文中有不正确的地方,請大家指正,謝謝

參考:

https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html

繼續閱讀