相对于api 来说我们都熟悉,但是spi知道的较少一些,因为我们大多是使用api,参与开源项目较少,spi是给拓展者使用的,对于一个好的开源框架来说,有必要留一些拓展点让参与者尽量黑盒拓展,而不是白盒修改代码,否则的话框架作者能做的功能,拓展者也在做的话,那么分支、合并将很难管理。简单来说就是为某个接口寻找服务实现的机制。
从使用的层面来看的话,就是动态的给接口添加实现类,有点像IOC的思想,将装配的控制权向外转移。比如我们在浏览器安装插件,而不是拆了重新组装。
接口和具体实现类
package com.mcgj.spi;
/**
* Created by zjc on 2018/7/15.
*/
public interface ILookTheWorldCup {
void look();
}
package com.mcgj.spi.impl;
import com.mcgj.spi.ILookTheWorldCup;
/**
* Created by zjc on 2018/7/15.
*/
public class LookCroatiaImpl implements ILookTheWorldCup {
@Override
public void look() {
System.out.println("克罗地亚");
}
}
import com.mcgj.spi.ILookTheWorldCup;
/**
* Created by zjc on 2018/7/15.
*/
public class LookFranceImpl implements ILookTheWorldCup {
@Override
public void look() {
System.out.println("法国");
}
}
新建个配置文件,将实现类的全限定名粘贴,并把配置文件放到META-INF/services/接口全限定名 (为什么是这个路径下?约定--->这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)META-INF/dubbo/internal/ //dubbo内部实现的各种扩展都放在了这个目录了;META-INF/dubbo/ META-INF/services/)
com.mcgj.spi.impl.LookCroatiaImpl
com.mcgj.spi.impl.LookFranceImpl
目录结构:
测试结果:
package com.mcgj;
import com.mcgj.spi.ILookTheWorldCup;
import org.junit.Test;
import java.util.ServiceLoader;
/**
* Created by zjc on 2018/7/15.
*/
public class TestSpi {
@Test
public void test() throws Exception{
ServiceLoader loaders=ServiceLoader.load(ILookTheWorldCup.class);
for (ILookTheWorldCup lookTheWorldCup:loaders){
lookTheWorldCup.look();
}
}
}
我们以Protocol接口为例, 接口上打上SPI注解,默认扩展点名字为dubbo
@SPI("dubbo") public interface Protocol{ }
Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo
@SPI("javassist")
public interface Compiler {
//省略...
}
public Class> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name); //通过key的name获取配置文件中的编译方式
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}
//com.alibaba.dubbo.common.compiler.Compiler 文件配置如下
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
@SPI("dubbo")//@SPI("dubbo")代表默认的spi对象,比如Protocol默认使用的是dubbo,可通过
public interface Protocol{
}
其实这个功能不用spi,使用spring的ioc也可以通过配置文件来实现动态注入不同的实现类啊,但是我们在从dubbo的官网可以看到这句话:
理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。
源码的扩展点框架主要在 com.alibaba.dubbo.common.extension
结构如下:
com.alibaba.dubbo.common.extension
|
|--factory
| |--AdaptiveExtensionFactory #稍后解释
| |--SpiExtensionFactory #稍后解释
|
|--support
| |--ActivateComparator
|
|--Activate #自动激活加载扩展的注解
|--Adaptive #自适应扩展点的注解
|--ExtensionFactory #扩展点对象生成工厂接口
|--ExtensionLoader #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类
|--SPI #扩展点注解
ExtensionLoader
没有提供public
的构造方法,但是提供了一个public static
的getExtensionLoader
,这个方法就是获取ExtensionLoader
实例的工厂方法。其public
成员方法中有三个比较重要的方法:
ExtensionLoader
实例仅负责加载特定SPI
扩展的实现*。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader
实例,下面我们就来看一下获取ExtensionLoader
实例的工厂方法getExtensionLoader
:
public static ExtensionLoader getExtensionLoader(Class type) {
if (type == null) //拓展点类型非空判断
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) { //拓展点只能是接口
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) { // 只接受使用@SPI注解注释的接口类型,否则抛异常
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// 先从静态缓存 EXTENSION_LOADERS中获取对应的ExtensionLoader实例
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); // 为Extension类型创建ExtensionLoader实例,并放入静态缓存
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader
实例。
ExtensionLoader
的私有构造函数:
private ExtensionLoader(Class> type) {
this.type = type;
// 如果扩展类型是ExtensionFactory,那么则设置为null
// 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类)
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
保存对应的扩展类型,并且设置了一个额外的objectFactory属性,它是一个ExtensionFactory类型,ExtensionFactory用于加载扩展的实现:
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
T getExtension(Class type, String name);
}
我们看到@SPI注解在此接口上,说明他也是一个扩展点,从上述的结构图中可以看出dubbo内部提供了两个实现类SpiExtensionFactory
和 AdaptiveExtensionFactory,从ExtensionLoader
的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory
是,object
字段被设置为null。由于ExtensionLoader
的使用范围有限(基本上局限在ExtensionLoader
中),因此对他做了特殊对待:在需要使用ExtensionFactory
的地方,都是通过对应的自适应实现来代替。
ExtensionFactory
实现类中,AdaptiveExtensionFactotry
被@Adaptive
注解注释,也就是它就是ExtensionFactory
对应的自适应 扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive
注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory
调用的地方,实际上调用的都是AdpativeExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List factories;
public AdaptiveExtensionFactory() {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
public T getExtension(Class type, String name) {
// 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回
// 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
看到这里,明白了这货相当于入口,遍历当前系统中所有的ExtensionFactory
实现来获取指定的扩展实现,获取到扩展实现或遍历完所有的ExtensionFactory
实现。这里调用了ExtensionLoader
的getSupportedExtensions
方法来获取ExtensionFactory
的所有实现,又回到了ExtensionLoader
类。
现在我们在看看像刚才提到的那三个比较重要的方法 具体调用流程:
getExtension
getExtension(name)
-> createExtension(name) #如果无缓存则创建
-> getExtensionClasses().get(name) #获取name对应的扩展类型
-> 实例化扩展类
-> injectExtension(instance) # 扩展点注入
-> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入
getAdaptiveExtension
public T getAdaptiveExtension()
-> createAdaptiveExtension() #如果无缓存则创建
-> getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass
-> getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive
-> createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类
-> createAdaptiveExtensionClassCode() #动态生成实现类java代码
-> compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化
-> injectExtension(instance)
getActivateExtesion
该方法有多个重载方法,不过最终都是调用了三个参数的那一个重载形式。其代码结构也相对剪短,就不需要在列出概要流程了。
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) { // 判断是否是获取默认实现
return getDefaultExtension();
}
Holder
这方法中也使用了在调用getExtensionClasses
方法的时候收集并缓存的数据,其中涉及到名字和具体实现类型对应关系的缓存属性是cachedClasses
。
private T createExtension(String name) {
Class> clazz = getExtensionClasses().get(name); // getExtensionClass内部使用cachedClasses缓存
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz); // 从已创建Extension实例缓存中获取
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance); // 属性注入
// Wrapper类型进行包装,层层包裹
Set> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
怎么处理缓存的呢?魔鬼都藏在细节当中!
public class Holder {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
这个类用于保存一个值,并且给值添加 volatile
来保证线程的可见性.
static
修饰方法,是属于类级别的,优先级高,) 内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,dubbo对扩展实现中公有的set方法且入参个数为一个的方法,
尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。
dubbo内部默认实现的对象工厂是SpiExtensionFactory和SrpingExtensionFactory,
他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。
1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,
所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。
2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象
下面 给出整体活动图