当前位置: 移动技术网 > IT编程>开发语言>Java > Dubbo源码分析之SPI(三)

Dubbo源码分析之SPI(三)

2019年12月18日  | 移动技术网IT编程  | 我要评论

苏豪鸿,彩酷酷双色球3Dcaikuku,火麻仁的功效

一、概述

  本篇介绍自适应扩展,方法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<compiler> 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<string, class<?>> getextensionclasses() {
 2     map<string, class<?>> 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<string, class<?>> 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<string, class<?>> extensionclasses = new hashmap<string, class<?>>();
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<string, class<?>> extensionclasses, string dir) {
 2     // 扩展配置文件完整文件路径+文件名
 3     string filename = dir + type.getname();
 4     try {
 5 
 6         enumeration<java.net.url> 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<string, class<?>> 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<string, class<?>> 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<class<?>> wrappers = cachedwrapperclasses;
20         if (wrappers == null) {
21             cachedwrapperclasses = new concurrenthashset<class<?>>();
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: 格式化字符串中的 %<s 表示使用前一个转换符所描述的参数,即 type 全限定名
11 s = string.format("\n%s extension = (%<s)%s.getextensionloader(%s.class).getextension(extname);",
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注解方法时,生成新类的字符串之处。别的逻辑还算清晰。如果在读到此处有困惑,请评论留言,我会进行详细解释。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网