Dubbo源码分析之SPI(三)

一、概述

  本篇介绍自适应扩展,方法getAdaptiveExtension()的实现。ExtensionLoader类本身很多功能也使用到了自适应扩展。包括ExtensionFactory扩展。

  通俗的讲,自适应扩展要实现的逻辑是:调用扩展点的方法时,自动判断要调用那个扩展点实现类的方法。我们知道,一个扩展点通常有多个实现类,在配置文本文件中分多行配置,在前面的分析中,我们知道通过getExtension(String name)方法,返回的是指定key的扩展点,而自适应扩展点方法getAdaptiveExtension()在调用前,不确认返回那个扩展点。而是在方法调用的时候,根据方法入参,进行确定,具体是调用那个实现类。

  自适应扩展基于@Adaptive注解,可以修饰类,也可以修饰方法。修饰类的时候,逻辑比较简单,不会动态生成代码逻辑,使用的场景也比较少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修饰方法的时候,会动态生成一个新类,新类包括扩展点的所有方法,调用getAdaptiveExtension()返回的就是新类对象。

二、详细介绍

  前面我们说过,@Adaptive可以修饰类,也可以修饰方法。我们先看下修饰类的场景。

  通过一个具体的实现类来看下,这里我们分析AdaptiveCompiler类的实现:

 1 @Adaptive
 2 public class AdaptiveCompiler implements Compiler {
 3 
 4     private static volatile String DEFAULT_COMPILER;
 5 
 6     public static void setDefaultCompiler(String compiler) {
 7         DEFAULT_COMPILER = compiler;
 8     }
 9 
10     @Override
11     public Class compile(String code, ClassLoader classLoader) {
12         Compiler compiler;
13         ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Compiler.class);
14         String name = DEFAULT_COMPILER; // copy reference
15         if (name != null && name.length() > 0) {
16             compiler = loader.getExtension(name);
17         } else {
18             compiler = loader.getDefaultExtension();
19         }
20         return compiler.compile(code, classLoader);
21     }
22 
23 }

  此类,只有一个对外提供的方法compile(String code, ClassLoader classLoader),我们来看方法的实现。

首先沟通ExtensionLoader.getExtensionLoader(Compiler.class),获取Compiler对应的ExtensionLoader对象,然后判断构造器中初始化的DEFAULT_COMPILER 变量是否有值。如果存在就通过loader.getExtension(name)方法获得扩展点实现。如果DEFAULT_COMPILER 为空,则调用loader.getDefaultExtension()方法,返回默认实现。获取compiler扩展点实现对象后,调用对应的compile方法。

  由此,我们可以看到,@Adaptive修饰的类,在调用具体方法的时候,是根据一定的条件进行判断,确认具体调用的实现类对象。

  我们再说下@Adaptive修饰方法的场景。

  扩展点实现类的方法如果被@Adaptive修饰,在调用getAdaptiveExtension()方法时候,程序会自动生成一个新类,新类是一个名为扩展点接口名+$Adaptive,实现了扩展点接口的类。新类中的方法,主要分为两类,一是有@Adaptive注解的方法,一个是没有@Adaptive注解的方法。

  有@Adaptive注解的方法,方法内部会判断方法入参是否有URL(此处是dubbo内的URL),或是方法入参对象是否可以get到URL。如果都不能获取到URL,直接throw 出Exception。如果能获取到URL,则从URL对象中获取需要调用的实现类对应的配置文本文件的key,根据什么参数从URL中获取呢?首先是从@Adaptive的value获取(此value是一个字符串数组),如果@Adaptive为空,则根据类名进行转换,得出从URL获取key的参数名,转换规则是根据驼峰规则,遇到大写字符添加”.“,如 AdaptiveFactory 为:adaptive.factory。获得参数后,再通过getExtension(..)方法,获得需要调用的扩展点实现类对象。

  到这里,我们基本介绍了自适应扩展点的实现逻辑,但是有一点没有说到,就是不管@Adaptive修饰类还是修饰方法,自适应扩展点的返回逻辑,这点是要结合代码进行说明,接下来就开启我们的源代码分析。

三、源代码分析

  我们从getAdaptiveExtension()方法开始

  

 1 public T getAdaptiveExtension() {
 2     // 从缓存中获取自定义拓展
 3     Object instance = cachedAdaptiveInstance.get();
 4     if (instance == null) {
 5         if (createAdaptiveInstanceError == null) {
 6             synchronized (cachedAdaptiveInstance) {
 7                 instance = cachedAdaptiveInstance.get();
 8                 if (instance == null) {
 9                     try {
10                         instance = createAdaptiveExtension();
11                         cachedAdaptiveInstance.set(instance);
12                     } catch (Throwable t) {
13                         createAdaptiveInstanceError = t;
14                         throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
15                     }
16                 }
17             }
18         } else {
19             throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
20         }
21     }
22 
23     return (T) instance;
24 }

  这个方法的逻辑很简单,主要包括

  1、从缓存对象cachedAdaptiveInstance获取自适应扩展点实例
  2、缓存有直接返回,没有进行方法createAdaptiveExtension()调用
  3、根据方法返回的实例,设置到到缓存里,并进行返回

  所以我们接着分析createAdaptiveExtension方法

 1 private T createAdaptiveExtension() {
 2     try {
 3         // injectExtension 为@Adaptive注解的类 可能存在的IOC服务
 4         // @Adaptive注解方法 自动生成的代理类不存在IOC可能
 5         T instance = (T) getAdaptiveExtensionClass().newInstance();
 6         return injectExtension(instance);
 7     } catch (Exception e) {
 8         throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
 9     }
10 }

  可以看到,方法内部是通过getAdaptiveExtensionClass() 获取到Class实例,再反射实例化,获取到实例对象。

  我们接着往下看getAdaptiveExtensionClass()方法

 1 private Class getAdaptiveExtensionClass() {
 2     // 通过SPI获取所有的扩展类,赋值相关的成员变量
 3     getExtensionClasses();
 4     // 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空
 5     if (cachedAdaptiveClass != null) {
 6         return cachedAdaptiveClass;
 7     }
 8     // 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类
 9     return cachedAdaptiveClass = createAdaptiveExtensionClass();
10 }

  首先执行的是getExtensionClasses()方法,之后判断cachedAdaptiveClass  是否为空,不为空就直接返回了。这个cachedAdaptiveClass 变量,其实就是有@Adaptive修饰的扩展点实现。也就是说,如果在扩展点的实现类中,存在@Adaptive修饰的类,就直接返回这个类了。

  那么cachedAdaptiveClass 在是哪里赋值的呢?我们需要再看getExtensionClasses()方法。getExtensionClasses这个方法在前面两篇文章中已经都有介绍。在默认扩展点的实现里面,cachedDefaultName变量的赋值就是在这个方法里进行的。cachedAdaptiveClass 的赋值的方法调用链我们这里直接给出来

  

1 getExtensionClasses()-->loadExtensionClasses()-->loadDirectory()-->loadResource()-->loadClass()

  隐藏的比较深,第5个方法才对cachedDefaultName进行了赋值。

  我们一步一步来分析,先看getExtensionClasses()

  

 1 private Map> getExtensionClasses() {
 2     Map> classes = cachedClasses.get();
 3     if (classes == null) {
 4         synchronized (cachedClasses) {
 5             classes = cachedClasses.get();
 6             if (classes == null) {
 7                 classes = loadExtensionClasses();
 8                 cachedClasses.set(classes);
 9             }
10         }
11     }
12     return classes;
13 }

  这个方法很简单,我们接着看loadExtensionClasses()

 1 private Map> loadExtensionClasses() {
 2     // 获取注解 SPI的接口
 3     // type为传入的扩展接口,必须有@SPI注解
 4     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 5     // 获取默认扩展实现value,如果存在,赋值给cachedDefaultName
 6     if (defaultAnnotation != null) {
 7         String value = defaultAnnotation.value();
 8         if ((value = value.trim()).length() > 0) {
 9             // @SPI value 只能是一个,不能为逗号分割的多个
10             // @SPI value为默认的扩展实现
11             String[] names = NAME_SEPARATOR.split(value);
12             if (names.length > 1) {
13                 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
14             }
15             if (names.length == 1)
16                 cachedDefaultName = names[0];
17         }
18     }
19     // 加载三个目录配置的扩展类
20     Map> extensionClasses = new HashMap>();
21     // META-INF/dubbo/internal
22     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
23     // META-INF/dubbo
24     loadDirectory(extensionClasses, DUBBO_DIRECTORY);
25     // META-INF/services/
26     loadDirectory(extensionClasses, SERVICES_DIRECTORY);
27     return extensionClasses;
28 }

  很熟悉吧,这个方法我们在第一篇文章中已经有介绍了,我们再接着往下看loadDirectory方法

 1 private void loadDirectory(Map> extensionClasses, String dir) {
 2     // 扩展配置文件完整文件路径+文件名
 3     String fileName = dir + type.getName();
 4     try {
 5 
 6         Enumeration urls;
 7         // 获取类加载器
 8         ClassLoader classLoader = findClassLoader();
 9         if (classLoader != null) {
10             urls = classLoader.getResources(fileName);
11         } else {
12             urls = ClassLoader.getSystemResources(fileName);
13         }
14         if (urls != null) {
15             while (urls.hasMoreElements()) {
16                 java.net.URL resourceURL = urls.nextElement();
17                 // 加载
18                 loadResource(extensionClasses, classLoader, resourceURL);
19             }
20         }
21     } catch (Throwable t) {
22         logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
23     }
24 }

  这个方法我们也很分析过了,再往下看loadResource()方法

 1 private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
 2         try {
 3             BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
 4             try {
 5                 String line;
 6                 while ((line = reader.readLine()) != null) {
 7                     // 字符#是注释开始标志,只取#前面的字符
 8                     final int ci = line.indexOf('#');
 9                     if (ci >= 0)
10                         line = line.substring(0, ci);
11                     line = line.trim();
12                     if (line.length() > 0) {
13                         try {
14                             String name = null;
15                             int i = line.indexOf('=');
16                             if (i > 0) {
17                                 // 解析出 name 和 实现类
18                                 name = line.substring(0, i).trim();
19                                 line = line.substring(i + 1).trim();
20                             }
21                             if (line.length() > 0) {
22                                 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
23                             }
24                         } catch (Throwable t) {
25                             IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
26                             exceptions.put(line, e);
27                         }
28                     }
29                 }
30             } finally {
31                 reader.close();
32             }
33         } catch (Throwable t) {
34             logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
35         }
36     }

  这里是解析配置文本文件的内容,通过反射获得Class,再调用loadClass(),我们接着往下

 1 private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException {
 2     // type是否为clazz的超类,clazz是否实现了type接口
 3     // 此处clazz 是扩展实现类的Class
 4     if (!type.isAssignableFrom(clazz)) {
 5         throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
 6     }
 7     // clazz是否注解了 Adaptive 自适应扩展
 8     // 不允许多个类注解Adaptive
 9     // 注解adaptive的实现类,赋值给cachedAdaptiveClass
10     if (clazz.isAnnotationPresent(Adaptive.class)) {
11         if (cachedAdaptiveClass == null) {
12             cachedAdaptiveClass = clazz;
13             // 不允许多个实现类都注解@Adaptive
14         } else if (!cachedAdaptiveClass.equals(clazz)) {
15             throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
16         }
17         // 是否为包装类,判断扩展类是否提供了参数是扩展点的构造函数
18     } else if (isWrapperClass(clazz)) {
19         Set> wrappers = cachedWrapperClasses;
20         if (wrappers == null) {
21             cachedWrapperClasses = new ConcurrentHashSet>();
22             wrappers = cachedWrapperClasses;
23         }
24         wrappers.add(clazz);
25         // 普通扩展类
26     } else {
27         // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
28         clazz.getConstructor();
29         // 此处name为 SPI配置中的key
30         // @SPI配置中key可以为空,此时key为扩展类的类名(getSimpleName())小写
31         if (name == null || name.length() == 0) {
32             // 兼容旧版本
33             name = findAnnotationName(clazz);
34             if (name.length() == 0) {
35                 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
36             }
37         }
38         // 逗号分割
39         String[] names = NAME_SEPARATOR.split(name);
40         if (names != null && names.length > 0) {
41             // 获取Activate注解
42             Activate activate = clazz.getAnnotation(Activate.class);
43             if (activate != null) {
44                 cachedActivates.put(names[0], activate);
45             }
46             for (String n : names) {
47                 if (!cachedNames.containsKey(clazz)) {
48                     cachedNames.put(clazz, n);
49                 }
50                 // name不能重复
51                 Class c = extensionClasses.get(n);
52                 if (c == null) {
53                     extensionClasses.put(n, clazz);
54                 } else if (c != clazz) {
55                     throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
56                 }
57             }
58         }
59     }
60 }

  我们在第10行,终于看到了Adaptive注解判断。

  如果扩展点实现类存在@Adaptive注解,Class对象赋值给cachedAdaptiveClass,并且在第14行判断是否存在多个类都是@Adaptive注解,如果同一个扩展点的多个实现类都有@Adaptive注解,则抛出异常。

  到这里,我们看到了扩展点自适应扩展点的类级别注解的调用及返回逻辑。其实前面也说过了,@Adaptive修饰类的场景并不多,也不是重点,重点是@Adaptive修饰方法的时候。

  我们返回到getAdaptiveExtensionClass()方法,为了清晰,我们再看看这个方法的代码

 1 private Class getAdaptiveExtensionClass() {
 2         // 通过SPI获取所有的扩展类,赋值相关的成员变量
 3         getExtensionClasses();
 4         // 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空
 5         if (cachedAdaptiveClass != null) {
 6             return cachedAdaptiveClass;
 7         }
 8         // 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类
 9         return cachedAdaptiveClass = createAdaptiveExtensionClass();
10 }

  通过前面的分析,如果@Adaptive没有修饰类,则cachedAdaptiveClass 为空,此时,我们会进入createAdaptiveExtensionClass(),这个方法是实现@Adaptive修饰方法的逻辑实现,也是自适应扩展的重点所在。

  我们来看createAdaptiveExtensionClass这个方法

1 private Class createAdaptiveExtensionClass() {
2     // 创建自适应扩展代码 字符串
3     String code = createAdaptiveExtensionClassCode();
4     ClassLoader classLoader = findClassLoader();
5     // 获取编译器实现类
6     com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
7     // 编译代码,获取自适应扩展类的Class
8     return compiler.compile(code, classLoader);
9 }

  这个方法实现的功能主要两点:

  1、通过createAdaptiveExtensionClassCode()获取创建的新类字符串

  2、通过Compiler编译器,编译新类字符串,获得新类的Class对象

  所以,我们的重点是分析新类字符串的实现逻辑,这也是自适应扩展的重点。

  我们接着看createAdaptiveExtensionClassCode()方法,这个方法有300多行,我们分段来看

 1 private String createAdaptiveExtensionClassCode() {
 2     StringBuilder codeBuilder = new StringBuilder();
 3     // 通过反射获取所有方法
 4     Method[] methods = type.getMethods();
 5     boolean hasAdaptiveAnnotation = false;
 6     // 遍历方法,判断至少有一个方法被@Adaptive修饰
 7     for (Method m : methods) {
 8         if (m.isAnnotationPresent(Adaptive.class)) {
 9             hasAdaptiveAnnotation = true;
10             break;
11         }
12     }
13     // no need to generate adaptive class since there's no adaptive method found.
14     // 没有被@Adaptive修饰的方法,抛出异常
15     if (!hasAdaptiveAnnotation)
16         throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
17     // 生成package代码:package+type所在包
18     codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
19     // 生成import代码:import+ExtensionLoader权限定名
20     codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
21     // 生成类代码:public class + type简单名称+$Adaptive+implements + type权限定名+{
22     codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
23     ...............
24     ...暂时省略....
25     ..............
26 }

  方法开始的逻辑很简单,拿到扩展点type的方法,循环方法列表,判断是否存在一个方法是被@Adaptive注解的,如果存在则继续,否则抛出异常。这也符合正常的逻辑,如果所有的方法都没@Adaptive注解,那么获取自适应扩展就没有意义了。

  从15行开始进行新类字符串的构造,我们看到了关键字”package“、”import“、”class“等,执行到22行处,生成的代码字符串,我们以扩展点Protocol为例,展示一下:

1 package com.alibaba.dubbo.rpc;
2 import com.alibaba.dubbo.common.extension.ExtensionLoader;
3 public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
4     // 省略方法代码
5 }

  这个类就是我们生成的新的扩展点实现类,我们可以看到类名以及实现的接口。
  我们接着往下分析,前面我们说过,扩展点方法分为两种,一个是有@Adaptive注解的,一个是无@Adaptive注解的,既然实现了扩展点接口,这两种方法都要在新类中实现。
  我们首先分析没有@Adaptive注解的方法。

 1 private String createAdaptiveExtensionClassCode() {
 2     ...............
 3     ...暂时省略....
 4     ..............
 5     // 生成方法
 6     for (Method method : methods) {
 7         // 方法返回类型
 8         Class rt = method.getReturnType();
 9         // 方法参数数组
10         Class[] pts = method.getParameterTypes();
11         // 方法异常数组
12         Class[] ets = method.getExceptionTypes();
13         // 方法的Adaptive注解
14         Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
15         StringBuilder code = new StringBuilder(512);
16         // 没有@Adaptive注解的方法,生成的方法体内为 throw 异常
17         if (adaptiveAnnotation == null) {
18             // throw new UnsupportedOperationException(
19             // "method " + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”)
20             code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()).append(" of interface ").append(type.getName()).append(" is not adaptive method!\");");
21         } else {
22          ...............
23          ...暂时省略....
24          ..............
25          }

  通过for循环,进行逐个方法生成。

  在第14行,获取到方法的@Adaptive注解,第17行进行判断,如果为空,生成的代码是抛出一个异常,示例如下:

1 throw new UnsupportedOperationException(
2             "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");

  我们接着分析存在@Adaptive注解的方法,生成代码的逻辑

 1 ...暂时省略....
 2 if (adaptiveAnnotation == null) {
 3         // throw new UnsupportedOperationException(
 4         // "method " + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”)
 5         code.append("throw new UnsupportedOperationException(\"method ").append(method.toString())
 6                 .append(" of interface ").append(type.getName()).append(" is not adaptive method!\");");
 7     } else {
 8         // 有@Adaptive注解的方法,参数中必须有URL,或是可以从方法参数中获取URL
 9         int urlTypeIndex = -1;
10         for (int i = 0; i < pts.length; ++i) {
11             if (pts[i].equals(URL.class)) {
12                 urlTypeIndex = i;
13                 break;
14             }
15         }
16         // found parameter in URL type
17         // 方法中存在URL
18         if (urlTypeIndex != -1) {
19             // Null Point check
20             // 为URL类型参数判断空代码,格式如下:
21             // if (arg + urlTypeIndex == null)
22             // throw new IllegalArgumentException("url == null");
23             String s = String.format(
24                     "\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", urlTypeIndex);
25             code.append(s);
26             // 为URL类型参数生成赋值代码,例如:URL url = arg1;
27             s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
28             code.append(s);
29         }
30 ...暂时省略....

  第10行,循环的变量pts 为方法参数数组,如果参数数组中有URL类型,数组下标赋值给urlTypeIndex,跳出循环。

  第18行进行urlTypeIndex 判断,此时如果不为-1,说明方法参数数组中存在URL类型的参数,生成的代码首先是非空判断,接着就是把对应的URL类型参数就行变量赋值。
  接着往下看

  

 1 ...暂时省略....
 2 else {
 3 String attribMethod = null;
 4 // find URL getter method
 5 // 遍历方法的参数类型
 6 LBL_PTS: for (int i = 0; i < pts.length; ++i) {
 7     // 方法参数类型的方法数组
 8     Method[] ms = pts[i].getMethods();
 9     for (Method m : ms) {
10         String name = m.getName();
11         // 1、方法名以get开头,或方法名大于3个字符
12         // 2、方法的访问权限是public
13         // 3、非静态方法
14         // 4、方法参数数量为0
15         // 5、方法返回值类型为URL
16         if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers())
17                 && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0
18                 && m.getReturnType() == URL.class) {
19             urlTypeIndex = i;
20             attribMethod = name;
21             // 结束for (int i = 0; i < pts.length; ++i)循环
22             break LBL_PTS;
23         }
24     }
25 }
26 ...暂时省略....

  上面的代码是参数数组中不存在URL类型参数的情况。如果不存在URL类型的参数,就需要从所有的入参中判断,参数对象中是否可以通过get方法 获取到URL对象。如果不可以则抛出异常。

  标签LBL_PTS用于结束最外部的循环。我们看到最外边的循环,还是参数数组pts。

  第8行是拿到参数数组中单个参数的所有方法,再进行循环,判断是否存在满足如下条件: 1、方法名以get开头,或方法名大于3个字符;2、方法的访问权限是public; 3、非静态方法;4、方法参数数量为0; 5、方法返回值类型为URL的方法,如果存在赋值方法名给attribMethod ,跳出最外变循环。

  我们接着往下看

1 ...暂时省略....
2 // 如果参数中都不包含可返回的URL的get方法,抛出异常
3 if (attribMethod == null) {
4     throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
5             + ": not found url parameter or url attribute in parameters of method "
6             + method.getName());
7 }
8 ...暂时省略....

  我们看到,如果attribMethod 为空,也就是前面的两个循环没有找到存在返回URL方法的参数对象,直接抛出异常,方法结束执行。

  如果attribMethod 不为空,即存在返回URL方法的参数对象,我们再往下看:

 1 ...暂时省略....
 2 // Null point check
 3 // 为可返回URL的参数生成判空代码,格式如下:
 4 // if (arg + urlTypeIndex == null)
 5 // throw new IllegalArgumentException("参数全限定名 + argument == null");
 6 String s = String.format(
 7         "\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
 8         urlTypeIndex, pts[urlTypeIndex].getName());
 9 code.append(s);
10 // 为 getter 方法返回的 URL 生成判空代码,格式如下:
11 // if (argN.getter方法名() == null)
12 // throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null);
13 s = String.format(
14         "\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
15         urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
16 code.append(s);
17 // 生成赋值语句,格式如下:
18 // URL全限定名 url = argN.getter方法名(),比如
19 // com.alibaba.dubbo.common.URL url = invoker.getUrl();
20 s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
21 code.append(s);
22 ...暂时省略....

  第6行生成新类代码是空判断,如果参数数组下标为urlTypeIndex的参数为空,抛出异常。

  第13行是返回URL类型方法的空判断,我们知道参数数组下标是urlTypeIndex的参数,存在返回URL类型的方法,方法名为attribMethod,此处就是判断方法attribMethod是否为空null。

  第20行就是执行attribMethod,赋值给url变量。

  至此,从入参中获得了URL类型的变量。前面是直接从参数数组中获取类型为URL的参数,后面是从参数数组中的某个可以返回URL参数方法的参数。两种方式目的就是获取到URL类型的变量,这个是必须的,因为自适应的扩展,获取扩展点的key是从URL中解析出来的。

  在获取到URL类型的变量后,现在就要获取关键字key了,根据key从URL中获取的value,就是自适应扩展点在配置文本文件中对应的key。

  我们接着往下看

 1 ...暂时省略....
 2 // 获取方法@Adaptive的注解值
 3 String[] value = adaptiveAnnotation.value();
 4 // value is not set, use the value generated from class name as the key
 5 // @Adaptive的value为空,需要特殊处理
 6 // 将类名转换为字符数组,然后遍历字符数组,并将字符存入StringBulder
 7 // 若字符为大写字母,则向StringBuiilder中添加“.”,随后字符变为小写存入StringBuilder
 8 // 比如LoadBalance经过处理,得到load.balance
 9 if (value.length == 0) {
10     // 获取类名,并将类名转换为字符数组
11     char[] charArray = type.getSimpleName().toCharArray();
12     StringBuilder sb = new StringBuilder(128);
13     // 遍历字符数组
14     for (int i = 0; i < charArray.length; i++) {
15         // 判断大小写
16         if (Character.isUpperCase(charArray[i])) {
17             if (i != 0) {
18                 // 大写字符时,加
19                 sb.append(".");
20             }
21             // 转为小写
22             sb.append(Character.toLowerCase(charArray[i]));
23         } else {
24             sb.append(charArray[i]);
25         }
26     }
27     value = new String[] { sb.toString() };
28 }
29 ...暂时省略....

  第3行,是直接从@Adaptive注解中获取value,类型为字符串数组。

  如果@Adaptive注解没有设置value的值,接着看第9行的判断。

  从第11行开始,自动生成从URL获取自适应扩展关键字的key。生成的逻辑是根据扩展点type的名称,遍历type名称的字符数组,除了首字符,遇到大写的字符前面加“.",大写转小写,组装的字符串就是要获取的value值。

  我们接着往下看

 1 ...暂时省略....
 2 // 检测方法列表中是否存在Invocation类型的参数
 3 // 若存在,则为其生成判空代码和其他一些代码
 4 boolean hasInvocation = false;
 5 for (int i = 0; i < pts.length; ++i) {
 6     // 判断参数名称是否等于 com.alibaba.dubbo.rpc.Invocation
 7     if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
 8         // Null Point check
 9         // 为Invocation 类型参数生成判空代码
10         String s = String.format(
11                 "\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
12         code.append(s);
13         // 生成getMethodName方法调用代码,格式为:
14         // String methodName = argN.getMethodName();
15         s = String.format("\nString methodName = arg%d.getMethodName();", i);
16         code.append(s);
17         // 设置hasInvocation为true
18         hasInvocation = true;
19         break;
20     }
21 }
22 ...暂时省略....

  这段代码选好pts,判断是否存在类型为Invocation的参数。如果存在生成为空判断,之后从Invocation类型的参数中获取methodName。并设置hasInvocation为true。

 1 ...暂时省略....
 2 /**
 3  * 根据SPI和Adaptive注解值生成“获取扩展名逻辑”,同时生成逻辑也受Invocation类型参数影响 生成格式如: String extName =
 4  * url.getMethodParameter(methodName, "loadbalance","random");
 5  */
 6 // 设置默认扩展名,cachedDefaultName源于SPI注解值,默认情况下,
 7 // SPI注解值为空串,此时cachedDefaultName 为 null
 8 String defaultExtName = cachedDefaultName;
 9 String getNameCode = null;
10 // 遍历value,value是Adaptive的注解值,上面有value的获取过程
11 // 此处循环的目的是生成从URL中获取扩展名的代码,生成的代码会赋值给getNameCode变量
12 // 这个循环的遍历顺序是由后向前遍历的
13 for (int i = value.length - 1; i >= 0; --i) {
14     // i为最后一个元素的坐标时
15     if (i == value.length - 1) {
16         // 默认扩展名非空
17         if (null != defaultExtName) {
18             // protocol是扩展名的一部分,可以通过getProtocol方法获取,其他则是从URL参数中获取
19             // 因为获取方式不同,因此要进行判断
20             if (!"protocol".equals(value[i])) {
21                 // hasInvocation 用于标识方法参数列表中是否有Invocation类型参数
22                 if (hasInvocation) {
23                     // 生成的代码功能等价于下面的代码:
24                     // url.getMethodParameter(methodName, value[i], defaultExtName)
25                     // 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下:
26                     // url.getMethodParameter(methodName, "loadbalance", "random")
27                     getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")",
28                             value[i], defaultExtName);
29                 } else {
30                     // 生成的代码功能等价于下面的代码:
31                     // url.getParameter(value[i], defaultExtName)
32                     getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i],
33                             defaultExtName);
34                 }
35             } else {
36                 // 生成的代码功能等价于下面的代码:
37                 // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
38                 getNameCode = String.format(
39                         "( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
40                 // 默认扩展名为空
41             }
42         } else {
43             if (!"protocol".equals(value[i])) {
44                 if (hasInvocation) {
45                     // 生成代码格式同上
46                     getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")",
47                             value[i], defaultExtName);
48                 } else {
49                     // 生成的代码功能等价于下面的代码:
50                     // url.getParameter(value[i])
51                     getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
52                 }
53             } else {
54                 // 生成从 url 中获取协议的代码,比如 "dubbo"
55                 getNameCode = "url.getProtocol()";
56             }
57         }
58     } else {
59         if (!"protocol".equals(value[i])) {
60             if (hasInvocation) {
61                 // 生成代码格式同上
62                 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")",
63                         value[i], defaultExtName);
64             } else {
65                 // 生成的代码功能等价于下面的代码:
66                 // url.getParameter(value[i], getNameCode)
67                 // 以 Transporter 接口的 connect 方法为例,最终生成的代码如下:
68                 // url.getParameter("client", url.getParameter("transporter", "netty"))
69                 getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
70             }
71         } else {
72             // 生成的代码功能等价于下面的代码:
73             // url.getProtocol() == null ? getNameCode : url.getProtocol()
74             // 以 Protocol 接口的 connect 方法为例,最终生成的代码如下:
75             // url.getProtocol() == null ? "dubbo" : url.getProtocol()
76             getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()",
77                     getNameCode);
78         }
79     }
80 }
81 ...暂时省略....

  第8行获取默认扩展名。来自于@SPI注解的value值。

  第13行开始循环value,此处的value是@Adaptive注解的内容,前面有过分析,如果@Adaptive没有设置value,则通过type名称解析出value。

  上面的代码分支比较多,但是主要是集中在protocol、hasInvocation的判断上。

  首先看hasInvocation,如果hasInvocation不为空,我们看到生成的代码是“url.getMethodParameter(methodName...”,这个methodName也是前面判断hasInvocation时获取到的。这等于在从URL中获取值的时候,加上了methodName。如果hasInvocation为空,此时的分支生成的代码,直接是“url.getParameter(.."。
第二个是判断protocol的分支,由于URL类中有单独的protocol变量,所以 如果value值为protocol,此时从URL中取值,可以直接调用url.getProtocol(),不需要通过URL的getParameter方法。
  上面的代码是从URL中获取扩展点key的主要逻辑,分支比较多,但是很多重复的代码,也进行了比较详细的注释。

  我们接着往下看,从URL中拿到扩展点的key后的代码

 1 code.append("\nString extName = ").append(getNameCode).append(";");
 2 // check extName == null?
 3 String s = String.format("\nif(extName == null) "
 4         + "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
 5         type.getName(), Arrays.toString(value));
 6 code.append(s);
 7 // 生成扩展获取代码,格式如下:
 8 // type 全限定名 extension = (type全限定名)ExtensionLoader全限定名
 9 // .getExtensionLoader(type全限定名.class).getExtension(extName);
10 // Tips: 格式化字符串中的 %
11 s = String.format("\n%s extension = (%,
12         type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
13 code.append(s);
14 
15 // return statement
16 // 如果方法返回值类型非 void,则生成 return 语句。
17 if (!rt.equals(void.class)) {
18     code.append("\nreturn ");
19 }
20 
21 s = String.format("extension.%s(", method.getName());
22 code.append(s);
23 for (int i = 0; i < pts.length; i++) {
24     if (i != 0)
25         code.append(", ");
26     code.append("arg").append(i);
27 }
28 code.append(");");

  这段代码就比较简单明了了,核心在第11行,强制转换为扩展点type类型,通过ExtensionLoader的getExtensionLoader获取type接口对应的ExtensionLoader实例。

  现在已经拿到的扩展点实现的key,只要调用ExtensionLoader实例的getExtension()方法,即可返回需要调用的扩展点实现。

  我们分析的主线是按扩展点的一个方法进行,每个被@Adaptive修饰的方法,生成的逻辑都是一样的,主要的逻辑是:

  1、根据@Adaptive注解的value,或是扩展点type的名称生成从URL获取扩展点实现类key的关键字
  2、根据第一步获取的关键字,从URL中获取要调用的扩展点实现类的key
  3、获取到扩展点实现类对应的key,调用ExtensionLoader实例的getExtension()方法,即可拿到对应的扩展点实现
  4、方法的执行是调用扩展点实现类的目标方法。

  至此新类的字符串已经生成了,我们回到createAdaptiveExtensionClass方法

 1 private Class createAdaptiveExtensionClass() {
 2     // 创建自适应扩展代码 字符串
 3     String code = createAdaptiveExtensionClassCode();
 4     ClassLoader classLoader = findClassLoader();
 5     // 获取编译器实现类
 6     com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader
 7             .getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
 8     // 编译代码,获取自适应扩展类的Class
 9     return compiler.compile(code, classLoader);
10 }

  第3行就是我们前面分析的获取新类字符串的方法,拿到code之后,再获取类加载器,获取编辑器,执行编译。返回的就是自适应扩展类的Class对象。

  通过此方法,再往上返回就是自适应扩展类的对象,以及缓存对象等逻辑。自适应扩展的获取基本就结束了。

四、总结

  通过上面的分析,我们基本了解的自适应扩展点的实现逻辑,难点就是@Adaptive注解方法时,生成新类的字符串之处。别的逻辑还算清晰。如果在读到此处有困惑,请评论留言,我会进行详细解释。

你可能感兴趣的:(Dubbo源码分析之SPI(三))