dubbo源码(章节二) -- 内核探索之SPI
dubbo为什么不采用jdk的spi?
- jdk标准的spi会一次性实例化扩展点的所有实现,如果有扩展实现初始化很耗时,或者有的扩展实现没有使用到也会被加载,会造成资源浪费。
- dubbo增加了对扩展点的ioc和aop的支持,一个扩展点可以直接setter注入其他的扩展点。
dubbo spi的一些约定:
spi文件的存储路径:
meta-inf/dubbo/internal/com.xxx.protocol
其中com.xxx.protocal代表文件名,即接口的全路径名。
spi的文件格式定义为:
xxx=com.foo.xxxprotocol yyy=com.foo.yyyprotocol
这么定义的原因是:如果扩展实现中的静态属性或方法引用了某些第三方库,而该库又依赖缺失的话,就会导致这个扩展实现初始化失败。在这种情况下,dubbo无法定位失败的扩展id,所以无法精确定位异常信息。
dubbo spi的目的:
获取一个扩展实现类的对象。
extensionloader<t> getextensionloader(class<t> type)
为了获取到扩展实现类的对象,需要先为该接口获取一个extensionloader,缓存起来,之后通过这个extensionloader获取到对应的extension。
由extensionloader获取对应extension主要有两种方式:
/** * find the extension with the given name.
*/ getextension(string name)
通过给定的name获取对象。
/** * get activate extensions. */ getadaptiveextension()
获取一个扩展装饰类对象,dubbo的扩展接口有个规则,它所有的实现类中,要么有且仅有一个类被@adaptive标注,getadaptiveextension()返回的就是这个类对象,要么所有的实现类都没有@adaptive注解,此时,dubbo就动态创建一个代理类并由getadaptiveextension()返回。这块后面详细谈论。
下面先讨论extensionloader的获取
先从dubbo的第一行代码开始:
com.alibaba.dubbo.container.main.main(args);
从这里的main方法进入,就来到dubbo的main class,这里定义了一个静态初始化的变量loader,这是dubbo的第一个扩展点,我们从这里开始跟代码,
private static final extensionloader<container> loader =
extensionloader.getextensionloader(container.class);
进入getextensionloader内部,
1 private static final concurrentmap<class<?>, extensionloader<?>> extension_loaders =
new concurrenthashmap<class<?>, extensionloader<?>>(); 2 3 public static <t> extensionloader<t> getextensionloader(class<t> type) { 4 ...... 5 extensionloader<t> loader = (extensionloader<t>) extension_loaders.get(type); 6 if (loader == null) { 7 extension_loaders.putifabsent(type, new extensionloader<t>(type)); 8 loader = (extensionloader<t>) extension_loaders.get(type); 9 } 10 return loader; 11 }
这个方法的入参为class<t> type,标志了将要获取到的extensionloader的扩展接口的类型,此时实际的传入参数为container.class。
同时大家注意,我们前面说过,为扩展接口创建extensionloader时,所创建的extensionloader会被缓存起来,所以我们这里看到一个concurrenthashmap被申明用做缓存。
上述代码显示缓存中查询为null时,会创建一个extensionloader<t>,我们继续跟踪new extensionloader<t>(type),从代码第7行进入,
1 private extensionloader(class<?> type) { 2 this.type = type; 3 objectfactory = (type == extensionfactory.class ? null :
extensionloader.getextensionloader(extensionfactory.class).
getadaptiveextension()); 4 }
首先为扩展接口的类型type赋值,这里是container.class,大家注意一个细节,前面getextensionloader方法的入参是class<t>,而这里却是class<?>,由泛型变成了通配符,稍后解释原因。
第三行为objectfactory赋值,暂时先不管objectfactory的作用,我们先看主干逻辑,因为这里type是container.class,三元运算符进入后半部分,再次调用getextensionloader,并传入参数extensionfactory.class。
继续跟踪代码,在getextensionloader内部它又会去查询缓存,因为这里还是不存在extensionfactory.class的key,所以继续进入new extensionloader<t>(type)的逻辑,这次的传入参数是extensionfactory.class,所以objectfactory被赋值为null。
这里就可以看出来了,两次调用extensionloader的构造方法,入参分别为container.class和extensionfactory.class,所以构造方法的入参使用通配符,同时concurrenthashmap作为缓存,它的key也是class<?>。
ok,到目前为止,我们总结下调用链:
extensionloader.getextensionloader(container.class); -->this.type = type; -->objectfactory = extensionloader.getextensionloader(extensionfactory.class).
getadaptiveextension(); -->extensionloader.getextensionloader(extensionfactory.class); -->this.type = type; -->objectfactory = null;
缓存中应该有两项记录了,
{ "key": "container.class", "value": "......"
}, { "key": "extensionfactory.class", "value": "......"
}
总结下以上代码,每一个extensionloader都包含有两个属性type,objectfactory:
- class<t> type,初始化时要得到的接口名。
- extensionfactory objectfactory,初始化一个adaptiveextensionfactory。objectfactory的作用是:为dubbo的ioc提供所有对象,这个的实现原理还是相当复杂的,之后单开一篇来说。
ok,现在我们获取到了extensionloader,接下来就是获取extension了。
下面讨论getadaptiveextension()
回忆前面在创建extensionloader<container>时,要初始化它的objectfactory:
objectfactory = extensionloader.getextensionloader(extensionfactory.class)
.getadaptiveextension();
这里就是先获取extensionloader<extensionfactory>,之后调用了getadaptiveextension()。所以我们跟踪这个方法:
1 public t getadaptiveextension() { 2 object instance = cachedadaptiveinstance.get(); 3 if (instance == null) {
4 instance = createadaptiveextension(); 5 cachedadaptiveinstance.set(instance);
6 } 7 return (t) instance; 8 }
为了节约篇幅,非主干逻辑的代码这里做了省略,可以看到,这个方法主要是为了给变量cachedadaptiveinstance赋值。继续跟踪代码第四行:
1 private t createadaptiveextension() {
2 return injectextension((t) getadaptiveextensionclass().newinstance());
3 }
这里调用了getadaptiveextensionclass(),先获取扩展的类对象,再实例化出具体的扩展对象,我们进入getadaptiveextensionclass():
1 private class<?> getadaptiveextensionclass() { 2 getextensionclasses(); 3 if (cachedadaptiveclass != null) { 4 return cachedadaptiveclass; 5 } 6 return cachedadaptiveclass = createadaptiveextensionclass(); 7 }
这里涉及到一个全局变量cachedadaptiveclass,这个变量很重要,留意一下,稍后马上就会说到,这里我们先看getextensionclasses()做了什么:
private map<string, class<?>> getextensionclasses() {
map<string, class<?>> classes = cachedclasses.get();
if(classes == null){
classes = loadextensionclasses();
cachedclasses.set(classes);
}
return classes; }
省约了非主干逻辑,这里其实就是加载所有的extensionclasses,也就是该扩展接口type的所有实现类,继续跟进:
private static final string services_directory = "meta-inf/services/"; private static final string dubbo_directory = "meta-inf/dubbo/"; private static final string dubbo_internal_directory = dubbo_directory + "internal/"; private map<string, class<?>> loadextensionclasses() { ...... map<string, class<?>> extensionclasses = new hashmap<string, class<?>>(); loaddirectory(extensionclasses, dubbo_internal_directory); loaddirectory(extensionclasses, dubbo_directory); loaddirectory(extensionclasses, services_directory); return extensionclasses; }
这里依次加载三个目录,由于我们知道spi的存储路径就是meta-inf/dubbo/internal/,所有我们这里暂时只关注第一个loaddirectory即可,
1 private void loaddirectory(map<string, class<?>> extensionclasses, string dir) { 2 string filename = dir + type.getname();
3 enumeration<java.net.url> urls; 4 classloader classloader = findclassloader();
5 urls = classloader.getresources(filename);
6 while (urls.hasmoreelements()) { 7 java.net.url resourceurl = urls.nextelement(); 8 loadresource(extensionclasses, classloader, resourceurl); 9 }
10 }
这里传入的dir就是dubbo spi的存储路径meta-inf/dubbo/internal/,type.getname()获得扩展接口的类路径,两者拼接就得到一个dubbo spi的完整路径,这里的话就是:meta-inf/dubbo/internal/com.alibaba.dubbo.common.extension.extensionfactory。
获取该路径下的所有url资源,逐个处理,我们跟踪方法loadresource(......),
1 private void loadresource(......) {
2 ......
3 string line; 4 while ((line = reader.readline()) != null) {
5 int i = line.indexof('=');
6 string name = line.substring(0, i).trim();
7 line = line.substring(i+1).trim();
8 loadclass(......, class.forname(line, true, classloader), name);
9 }
10 }
这个类的作用就是通过传入的url,读取文件,并逐行处理,前面说过dubbo spi的文件格式为xxx=com.foo.xxx,所以这里解析字符串就可以拿到扩展实现类的类名及对应的类路径,下一步就是加载这些实现类了,再这之前我们不妨先看看extensionfactory的扩展实现都有哪些,手动打开meta-inf/dubbo/internal/com.alibaba.dubbo.common.extension.extensionfactory文件,查看里面的内容,
adaptive=com.alibaba.dubbo.common.extension.factory.adaptiveextensionfactory spi=com.alibaba.dubbo.common.extension.factory.spiextensionfactory
我们看到有两个实现,分别是adaptiveextensionfactory、spiextensionfactory,下面就将分别加载它们,
1 private void loadclass(......, class<?> clazz, string name) {
2 if (clazz.isannotationpresent(adaptive.class)) { 3 if (cachedadaptiveclass == null) { 4 cachedadaptiveclass = clazz; 5 } else if (!cachedadaptiveclass.equals(clazz)) { 6 throw new illegalstateexception("more than 1 adaptive class found: "......)
7 } 8 } else if (iswrapperclass(clazz)) { 9 set<class<?>> wrappers = cachedwrapperclasses; 10 if (wrappers == null) { 11 cachedwrapperclasses = new concurrenthashset<class<?>>(); 12 wrappers = cachedwrapperclasses; 13 } 14 wrappers.add(clazz); 15 } else { 16 clazz.getconstructor();
17 ......
18 cachedactivates.put(name, activate);
19 cachednames.put(clazz, name);
20 extensionclasses.put(name, clazz);
21 } 22 }
1 private boolean iswrapperclass(class<?> clazz) { 2 try { 3 clazz.getconstructor(type); 4 return true; 5 } catch (nosuchmethodexception e) { 6 return false; 7 } 8 }
程序第二行判断如果当前类拥有@adaotive注解,则为全局变量cachedadaptiveclass赋值,否则,程序第八行判断如果当前类不包含@adpative注解,且当前类的构造器方法包含目标接口(type)类型,则当前类被加入cachedwrapperclasses缓存,否则,如果当前类即不被@adaptive注解,也没有type类型的构造器,它最终会被加入到extensionclasses中,extensionclasses最终赋值给了cachedclasses。
注意代码第五行,cachedadaptiveclass是class<?>类型的变量,也就是说,只能是一个值,那么当我们逐行加载spi配置文件里的类时,如果有两个类都标注了@adaptive注解呢?第二个被标注的类会直接抛异常,因为此时cachedadaptiveclass已经被赋值了第一个类的类型,它当然不会equals第二个类了,由此就证明了我们开篇提到的dubbo spi的规则,它的所有实现类中,最多有一个被@adaptive注解。
ok,到了这里,加载扩展classes的过程就结束了,我们回到getextensionclasses()的调用处,在方法getadaptiveextensionclass()中,完成了classes的加载之后,接下来就判断全局变量cachedadaptiveclass的值,如果不为null,则表明该变量在上述类加载过程中被赋值了,我们前面描述了,这个值就只能是一个带有@adaptive注解的扩展类。如果该变量仍然为null,则表明这个扩展接口没有一个实现类带有@adaptive注解,大家回忆一下,前面我们说过dubbo spi的规则,它要么只有一个实现类被@adaptive注解,要么就没有,如果它没有一个实现类被@adaptive注解,那么就动态创建一个代理类,这句话就体现在这里了,显而易见,方法createadaptiveextensionclass()就将被用来完成这件事。
1 private class<?> createadaptiveextensionclass() { 2 string code = createadaptiveextensionclasscode(); 3 classloader classloader = findclassloader(); 4 compiler compiler = extensionloader.getextensionloader(compiler.class)
5 .getadaptiveextension(); 6 return compiler.compile(code, classloader); 7 }
这个过程分四步来做,首先在程序第二行,将动态生成一个代理类,这个的实现代码非常繁琐,这里不贴出来了,其原理就是通过一个adpative类的模板来生成代理类,我总结下模板给大家参考,
package <扩展点接口所在包>
public class <扩展点接口名>$adaptive implements <扩展点接口>{ public <有@adaptive注解的接口方法>(参数){
//这里生成代理对象,执行方法
} }
也就是说,代理类只会生成接口中被@adaptive注解了的方法,如果试图在代理类中调用接口中没有标注@adaptive的方法,程序会抛出异常。
因为这里当前的type是extensionfactory,这个接口是拥有一个被@adaptive注解了的类的,所以我们debug到这里会直接获得adaptiveextensionfactory,不会触发动态生成代理类的过程,为了了解动态代理类到底长什么样子,这里我给大家举个例子,protocol是dubbo的一个spi接口,它的所有实现类没有一个被@adaptive注解,所以如果我们执行下面这句代码,
extensionloader.getextensionloader(protocol.class).getadaptiveextension();
它在通过spi配置文件的类加载中得不到一个adaptive的实现类,所以代码最终会进入createadaptiveextensionclass()方法中,我们通过debug拿到string code的值,如下,
1 package com.alibaba.dubbo.rpc; 2 import com.alibaba.dubbo.common.extension.extensionloader;
3 4 public class protocol$adaptive implements com.alibaba.dubbo.rpc.protocol { 5 public void destroy() {
6 throw new exception("method destroy() of protocol is not adaptive method!"); 7 }
8 9 public int getdefaultport() {
10 throw new exception("... not adaptive method!"); 11 }
12 13 public exporter export(invoker arg0) throws rpcexception {
14 ......
15 protocol extension = (protocol)extensionloader.getextensionloader(protocol.class)
16 .getextension(extname); 17 return extension.export(arg0); 18 }
19 20 public invoker refer(class arg0, url arg1) throws rpcexception { 21 ......
22 protocol extension = (protocol)extensionloader.getextensionloader(protocol.class)
23 .getextension(extname); 24 return extension.refer(arg0, arg1); 25 } 26 }
我们对比protocol接口的定义来看,
1 @spi("dubbo") 2 public interface protocol { 3 4 int getdefaultport(); 5 6 @adaptive 7 <t> exporter<t> export(invoker<t> invoker) throws rpcexception; 8 9 @adaptive 10 <t> invoker<t> refer(class<t> type, url url) throws rpcexception; 11 12 void destroy(); 13 }
可以看到,方法export和refer被标注了@adaptive注解,所以在代理类中生成了代理方法,为什么说是代理方法呢,因为在方法内部又生成了一个protocol对象,通过这个对象完成了方法调用,对象extension就是一个不折不扣的代理对象。另外,在接口中方法getdefaultport()和方法destroy()没有被标注@adaptive注解,所以在代理类中它们没有被实现。
生成了代理类之后,下一步,就是动态编译这个代理类,关于动态编译的内容,之后单独开一篇来讨论。另外这里从代码中可以看到,compiler也是由spi获得的,实际上,dubbo所有扩展对象的获取都是通过spi完成的,故而我们也说spi是dubbo内核的灵魂之所在。
ok,不论是通过配置文件加载,还是通过动态编译生成代理类,到这里为止,getadaptiveextensionclass()方法就执行完了,我们终于获得了扩展类的类对象,但这还不是扩展对象,继续回到方法getadaptiveextensionclass()的调用处,
1 private t createadaptiveextension() {
2 return injectextension((t) getadaptiveextensionclass().newinstance());
3 }
这里我们拿刚刚返回的类对象new了一个instance出来,之后就作为参数被传入injectextension()方法中了,这个方法会进入dubbo的ioc,控制反转,实现扩展对象的动态注入,ioc的相关内容下一篇文章再做讨论。
dubbo的adaptive spi,主要逻辑到这里就梳理完成了,spi是dubbo内核的灵魂,同时它的实现原理也是颇复杂的,这里提到的几段代码需要结合debug反复跟踪,不断琢磨。
上一篇: Asp.Net MVC记住用户登录信息下次直接登录功能
下一篇: 百度霸屏5大引流方法,人人可操作