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<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注解方法时,生成新类的字符串之处。别的逻辑还算清晰。如果在读到此处有困惑,请评论留言,我会进行详细解释。
上一篇: 列表推导式及系列应用
下一篇: jQuery插件,判断鼠标的移入移出方向