ダボソースコードシリーズ11-アダプティブ拡張メカニズムアダプティブ

1.フロンティア

ダボソースコードをデバッグするとき、このような問題が頻繁に発生すると思います。つまりProtocol、Cluster、ProxyFactory、LoadBalance (これらのクラスにはSPIアノテーションがあります)で定義されAdaptiveアノテーション付きメソッドを 呼び出すと、常に次のように呼び出されます。次のプロセス:

ステップ1:URLのgetXXXメソッドを呼び出して、パラメーター値を取得します

ステップ2:ExtensionLoaderのgetExtensionLoaderを呼び出して、ローダーを取得します

ステップ3:ExtensionLoaderのgetExtensionを呼び出して、クラス名としてURLから取得したパラメーターに従って実装クラスをロードします

以下に示すように:

debugデバッグ図はOKです。呼び出しの3つのステップには、1つの共通点があります。つまり、特定の呼び出しメソッドがProtocol $ Adaptiveクラスから呼び出されます。このようなクラスは、定義されたクラスがインスタンス化されるときにロードされます。次の図。:

ReferenceConfigがインスタンス化されると、Protocol、Cluster、ProxyFactoryはそれぞれProtocol $ Adaptive、Cluster $ Adaptive、ProxyFactory $ Adaptiveプロキシクラスを生成します

それでは、ダボの適応拡張メカニズムを紹介しましょう

第二に、適応拡張メカニズムの原理

Dubboでは、 Protocol、Cluster、LoadBalance、ProxyFactoryなど、多くの拡張機能がSPIメカニズムを介して読み込まれることがわかっています一部の拡張機能は、フレームワークの起動フェーズでロードしたくない場合がありますが、拡張メソッドが呼び出されたときに、ランタイムパラメータに従ってロードされることを期待します。つまり、実装クラスはパラメータに従って動的にロードされます。これは矛盾しているように聞こえます。拡張機能がロードされていない場合、拡張メソッドを呼び出すことはできません(静的メソッドを除く)。拡張メソッドが呼び出されない場合、拡張をロードできません。この相反する問題に対して、ダボは適応拡張メカニズムを通じてそれをうまく解決しました。

原則は次のとおりです。

1)Dubboは、拡張インターフェースのプロキシ機能を備えたコードを生成します

2)、javassistまたはjdkを介してこのコードをコンパイルし、Classクラスを取得します

3)、リフレクションを介してプロキシクラスを作成します

アダプティブ拡張メカニズムの実装ロジックはより複雑です。アダプティブ拡張を誰もが知覚的に理解できるようにするために、公式Webサイトの例を使用して説明します。これは自動車に関連する例です。ホイールメーカーインターフェイス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メソッドは、主に次の3つのことを行います。

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タイプのメンバー変数を保持します。プログラムの開始時に、setterメソッドを介してAdaptiveWheelMakerを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を介してロードされ、拡張オブジェクトの同じ名前のメソッドが呼び出されます。 。

次に、ソースコードを詳しく調べて、適応型拡張クラスの生成プロセスを調べます。

3、ソースコード

アダプティブ拡張ソースコードを理解する前に、まずカスタムアノテーション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では、AdaptiveCompilerとAdaptiveExtensionFactoryの2つのクラスのみがAdaptiveによってアノテーションされます。拡張ロードロジックが手動コーディングによって完了したことを示します

場合2)。適応アノテーションが方法にある、ダボは、メソッドのプロキシロジックが生成され拡張されたロードロジックのニーズが自動的にフレームワークによって生成されることを示します

以下は、アダプティブ拡張のソースコードの分析を正式に開始し、XXX $ Adaptiveプロキシクラスの生成プロセスを調査します。分析は以下の2つの部分に分かれています。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には2種類の適応拡張機能があり、1つは手動でコーディングされ、もう1つは自動的に生成されます。自動生成されたアダプティブ拡張は他のクラスに依存しませんが、手動でコーディングされたアダプティブ拡張にはいくつかの依存関係がある場合があります。ここでinjectExtensionメソッドを呼び出す目的は、手動でコーディングされた適応拡張の依存性を注入することです。

2)SPIメカニズム でgetExtensionClassesをすでに分析しているので、ここでは繰り返しません。SPIを介してすべての拡張クラスを取得します。たとえば、このメソッドは、プロトコルインターフェイスの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();
    }

アダプティブ拡張クラスを作成するためのコードは、次の3つのステップに分かれています。1。インターフェイスメソッドのアダプティブアノテーションを確認します。2。クラスコードを生成します。3。インターフェイスのすべてのメソッドのメソッドコードを生成します。

1と2のロジックは非常に単純なので、ここでは分析を拡張しません。2で生成されたクラスコードを見て、次のようにプロトコルインターフェイスを例として取り上げましょう。

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 $ 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のソースコードとデバッグプロセスの単体テストを1回組み合わせると、上記で生成された拡張コードのソースコードを完全に理解できます。

これまで、dubboでの自己適応型拡張について説明してきました。例としてプロトコルインターフェイス呼び出す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メソッドを呼び出し、最後に結果を返します。

4、まとめ

dubboのアダプティブ拡張機能は、パラメーターに従って実装クラスの動的ロードを実装します。アイデアは非常に優れています。ソースコードを読み取るとき、まだ比較的あいまいな場所がいくつかあります。ソースコードの単体テストを使用してデバッグできることを願っています。これは正しいです。プロセス全体が非常に明確です。この記事に誤りがあります、訂正してください、ありがとう

参照:

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

おすすめ

転載: blog.csdn.net/ywlmsm1224811/article/details/103143708