版本2.7.1
Dubbo有官方源码解读
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。
Dubbo的SPI主要改进了JDK的SPI实现:
1,JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
2,如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
3,增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点
关于拓展点:Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。
Dubbo SPI 约定
SPI文件的存储路径在以下三个文件路径:
- META-INF/dubbo/internal/ dubbo内部实现的各种扩展都放在了这个目录了
- META-INF/dubbo/
- META-INF/services/
文件名为接口的全限定名(包名+类名),内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
对于在扩展类的 jar 包内 ,放置扩展点配置文件 META-INF/dubbo/接口全限定名。
相关类注解说明
Dubbo SPI核心类是ExtensionLoader。每个SPI接口都会对应一个ExtensionLoader实例。
相关注解
- @SPI: 拓展点接口上,可指定默认值
- @Adaptive:这个注解和@SPI注解配置使用,用于它可以标注在SPI接口扩展实现类上,也可以标注在SPI接口的方法上。如果注解在标注在SPI接口的方法上说明就是一个动态代理类,它会通过dubbo里
com.alibaba.dubbo.common.compiler.CompilerSPI
接口通过字节码技术来创建对象。创建出来的对象名格式为SPI接口$Adaptive
,例如Protocol接口创建的SPI对象为Protocol$Adaptive。 - @Activate: 是一个 Duboo 框架提供的注解。在 Dubbo 官方文档上有记载:
对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等, 可以同时加载多个实现,此时,可以用自动激活来简化配置。
关于Wrapper包装类
Wrapper: Dubbo在加载某个接口的扩展类时候,若其没有被@Adaptive标注,且其有单把该接口作为参数的构造函数的情况下,那么它就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类。
由于代码封装的好,导致调用链有点长,这里先贴个所调用到的方法
ExtensionLoader
Dubbo SPI核心类是ExtensionLoader,通过 ExtensionLoader 的 getExtensionLoader
方法获取该接口对应的 ExtensionLoader 对象,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象
几个重要方法:
getExtensionLoader(Class type)
从缓存中获取ExtensionLoader,然后缓存起来getAdaptiveExtension()
获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。getExtension(String name)
获取一个指定对象getActivateExtension(URL url, String[] values, String group)
:方法主要获取当前扩展的所有可自动激活的实现标注了@Activate注解
在开始分析这些方法先贴一些要用到的变量:实现过程对配置名,实现类Class对象,实现类对象等各种关系做了缓存,这里先全部贴出来,不用管这直接在实际代码中来看
// map<拓展点接口, ExtensionLoader>,每个拓展点接口都有一个对应的ExtensionLoader对象
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS =
new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
// 拓展点接口在@SPI注解中指定的实现类的名字
private String cachedDefaultName;
//是个ConcurrentHashSet,存储该接口所有的包装类Class对象
private Set<Class<?>> cachedWrapperClasses;
//当厂商为该拓展点接口指定的实现类即没有被Adaptive标注也非接口的包装类
//但其被@Activate标注,便将其存储进cachedActivates
//key为第一个配置名name,因为可能是:name1,name2=xxxxx的情况
//value为实现类对象
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<String, Object>();
//key为实现类的class对象,该实现类即没有被Adaptive标注也非接口的包装类
//value为第一个配置名name
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
//该Holder的value是个map,其key是属性名,value是实现类的class对象
//该实现类即没有被Adaptive标注也非接口的包装类
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
//在介绍下面这两个变量之前先介绍一个名词:自适应扩展点实现类
//对于SPI接口,首先去指定目录找文件名为接口全限定名的文件,加载文件里指定的类
//但是若该指定类并非是@Adaptive标注的类,则Dubbo会为该接口生成一个类作为该接口的实现类
//这样的类叫做自适应扩展点实现类,这种类的名字也很特殊,<拓展点接口名>$Adpative
//如Protocol$Adpative
// 拓展点接口实现类对象,被存储进Holder里的value,该类就是个简单的存储类
// 其value是volatile的,cachedAdaptiveInstance对象在创建实现类的操作中充当锁
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
// volatile的,拓展点接口实现类的Class对象
private volatile Class<?> cachedAdaptiveClass = null;
先总述下下面代码的逻辑:去约定的目录下查找文件名为拓展点接口全限定名的文件,加载该文件中指定的实现类,若该指定类上没有@Adaptive标注,而在该拓展点接口方法上有@Adaptive标注,则生成自适应扩展点实现类对象作为其实现类。在这一过程中对多种对象等信息进行了缓存,以用于其它的功能,就如上面介绍的那些。
在阅读代码中带着这三个问题:1,类加载器如何选择。2,线程安全问题。3,如何扫描遍历文件。
1,getExtensionLoader
初始化一个ExtensionLoader,缓存起来。并为非ExtensionFactory的对象创建一个objectFactory用来依赖注入。
// map<拓展点接口, ExtensionLoader>,
// 每个拓展点接口都有一个对应的ExtensionLoader对象
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>>
EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not interface!");
}
//接口需要@SPI注解
// type.isAnnotationPresent(SPI.class)
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//从缓存中获取该SPI接口对应的ExtensionLoader,没有就创建
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
接口对应的ExtensionLoader,利用其来获取拓展实现类。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null :
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());