dubbo源码系列11-自适应拓展机制 Adaptive

一、前沿

调试 dubbo 源码时,相信大家经常会遇到这样一个问题,即调用 Protocol、Cluster、ProxyFactory、LoadBalance(这些类中都有 SPI 注解) 中定义的带有 Adaptive 注解的方法 时总是按照以下流程调用:

步骤一: 调用 url 的 getXXX 方法获取参数值

步骤二: 调用 ExtensionLoader 的 getExtensionLoader 获取加载器

步骤三: 调用 ExtensionLoader 的 getExtension 根据从url获取的参数作为类名称加载实现类

如下图所示:

debug 调试图中可以,三个步骤的调用有一个共同点,就是具体的调用方法都是从 Protocol$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

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/103143708