【dubbo系列】 11-Dubbo 自适应拓展机制 Adaptive

原理

在 Dubbo 中,很多的拓展都是通过 SPI 机制进行加载的,比如 Protocol 、 Cluster 、 LoadBalance 等。但是有些拓展并不想在框架启动的时候就加载,而是希望在拓展方法调用的时候被调用,根据运行的参数进行加载。这看起来有些矛盾,拓展未被加载时,那么拓展方法就无法调用,拓展方法未被调用时,拓展类就无法加载。对于这个矛盾问题,Dubbo 通过自适应拓展机制来实现解决。其主要实现思想是,首先会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后通过反射创建代理类。

使用示例

Dubbo 主要是通过提供一个注解 @Adaptive 来实现。

  1. 定义接口

    
    package com.joyxj.spi;
    
    import org.apache.dubbo.common.URL;
    import org.apache.dubbo.common.extension.Adaptive;
    import org.apache.dubbo.common.extension.SPI;
    
    /**
    * user service interface
    *
    * @author xiaoj
    * @version 1.0
    * @date 2019-06-27
    */
    @SPI
    public interface UserService {
    
       @Adaptive
       String getName(String name, URL url);
    }
    
    

    这里的关键的注解 Adaptive ,并且方法中必须有一个 URL 的参数。

    1. 实现类
    package com.joyxj.spi;
    
    import org.apache.dubbo.common.URL;
    
    import java.util.Random;
    
    /**
    * 【功能描述】
    *
    * @author xiaoj
    * @version 1.0
    * @date 2019-06-27
    */
    public class UserServiceImpl implements UserService {
       @Override
       public String getName(String name, URL url) {
           return name + ":" + (new Random().nextInt(100) + 1);
       }
    }
    
    
  2. spi 配置文件

    META-INF/dubbo 目录下新建文件 com.joyxj.spi.UserService , 其名称为接口的全限名。

    userServiceImpl = com.joyxj.spi.UserServiceImpl
    
  3. 测试

    @Test
    public void test3() {
       UserService userService = ExtensionLoader.getExtensionLoader(UserService.class)
               .getAdaptiveExtension();
       // 模拟的 URL 地址
       URL url = URL.valueOf("dubbo://192.168.0.101:20880?user.service=userServiceImpl");
       System.out.println(userService.getName("xiaojun",url));
    }
    

源码分析

以下源码发析基于 Dubbo 2.7.2 版本,其它版本可能会略有不同。

Adaptive 注解

在上面的示例中,说到 Dubbo 的自适应拓展机制中的一个关键注解 Adaptive, 那我们先看下这个注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};

}

从以上代码中可知, Adaptive 可以用在类上,也可以用在方法上,当 Adaptive 用在类上面时,Dubbo 不会为其生成代理类,注解在方法上时,Dubbo 会为其生成代理类 。在 Dubbo 中,仅有二个类被 Adaptive 注解了,分别是 AdaptiveCompilerAdaptiveExtensionFactory 。 此种情况,表明拓展的加载由人工编码的方式生成。更多的时候,Adaptive 注解是用在方法上面,表示加载的逻辑由框架自动生成。本文重点分析的也是 Adaptive 注解用在方法上的分析。

获取自适应拓展

Dubbo SPI 一文中, 在类 ExtensionLoader 中的构造方法中有这样一行代码 : objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()) 。其中的 getAdaptiveExtension 即为获取自适应拓展的入口方法。我们也是从这个方法开始进行分析:

@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
  // 从缓存中获得自适应拓展, cachedAdaptiveInstance 是一个 Holder 对象,用于持有目标对象
  Object instance = cachedAdaptiveInstance.get();
  // 缓存未命中
  if (instance == null) {
      // 判断是否有异常
      if (createAdaptiveInstanceError == null) {
          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);
                  }
              }
          }
      } else {
          throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
      }
  }

  return (T) instance;
}

首先,getAdaptiveExtension 方法会从缓存中获取自适应拓展对象,如果缓存未命中,则会调用 createAdaptiveExtension 方法创建自适应拓展。 createAdaptiveExtension 方法源码如下:

private T createAdaptiveExtension() {
      try {
          // 获得自适应拓展类,并通过反射创建对象。并通过injectExtension 向对象注入依赖。
          return injectExtension((T) getAdaptiveExtensionClass().newInstance());
      } catch (Exception e) {
          throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
      }
  }

createAdaptiveExtension 方法很简单,包含以下三个逻辑:

  • 调用 getAdaptiveExtensionClass 方法获得自适应拓展 Class 对象。
  • 通过反射进行实例化。
  • 通过 injectExtension 方法注入依赖。

前面说过,Adaptive 有两种类型的自适应拓展,一种是手工编码的,一种是通过框架自动生成的。手工编码的适应拓展中可能存在一些依赖,而自动生成的自适应拓展则不会依赖于其它类,因为它是作用在方法上面的。这里调用 injectExtension 方法主要也是为手工编码的自适应注入依赖。

下面分析 getAdaptiveExtensionClass 方法,获得自适应拓展类。

private Class<?> getAdaptiveExtensionClass() {
  // 通过 SPI 获得所有的拓展类。这个在分析Dubbo spi 已经分析过了。
  getExtensionClasses();
  // 检查缓存,如果缓存中有的话,则直接返回。
  if (cachedAdaptiveClass != null) {
      return cachedAdaptiveClass;
  }
  // 缓存中没有,则调用 createAdaptiveExtensionClass 创建自适应拓展类。
  return cachedAdaptiveClass = createAdaptiveExtensionClass();
  }

getAdaptiveExtensionClass 方法虽然简单,但仍然有一些细节需要注意的,getExtensionClasses 方法用于获某个接口的所有实现类,在获得这些实现类时,如果某个实现类被 Adaptive 注释了,那么该类就会被赋值给 cachedAdaptiveClass 变量。 此时在检查缓存时就不会为空了,则会直接返回。如果所有的实现类均未被 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();
      // 编译代码,生成 class
      return compiler.compile(code, classLoader);
  }

自适应拓展代码生成

通过 AdaptiveClassCodeGenerator 类的 generate 方法生成自适应拓展代码,下面分析 generate 的源码:

  /**
     * generate and return class code
     */
    public String generate() {
        // adaptive 注解检测,方法至少要有一个 adaptive 注解。
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        StringBuilder code = new StringBuilder();
        // 生成包信息
        code.append(generatePackageInfo());
        // 生成导入信息
        code.append(generateImports());
        // 生成类声明信息
        code.append(generateClassDeclaration());

        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

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

adaptive 注解检测

/**
  * adaptive 注解检测,SPI 接口 方法至少要有一个 adaptive 注解。
  */
  private boolean hasAdaptiveMethod() {
      return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
  }

生成类

这里包含生成 package 、import 、class 三个部分。

  /**
  * SPI 接口所在的包。package + type 所在包
  */
private String generatePackageInfo() {
    return String.format(CODE_PACKAGE, type.getPackage().getName());
}

/**
  * 生成import 信息。import + ExtensionLoader 全限定名
  */
private String generateImports() {
    return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
}

/**
  * 生成类声明信息。public class + type简单名称 + $Adaptive + implements + type全限定名 + {
  */
private String generateClassDeclaration() {
    return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}

以 Dubbo 的 Protocol 接口为例,生成的代码如下:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
  // 省略方式代码
}

生成方法

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);
}
先看 generateMethodContent 方法:
private String generateMethodContent(Method method) {
    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    // 判断是否有 Adaptive 注解
    if (adaptiveAnnotation == null) {
        // 生成无 Adaptive 注解方法内容,内容主要是抛出异常
        return generateUnsupported(method);
    } else {
        // 生成有 Adaptive 注解方法的内容,下面详细分析
        int urlTypeIndex = getUrlTypeIndex(method);

        // found parameter in URL type
        if (urlTypeIndex != -1) {
            // Null Point check
            code.append(generateUrlNullCheck(urlTypeIndex));
        } else {
            // did not find parameter in URL type
            code.append(generateUrlAssignmentIndirectly(method));
        }

        String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

        boolean hasInvocation = hasInvocationArgument(method);

        code.append(generateInvocationArgumentNullCheck(method));

        code.append(generateExtNameAssignment(value, hasInvocation));
        // check extName == null?
        code.append(generateExtNameNullCheck(value));

        code.append(generateExtensionAssignment());

        // return statement
        code.append(generateReturnAndInvocation(method));
    }

    return code.toString();
}

/**
*  生成无 Adaptive 注解方法内容,生成形如
* throw new UnsupportedOperationException("The method + 方法签名 + 
* of interface +类全限名+ is not  *adaptive method!");  的内容
*/
private String generateUnsupported(Method method) {
    return String.format(CODE_UNSUPPORTED, method, type.getName());
}

一个方法可以被注解 Adaptive 修饰,也可以不被修饰。对于未被 Adaptive 修饰的方法很简单,只是抛出异常。

下面重点分析 被 Adaptive 注解修饰的方法的代码生成:

  1. 获取 URL 数据

    从之前的例子中,我们知道需要从 URL 提取目标拓展的名称。因为代码生成逻辑的重新一部分就是从方法参数列表中或者其它参数中获得 URL 数据。

    我们以 Protocol 接口的 referexport 方法举例:

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    

    对于 refer 方法比较简单,因为其包含了 URL 数据。对于 export 方法比较困难,方法没有 URL 参数,因为需要从 Invoker 参数中获取 URL 数据。获得方式是调用 Invoker 中可返回 URL 的 getter 方法。如果 Invoker 中无相关的 getter 方法 ,则会抛出异常。

    Protocol 接口的 referexport 方法生成的代码如下(经过格式化):

    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;
     // 省略其它步骤生成的代码
    }
    
    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();
     // 省略其它步骤生成的代码
    }
    
    

    具体的源码如下:

    // 先获得 URL 参数的索引
    int urlTypeIndex = getUrlTypeIndex(method);
    // 判断参数中是否有 URL 参数
    if (urlTypeIndex != -1) {
       // Null Point check
       // 表示方法中有 URL 参数
       code.append(generateUrlNullCheck(urlTypeIndex));
    } else {
       // did not find parameter in URL type
       // 方法中没有URL 参数
       code.append(generateUrlAssignmentIndirectly(method));
    }
    
    // 有URL 的生成逻辑,生成的形如:
    // if (arg1 == null) throw new IllegalArgumentException("url == null"); 
    // org.apache.dubbo.common.URL url = arg1; 的代码
    private String generateUrlNullCheck(int index) {
     return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index);
    }
    // 无 URL 参数的生成逻辑
    private String generateUrlAssignmentIndirectly(Method method) {
     // 获取方法的参数类型列表
     Class<?>[] pts = method.getParameterTypes();
    
     // find URL getter method
     // 遍历方法的参数类型列表
     for (int i = 0; i < pts.length; ++i) {
         // 遍历每个参数类型的所有方法
         for (Method m : pts[i].getMethods()) {
             String name = m.getName();
             // 方法以get 开头
             // 长度大于3
             // 方法的访问权限为public
             // 方法不是静态方法
             // 方法的参数个数为0
             // 方法的返回类型为URL
             if ((name.startsWith("get") || name.length() > 3)
                     && Modifier.isPublic(m.getModifiers())
                     && !Modifier.isStatic(m.getModifiers())
                     && m.getParameterTypes().length == 0
                     && m.getReturnType() == URL.class) {
                 return generateGetUrlNullCheck(i, pts[i], name);
             }
         }
     }
     // getter method not found, throw
     // 如果找到相应的 getter 方法,则抛出异常
     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());
    }
    
    /**
     * 无 URL 参数的代码生成
     */
    private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
     // Null point check
     StringBuilder code = new StringBuilder();
     // 对 可返回 URL 参数进行判空
     // if (arg + index == null) throw new IllegalArgumentException(" + 参数全限定名 + argument == null");
     code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
             index, type.getName()));
     // 对 URL 参数进行判空
     // if (arg + index + .getter +  方法名() == null) throw new IllegalArgumentException("参数全限定名 getter +  方法名() == null");
     code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
             index, method, type.getName(), method));
     // 生成赋值语句
     // org.apache.dubbo.common.URL url = arg + index + .getter 方法名();
     code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
     return code.toString();
    }
    
  2. 获得 Adaptive 注解值

    Adaptive 注解 value 类型是一个 String 数组,可以填写多个值,默认情况为空的数组。如果是非空数组的话,则直接取数组的值即可。如果为空的话,其处理过程为:把类转成数组,定义一个 StringBuilder , 并遍历数组,如果是大写字母,则把大写转小写,放入 StringBuilder ,同时如果不是第一个字母,则加入点号 。 如果是小写则直接加入。如 LoadBalance 处理后为 load.balance

    其源码如下:

    /**
     * 获取 Adaptive 注解的值 
     */
    private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
     // 获取注解的值,为一个数组
     String[] value = adaptiveAnnotation.value();
     // value is not set, use the value generated from class name as the key
     if (value.length == 0) {
         // 注解的值为空的处理逻辑,按照驼峰法命名分隔
         String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
         value = new String[]{splitName};
     }
     // 注解值不为空时,直接返回注解值
     return value;
    }
    
    /**
     * 驼峰法分隔
     * 如:HelloWorld -> hello.world
     *     helloworld -> hello.world
     *     helloWorld -> hello.world
     */
    public static String camelToSplitName(String camelName, String split) {
     // 判断是否为空
     if (isEmpty(camelName)) {
         return camelName;
     }
     StringBuilder buf = null;
     // 遍历类名字符串数组
     for (int i = 0; i < camelName.length(); i++) {
         char ch = camelName.charAt(i);
         // 判断是否是大写字母
         if (ch >= 'A' && ch <= 'Z') {
             // 大写字母处理
             if (buf == null) {
                 // 表示是第一次处理大写字母
                 buf = new StringBuilder();
                 if (i > 0) {
                     // 第一次处理大写字母,且不是在第一个位置,则从第一位开始到当前位置字符全部追加
                     buf.append(camelName.substring(0, i));
                 }
             }
             if (i > 0) {
                 //添加点号
                 buf.append(split);
             }
             // 转小写追加到 StringBuffer
             buf.append(Character.toLowerCase(ch));
         } else if (buf != null) {
             // 小写字母且 StringBuffer 不为空(即已经处理过大写字母)时,直接追加。
             // 
             buf.append(ch);
         }
     }
     return buf == null ? camelName : buf.toString();
    }
    
  3. 检测 Invocation 参数

    判断方法参数中是否含有 Invocation 类型的参数。如果存在,则生成判空的代码和其它的一些代码。

    
    private String generateMethodContent(Method method) {
     Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
     StringBuilder code = new StringBuilder(512);
     // 判断是否有 Adaptive 注解
     if (adaptiveAnnotation == null) {
           // ${无 Adaptive 注解方法代码生成逻辑}
     } else {
         // ${获取 URL 数据}
         // ${获取 Adaptive 注解值}
         // 检测 方法中参数是否含有 Invocation 参数,其它这个参数值 hasInvocation 在这里未用上
         boolean hasInvocation = hasInvocationArgument(method);
         // 参于含有 Invocation 参数的情况,追加判空代码和其它代码
         code.append(generateInvocationArgumentNullCheck(method));
    
         // 省略其它逻辑
     } 
     return code.toString();
    }
    
    /**
     * 判断方法参数中是否含有 Invocation 类型的参数
     */
    private boolean hasInvocationArgument(Method method) {
     // 获得方法参数列表
     Class<?>[] pts = method.getParameterTypes();
     // 判断是否含有 Invocation 类型的参数
     return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
    }
    
    /**
     * 判断方法参数中是否含有 Invocation 类型的参数,如果有 Invocation 参数,则追加类似如下代码:
     * if (arg + index +  == null) throw new IllegalArgumentException("invocation == null"); 
     *       String methodName = arg + index + .getMethodName();
     */
    private String generateInvocationArgumentNullCheck(Method method) {
     Class<?>[] pts = method.getParameterTypes();
     return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
                     .mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
                     .findFirst().orElse("");
    }
    
    
  4. 生成拓展名获取逻辑

    本段逻辑用于根据 SPI 和 Adaptive 注解值生成 “获得拓展名逻辑” ,同时生成逻辑也受 Invocation 参数的影响。可能会生成如下几种但不限于几下几种代码:

    String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
    

    String extName = url.getMethodParameter(methodName, "loadbalance", "random");
    

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

    其源码如下:

    
    private String generateMethodContent(Method method) {
     Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
     StringBuilder code = new StringBuilder(512);
     // 判断是否有 Adaptive 注解
     if (adaptiveAnnotation == null) {
           // ${无 Adaptive 注解方法代码生成逻辑}
     } else {
         // ${获取 URL 数据}
         // ${获取 Adaptive 注解值}
         // ${判断是否包含 Invocation 参数}
         // ${检测是否包含 Invocation 参数,追加判断代码和其它代码}
         code.append(generateExtNameAssignment(value, hasInvocation));
         // 省略其它逻辑
     } 
     return code.toString();
    }
    
    /**
    * 生成拓展名获取逻辑
    * @param value 方法中 Adaptive 注解的值,可以是一个数组
    * @param hasInvocation 方法中是否包含 Invocation 类型的参数
    */
    private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
    // TODO: refactor it
    String getNameCode = null;
    // 从后往前遍历
    // 此处循环的目的是生成从 URL 中获得拓展名的代码,生成的代码会赋值给 getNameCode 变量
    for (int i = value.length - 1; i >= 0; --i) {
     // 当 i 是最后一个元素时  
     if (i == value.length - 1) {
       // defaultExtName 为 SPI 注解的值,默认值为 null
       // defaultExtName 不为空
       if (null != defaultExtName) {
         // protocol 是 url 的一部分,可通过 getProtocol 方法获取,其他的则是从
         // URL 参数中获取。因为获取方式不同,所以这里要判断 value[i] 是否为 protocol
         // 值不等于 protocol
         if (!"protocol".equals(value[i])) {
             // 方法中含有 Invocation 类型参数
             if (hasInvocation) {
               // url.getMethodParameter(methodName,value[i],defalutExtName)
               getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
             } else {
               // url.getParameter(value[i],defaultExtName)
               getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
             }
         } else {
           // 值等于 protocol 
           // 从 URL 中获取
           // url.getProtocol() == null ? url.getProtocol() : defaultExtName
           getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
         }
       } else {
         // 默认拓展名为空时
         // 注解值为非 protocol
         if (!"protocol".equals(value[i])) {
           // 包含 Invocation 参数
           if (hasInvocation) {
             // url.getMethodParameter(methodName,value[i],defalutExtName)
             getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
           } else {
             // 不包含 Invocation 参数
             // url.getParameter(value[i])
             getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
           }
         } else {
           // 注解值为protocol
           // url.getProtocol() 表示从 url 中获取协议
           getNameCode = "url.getProtocol()";
         }
       }
     } else {
       // 表示不是最后一个元素
       // 注解值为非 protocol 
       if (!"protocol".equals(value[i])) {
         // 方法参数包含 Invocation 类型
         if (hasInvocation) {
           // url.getMethodParameter(methodName,value[i],defalutExtName)
           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 {
         // 注解值为 protocol 
         // url.getProtocol() == null ? url.getProtocol(), getNameCode;
         getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
       }
     }
    }
     // CODE_EXT_NAME_ASSIGNMENT : String extName = %s;
     // ext 赋值 
     return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
    }
    

    我们先对 Protocol 的 export 方法进行分析:其相关变量的值为:

    String defaultExtName = "dubbo";
    boolean hasInvocation = false;
    String getNameCode = null;
    String[] value = ["protocol"]; // 由于注解 Adaptive 没有值,所以其值取类名的驼峰法的值
    
    

    先对 value进行遍历,其只有一个值,i = 0 , value[i] = “protocol”。且是最后一个值。 其生成的代码为

    String extName = ( url.getProtocol() == null ? "" : url.getProtocol() );
    

    再分析 Transporterconnect 方法。其相关变量的值为:

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

    对 value 进行遍历,并且从后往前遍历。此时 i = 1, value[i] = “transporter” , 生成的代码如下:

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

    下一步赋值 , i = 0 , 此时 value[i] = “client”,生成的代码如下:

    url.getParameter("server", url.getParameter("transporter", "netty"));
    
  5. 拓展名判断处理

    在上一步我们生成了形如 String extName = ( url.getProtocol() == null ? "" : url.getProtocol() ); 这样的代码。这一步主要是一个判空处理。如果为空的话,则抛出异常。

    private String generateMethodContent(Method method) {
     Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
     StringBuilder code = new StringBuilder(512);
     // 判断是否有 Adaptive 注解
     if (adaptiveAnnotation == null) {
           // ${无 Adaptive 注解方法代码生成逻辑}
     } else {
         // ${获取 URL 数据}
         // ${获取 Adaptive 注解值}
         // ${判断是否包含 Invocation 参数}
         // ${检测是否包含 Invocation 参数,追加判断代码和其它代码}
         // ${生成拓展名获取逻辑}
         // 验证拓展名是否为空
         code.append(generateExtNameNullCheck(value));
         // 省略其它逻辑
     } 
     return code.toString();
    }
    
    /**
    * 验证拓展名是否为空
    * 
    */
    private String generateExtNameNullCheck(String[] value) {
         return String.format(CODE_EXT_NAME_NULL_CHECK, type.getName(), Arrays.toString(value));
     }
    

    上面判断的代码生成类似如下的代码:

    if(extName == null) 
       throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from " + 
         "url (" + url.toString() + ") use keys([server, transporter])");
    
  6. 生成拓展加载与目标方法调用逻辑

    本段代码主要是根据拓展名生成拓展实例,并调用目标方法。

    private String generateMethodContent(Method method) {
     Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
     StringBuilder code = new StringBuilder(512);
     // 判断是否有 Adaptive 注解
     if (adaptiveAnnotation == null) {
           // ${无 Adaptive 注解方法代码生成逻辑}
     } else {
         // ${获取 URL 数据}
         // ${获取 Adaptive 注解值}
         // ${判断是否包含 Invocation 参数}
         // ${检测是否包含 Invocation 参数,追加判断代码和其它代码}
         // ${生成拓展名获取逻辑}
         // ${验证拓展名是否为空}
         // 根据拓展名获得拓展对象
         code.append(generateExtensionAssignment());
         // 调用目标方法和返回
         code.append(generateReturnAndInvocation(method));
     } 
     return code.toString();
    }
    
    /**
    * 根据拓展名获得拓展对象
    * 接口的全限名 + extension = (接口的全限名) + ExtensionLoader.getExtensionLoader( + 接口的全限名 + extName)
    */
    private String generateExtensionAssignment() {
     return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
    }
    
    /**
    * 调用目标方法
    */
    private String generateReturnAndInvocation(Method method) {
     // 根据方法的返回类型判断是否需要生成 return。
     String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";
     // 参数列表用逗号分隔
     String args = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.joining(", "));
     // 调用目标方法。
     return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
    }
    
    

    这步比较简单,以 Transporterconnect 方法为例,生成的代码如下:

    org.apache.dubbo.remoting.Transporter extension = (org.apache.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class).getExtension(extName);
         return extension.bind(arg0, arg1);
    

generateMethodContent 方法已经分析完成,以 Protocolexport 为例,生成的代码如下:

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 ? "proptocol" : 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);
生成完整的代码

主要是对方法的一个收尾操作,包括生成方法的声明和异常等的处理。

  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 generateMethodArguments(Method method) {
    Class<?>[] pts = method.getParameterTypes();
    return IntStream.range(0, pts.length)
                    .mapToObj(i -> String.format(CODE_METHOD_ARGUMENT, pts[i].getCanonicalName(), i))
                    .collect(Collectors.joining(", "));
  }

  /**
   * 方法异常
   */
  private String generateMethodThrows(Method method) {
      Class<?>[] ets = method.getExceptionTypes();
      if (ets.length > 0) {
          String list = Arrays.stream(ets).map(Class::getCanonicalName).collect(Collectors.joining(", "));
          return String.format(CODE_METHOD_THROWS, list);
      } else {
          return "";
      }
  }

Protocolexport 为例,生成的完整代码如下:


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 ? "" : 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);
    }


   转载规则


《【dubbo系列】 11-Dubbo 自适应拓展机制 Adaptive》 孤独如梦 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
【Elasticsearch 系列】 Elasticsearch 基本概念 【Elasticsearch 系列】 Elasticsearch 基本概念
前言本文主要介绍 Elasticsearch 的几个基本概念,包括 Index: 索引 Type: 分类 Document: 文档 Node: 节点 Shard: 分片 文档文档特点 Elasticsearch 是面向文档的,文
2019-07-09
下一篇 
提问的智慧 提问的智慧
声明本文来源于Github 的 How-To-Ask-Questions-The-Smart-Way。 说明How To Ask Questions The Smart Way Copyright © 2001,2006,2014 Eric
2019-06-27
  目录