dubbo源码(章节二) -- 内核探索之SPI
2018-10-19 06:29:45来源:博客园 阅读 ()
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反复跟踪,不断琢磨。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 你说研究过Spring里面的源码,循环依赖你会么? 2020-06-09
- Dubbo+Zookeeper集群案例 2020-06-09
- 通俗理解spring源码(六)—— 默认标签(import、alias、be 2020-06-07
- 最强Dubbo面试题,附带超级详细答案 2020-06-06
- 学习源码的第八个月,我成了Spring的开源贡献者 2020-06-02
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash