Dubbo源码解析(二)-自适应拓展

前言

前方还是借用官网的解释,我觉得说的还是蛮好的。

Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,然而有些拓展不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。Dubbo通过代理模式就可以解决这个问题,这里将具有代理功能的拓展称之为自适应拓展。Dubbo 并未直接通过代理模式实现自适应拓展,而是代理代理模式基础上,封装了另一种实现方式。Dubbo 首先会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后在通过反射创建代理类。

获取自适应拓展getAdaptiveExtension

让我们从Dubbo获取自适应扩展的方法开始,这个方法在上篇的SPI机制的文章中,在ExtensionLoader的构造方法中出现过。

当时是为了获取ExtensionFactory自适应扩展对象。那么我们就从这个点开始。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

点进去如下

public T getAdaptiveExtension() {
  	// 还是从缓存中获取先。
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " +
                    createAdaptiveInstanceError.toString(),
                    createAdaptiveInstanceError);
        }
      	
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                try {
                  	// 还是没有就创建。
                    instance = createAdaptiveExtension();
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    createAdaptiveInstanceError = t;
                    throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }

    return (T) instance;
}

先从缓存中获取,没有就去创建。那么下面看看createAdaptiveExtension方法

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

代码不多,就几行,但调用了三个方法。

  1. 使用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 使用用 injectExtension 方法向拓展实例中注入依赖

有关injectExtension在上篇中已经分析过了,就不再赘述了,有兴趣的可以取上篇看看。

那么从第一个getAdaptiveExtensionClass方法来看。

private Class<?> getAdaptiveExtensionClass() {
  	// 获取所有的扩展类。
    getExtensionClasses();
  	// 如果自适应缓存不为空,那么直接返回。
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
  	// 创建自适应拓展类。
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

有关getExtensionClasses我在上篇已经解释过了。至于这个cachedAdaptiveClass变量,在上篇的loadClass方法中,如果类上有Adaptive注解,那么这个值就是该class。就拿ExtensionFactory举例,在加载它所有的扩展类后,只有AdaptiveExtensionFactory有该Adaptive注解,那么,该方法就会直接返回AdaptiveExtensionFactory而不进行下面的createAdaptiveExtensionClass了。当然,如果接口下的实现类都没有Adaptive注解,就还是走。下面看下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();
    // 编译代码。
  	return compiler.compile(code, classLoader);
}

具体怎么编译的就不仔细去看了,这里主要看的是如何生成自适应扩展类的源码。其中AdaptiveClassCodeGenerator的cachedDefaultName是SPI注解的value,可能是null。下面来看generate

public String generate() {
    // 如果没有自适应方法,那么不需要生成自适应类,就报错。
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }
  	// 使用StringBuilder去衔接。
    StringBuilder code = new StringBuilder();
    code.append(generatePackageInfo());
    code.append(generateImports());
    code.append(generateClassDeclaration());
  	// 遍历class下的所有方法。
    Method[] methods = type.getMethods();
    for (Method method : methods) {
        code.append(generateMethod(method));
    }
    code.append("}");

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

由于里面方法众多,也不需要延伸,那么我分批来。

#自适应扩展类代码生成

// 生成 package 代码,如:package org.apache.dubbo.common.extension;
private String generatePackageInfo() {
  return String.format(CODE_PACKAGE, type.getPackage().getName());
}

// 生成 import 代码,如:import +  com.aaa.bbb.Ccc
private String generateImports() {
  return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
}

// 生成类代码:public class Adaptive { 这样的
private String generateClassDeclaration() {
  return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}

首先是生成自适应类的一些前缀信息。所属包名,引用的类,已经类名啥啥的。

下面看下如何生成里面的具体方法

private String generateMethod(Method method) {
  	// 获取方法返回类型
    String methodReturnType = method.getReturnType().getCanonicalName();
  	// 获取方法名
    String methodName = method.getName();
  	// 获取内容。
    String methodContent = generateMethodContent(method);
  	// 获取方法参数
    String methodArgs = generateMethodArguments(method);
  	// 获取异常
    String methodThrows = generateMethodThrows(method);
    return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

获取方法返回类型和获取方法名都比较好理解。主要是看如果获取方法的内容和方法参数,获取异常的那个就先不管。

生成方法内容

private String generateMethodContent(Method method) {	
  	// 获取方法上的Adaptive注解
    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
  	// 如果没有,那么返回的是异常信息:"The method %s of interface %s is not adaptive method!");
    if (adaptiveAnnotation == null) {
        return generateUnsupported(method);
    } else {
      	// 获取URL类型的参数索引,拿Protocol来举例,它的refer方法的第二个参数是URL,那么这个值就是1。
      	// 没有URL类型的就返回 -1.
        int urlTypeIndex = getUrlTypeIndex(method);

        // 针对有URL和没URL是两种处理。
        if (urlTypeIndex != -1) {
          	// 有URL参数我们就是这样:
            // if (arg%d == null) throw new IllegalArgumentException("url == null"); URL url = arg%d;
          	// 其中的 %d 是urlTypeIndex。说白了就是多一层URL为空的校验
            code.append(generateUrlNullCheck(urlTypeIndex));
        } else {
            // <1>
            code.append(generateUrlAssignmentIndirectly(method));
        }
      	// 获取方法上Adaptive注解的值。
        String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
      	// 获取方法的参数类型是否有为Invocation类型的。
        boolean hasInvocation = hasInvocationArgument(method);
      	// 生成Invocation参数的判空代码片段。
        code.append(generateInvocationArgumentNullCheck(method));
      	// <2> 生成为extName分配代码。
        code.append(generateExtNameAssignment(value, hasInvocation));
        // 生成变量extName空检查的代码。
        code.append(generateExtNameNullCheck(value));
      	// 生成 %s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);
      	// 第三个是ExtensionLoader类名,其他都是%s都是type name。
        code.append(generateExtensionAssignment());

        // 生成目标方法调用逻辑,格式为:return extension.%s(%s);
      	// 第一个%s,是方法名,第二个是参数,就是调用要调用的方法。
        code.append(generateReturnAndInvocation(method));
    }

    return code.toString();
}

上面一个个来。 先从没有URL参数的生成逻辑来,

<1>generateUrlAssignmentIndirectly

// 我们就假设传进来的是Protocol下没有URL参数的export方法
private String generateUrlAssignmentIndirectly(Method method) {
  	// 获取参数类型。
    Class<?>[] pts = method.getParameterTypes();

    Map<String, Integer> getterReturnUrl = new HashMap<>();
    // 遍历这些参数的类型下的方法。
    for (int i = 0; i < pts.length; ++i) {
        for (Method m : pts[i].getMethods()) {
            String name = m.getName();
          	// 如果有 方法名以“get”开头 或者 name长度 > 3
          	// 同时需要是public、非静态的、方法没参数、返回值是URL的。
          	// 那么就将该方法名和i的映射放进map中。
            if ((name.startsWith("get") || name.length() > 3)
                    && Modifier.isPublic(m.getModifiers())
                    && !Modifier.isStatic(m.getModifiers())
                    && m.getParameterTypes().length == 0
                    && m.getReturnType() == URL.class) {
                getterReturnUrl.put(name, i);
            }
        }
    }
  	// 如果找不到,那么抛异常。
  	// 说白了,就是export方法参数列表中即使没有URL参数,那么它参数中也要有能返回URL的参数类型。
  	// export方法中的唯一一个参数Invoker<T> invoker就是能返回URL的
    if (getterReturnUrl.size() <= 0) {
        // getter method not found, throw
        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());
    }
  	// 获取名称为“getUrl”的位置。
    Integer index = getterReturnUrl.get("getUrl");
    if (index != null) {
        return generateGetUrlNullCheck(index, pts[index], "getUrl");
    } else {
        Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
        return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
    }
}

generateGetUrlNullCheck方法主要是:

  1. 生成检查arg [i]是否为空
  2. 生成检查arg [i].getXX()是否为空。
  3. 生成一个赋值URL 方法 = arg [i].getXX()

还是拿export方法举例。

它会生成如下:

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();

<3>generateExtNameAssignment

这个方法看上去有点复杂,但蛮好理解的。

// value是Adaptive注解的值,hasInvocation表示方法的所有参数中是否有为Invocation类型的
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
    
    String getNameCode = null;
  	// 从Adaptive注解的值列表最后一个来
    for (int i = value.length - 1; i >= 0; --i) {
      	// 如果是最后一个
        if (i == value.length - 1) {
          	// 同时defaultExtName不为空
          	// 该defaultExtName是new AdaptiveClassCodeGenerator时传进来的cachedDefaultName
          	// 上面也解释过了,是SPI 注解的value,可能为null
            if (null != defaultExtName) {
              	// 如果值不是protocol
                if (!"protocol".equals(value[i])) {
                  	// 如果有方法中有参数为Invocation类型
                    if (hasInvocation) {
                      	// 分支1
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                      	// 分支2
                        getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    }
                } else {
                  	// 如果值是protocol
                  	// 分支3
                    getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                }
            } else {
              	// 如果defaultExtName为空
              	// 值不是protocol
                if (!"protocol".equals(value[i])) {
                  	// 有有参数为Invocation类型
                    if (hasInvocation) {
                      	// 分支4
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                      	// 分支5
                        getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                    }
                } else {
                  	// 值是protocol
                  	// 分支6
                    getNameCode = "url.getProtocol()";
                }
            }
        } else {
          	// 如果值不是protocol
            if (!"protocol".equals(value[i])) {
                if (hasInvocation) {
                  	// 分支7
                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                } else {
                  	// 分支8
                    getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                }
            } else {
              	// 分支9
                getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
            }
        }
    }
		// String extName = %s; %s就是各个分支下来的。
    return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

上面其实就是按不同的条件进入不同的生成代码片段中,难倒是不难,就是看着挺烦。下面我们拿两个来举例吧,首先拿负载均衡的LoadBalance来举例。

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

首先能看见SPI注解有个RandomLoadBalance.NAME,就是“random”,同时有Invocation类型的参数,那么hasInvocation就为true,value[]这个数组就是只有loadbalance一个值,defaultExtName不为空,而是为random。

那么综合来看:

value = [“loadbalance”];
hasInvocation = true;
defaultExtName = "random"

于是会走分支1,生成如下代码:

String extName = url.getMethodParameter(“select”, "loadbalance", "random");

这个方法比较简单,那再看一个。

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

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

}

只分析一个bind哈,毕竟都差不多,按上面的分析,各变量值为:

value = [“server”, "transporter"];
hasInvocation = false;
defaultExtName = "netty"

于是会走两遍for循环。第一遍会走分支2生成如下:

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

第二遍走分支8,连带第一遍的生成如下:

String extName = url.getParameter(“server”, url.getParameter("transporter", "netty"));

都举了两个例子了,应该不难理解吧?自觉写的还是比较通俗易懂的,再不理解可以多看两遍,不难的。

总结

好了,这个自适应代码还是不难的,不过我觉得也没必要看的太细,明白有这么一个方法就行。就像Spring,咱们大体明白就行,各个都了解,那看不完的。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值