关于 java spi 的介绍能够参见下面这个帖子java
http://singleant.iteye.com/blog/1497259缓存
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制增强而来。ruby
Dubbo改进了JDK标准的SPI的如下问题:app
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。ide
示例:函数
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:url
xxx=com.alibaba.xxx.XxxProtocolspa
实现类内容:代理
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocol implemenets Protocol { // ... }
加载扩展实现, 在dubbo中都是经过ExtensionLoader实现的, 代码如:code
ExtensionLoader.getExtensionLoader(Container.class).getExtension("registry");
咱们以方法getExtensionLoader做为入口来分析加载的实现:
public static<T> ExtensionLoader<T> getExtensionLoader(Class<T> type){ if(type==null) throw new IllegalArgumentException("Extensiontype == null"); if(!type.isInterface()){ throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)){ thrownewIllegalArgumentException("Extensiontype("+type+ ") is not extension, because WITHOUT@" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader=(ExtensionLoader<T>)EXTENSION_LOADERS.get(type); if(loader==null){ EXTENSION_LOADERS.putIfAbsent(type,newExtensionLoader<T>(type)); loader=(ExtensionLoader<T>)EXTENSION_LOADERS.get(type); } return loader; }
此方法根据扩展类型, 获得一个扩展加载器(ExtensionLoader)
1, 经过方法开始部分的几个校验能够知道:
· 扩展类泛型类型不能为空
· 必须是一个接口.
· 此接口必须打上@SPI注解
2, 先从EXTENSION_LOADERS缓存里取, 取不到会实例化一个ExtensionLoader, 而且缓存起来;
在ExtensionLoader的构造器中, 会经过AdaptiveExtension方式获得一个ExtensionFactory; AdaptiveExtension及ExtensionFactory后面会再讲到.
3,获得ExtensionLoader以后, 再来看看getExtension(String name)方法
逻辑比较简单,
· 若是name 传一个 "true", 表示取默认扩展; getDefaultExtension(), 实现逻辑见4.
· 先从缓存cachedInstances里取, 取不到经过createExtension方法建立, 完了再放缓存.
4, 获取默认扩展的实现逻辑.getDefaultExtension
5, createExtension方法:
1) 经过getExtensionClasses方法, 取得全部的扩展类缓存, 并从中取得name对应的class. getExtensionClasses实现逻辑见6.
· 根据类型, 从EXTENSION_INSTANCES得到缓存的实例.
· 若是不存在, 则经过class.newInstance建立实例, 并缓存.
· 经过injectExtension方法, 给这个实例注入各类属性.injectExtension实现逻辑, 详见7.
· 若是待扩展的T类型,有Wrapper包装器类(构造函数有T入参),实例化,而且injectExtension:
Set<Class<?>> wrapperClasses = cachedWrapperClasses; if(wrapperClasses != null && wrapperClasses.size() > 0){ for(Class<?> wrapperClass : wrapperClasses){ instance = injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance)); } } return instance;
能够到看最后返回的是最后一个包装器类;以前的包装器类以及最开始最纯粹的扩展类实例, 只是调用了一下构造器方法, 而后传递给后面的包装器类; 这样就能在各级包装器类中添加各类扩展属性级扩展方法(装饰器模式).
包装类在loadFile加载扩展类的时候, 加载到cachedWrapperClasses缓存的.(loadFile实现逻辑详见8)
6.getExtensionClasses 获取全部的扩展类, 实现逻辑
1) 从cachedClasses缓存里取, 若是不存在, 经过loadExtensionClasses方法加载.
2) loadExtensionClasses实现逻辑:
a.经过getExtensionLoader(Class<T>type)初始传进来的type类型上的SPI注解的value值, 解析获得一个cachedDefaultName(这个就是默认扩展了.
b.经过loadFile方法(实现逻辑见8), 加载DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY三个目录下的扩展类汇总到extensionClasses缓存, 最后一块儿返回.
7.injectExtension方法,给实例注入属性的实现逻辑.
经过反射, 找到只有一个入参的set public方法, 获得参数类型.
经过ExtensionFactory获得这个属性的扩展实例, 若是存在的话, 就注入.
8, loadFile 经过配置文件加载扩展类的实现逻辑:
1)得到指定扩展配置文件名, 如:META-INF/dubbo/internal/com.alibaba.dubbo.common.compiler.Compiler
2)经过类加载器获得配置文件资源Url, 在类路径下可能会找到多个.
3)遍历, 读取配置文件中的每一行, 并解析, 配置行形如:javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
4)经过class.forName将等号后面的类, 转为类类型.
5)校验:
a.必须是T接口的实现类 type.isAssignableFrom(clazz)
b. 若是这个类有@Adaptive注解. 而且缓存到cachedAdaptiveClass, 若是cachedAdaptiveClass已经有了, 但不是当前解析获得的class, 说明该接口有多个@Adaptive注解扩展类, 报错.关于@Adaptive后面会再分析.
c.若是不是@Adaptive扩展类, 尝试得到该类带有该类类型为入参的构造函数
clazz.getConstructor(type);
存在, 则认为是包装器类, 加入到包装器缓存.
d.若是不是包装器类,
若是配置行, 仅有类名, 没有=号及等号左边的内容, 看看这个类上有没有@Extension注解, 而且注解的值与类名
若是仍是没有: 若是扩展类的类名是接口类类名结尾.
clazz.getSimpleName().length()>type.getSimpleName().length()
&&clazz.getSimpleName().endsWith(type.getSimpleName()
那么 扩展名就是去掉接口名以后, 前半部分.
不然报错.
这个扩展名, 容许逗号隔开, 配置多个
从扩展类上获取@Activate注解, 若是有, 存入cachedActivates缓存, 若是扩展名有多个, 只以第一个做为缓存key.
遍历每个扩展名, 放入缓存, 名称扩展类对应扩展名.同时放入extensionClasses缓存,扩展名对应扩展类.
ExtensionLoader 还可加载Activate类 getActivateExtension()
Adaptive实例,直到扩展点方法执行时才决定调用是一个扩展点实现。
扩展点方法调用会有URL参数(或是参数有URL成员)
这样依赖的扩展点也能够从URL拿到配置信息,全部的扩展点本身定好配置的Key后,配置信息从URL上从最外层传入。
Adaptive实例的逻辑是固定,指定提取的URL的Key,便可以代理真正的实现类上,能够动态生成。
在Dubbo的ExtensionLoader的扩展点类开对应的Adaptive实现是在加载扩展点里动态生成。指定提取的URL的Key经过@Adaptive注解在接口方法上提供。
下面是Dubbo的Transporter扩展点的代码:
public interface Transporter { @Adaptive({"server", "transport"}) Server bind(URL url, ChannelHandler handler) throws RemotingException; @Adaptive({"client", "transport"}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }
对于bind方法表示,Adaptive实现先查找"server"key,若是该Key没有值则找"transport"key值,来决定代理到哪一个实际扩展点。
调用扩展以下面的代码:
Protocol refprotocol= ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
下面是getAdaptiveExtension()方法的实现逻辑:
1, 从cachedAdaptiveInstance缓存获取Adaptive类实例, 若是不存在, 调用createAdaptiveExtension()方法建立, 并缓存
2, createAdaptiveExtension方法, 经过getAdaptiveExtensionClass()获得Adaptive类并实例化, 而且经过injectExtension方法注入各属性.
3, getAdaptiveExtensionClass()实现逻辑: 从缓存cachedAdaptiveClass中获取, 取不到调用createAdaptiveExtensionClass方法建立
4, 经过createAdaptiveExtensionClassCode方法生成@Adaptive类字节码, 而且经过Compiler类编译获得, Compiler也是经过ExtensionLoader getAdaptiveExtension获得的.
这里不会递归调用栈溢出吗?
因为在getExtensionClasses()中已经找了cachedAdaptiveClass(见8.5.b), 因此若是cachedAdaptiveClass仍然为空, 会去createAdaptiveExtensionClass() , 这样就会调用堆栈溢出. 这样就不难理解为啥8.5.b中, 若是找到多个@Adaptive注解的类会报异常.
Compiler有一个实现类加了@Adaptive注解, 因此不会再去createAdaptiveExtensionClass.
AdaptiveCompiler
@Adaptive
public class AdaptiveCompilerimplementsCompiler