SPI
简介
SPI 的全称是Service Provider Interface,是一种服务提供发现机制。
Java SPI
Java SPI使用了策略模式,一个接口多种实现。我们只声明接口,具体的实现并不在程序中直接确定,而是由程序之外的配置掌控,用于具体实现的装配。具体步骤如下:
- 定义一个接口及对应的方法。
- 编写该接口的一个实现类。
- 在
META-INF/services/目录下,创建一个以接口全路径命名的文件,如com.test.spi.PrintService0 - 文件内容为具体实现类的全路径名,如果有多个,则用分行符分隔。
- 在代码中通过
java.util.ServiceLoader来加载具体的实现类。
Java SPI 示例
servicepublic interface PrintService {void print();}实现类1public class ChinesePrintServiceImpl implements PrintService {@Overridepublic void print() {System.out.println("中文");}}实现类2public class EnglishPrintServiceImpl implements PrintService {@Overridepublic void print() {System.out.println("English");}}文件META-INF/services/top.fuyuaaa.spidemo.PrintService文件内容top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImpltop.fuyuaaa.spidemo.javaspi.EnglishPrintServiceImpl测试类public class JavaSPIDemo {public static void main(String[] args) {ServiceLoader<PrintService> printServices = ServiceLoader.load(PrintService.class);printServices.forEach(PrintService::print);}}结果中文English
Dubbo SPI
与Java SPI相比,Dubbo SPI做了一定的改进和优化。Dubbo SPI 改进了Java SPI以下几个问题。
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- Dubbo增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
Dubbo SPI 示例
@SPI("chinese")public interface PrintService {void print();}文件META-INF/dubbo/top.fuyuaaa.spidemo.PrintService文件内容chinese=top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImplenglish=top.fuyuaaa.spidemo.javaspi.EnglishPrintServiceImpl测试类public class DubboSPIDemo {public static void main(String[] args) {ExtensionLoader<PrintService> extensionLoader = ExtensionLoader.getExtensionLoader(PrintService.class);PrintService defaultPrintService = extensionLoader.getDefaultExtension();defaultPrintService.print();}}结果中文
Dubbo SPI 特性
扩展点自动包装
ExtensionLoader在加载扩展时,如果扩展类存在 一个参数为扩展点构造函数,则该类会被识别成Wrapper(包装)类。
🌰如下:PrintServiceWrapper实现了扩展点PrintService,也与其他扩展类进行了一样的配置,但是PrintServiceWrapper有一个参数为PrintService的构造函数,此时PrintServiceWrapper会被识别称为一个Wrapper类,包装在真正的扩展点之外,有点类似AOP。当通过ExtensionLoader.getExtensionLoader(PrintService.class)获取扩展类时,得到的其实是包装类PrintServiceWrapper,并且包装类可以存在多个,如【包装类[包装类(扩展类)]】。
public class PrintServiceWrapper implements PrintService {private final PrintService printService;public PrintServiceWrapper(PrintService printService) {this.printService = printService;}@Overridepublic void print() {System.out.println("做一下前置工作");printService.print();System.out.println("做一下后置工作");}}在文件META-INF/dubbo/top.fuyuaaa.spidemo.PrintService添加内容wrapperTest=top.fuyuaaa.spidemo.PrintServiceWrapper
在加载扩展类时,会判断是否为包装类,如果是包装类,则会将其加入到cachedWrapperClasses。
ExtensionLoader#loadClass代码如下所示:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {//省略无关代码//判断是否包装类else if (isWrapperClass(clazz)) {Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();wrappers = cachedWrapperClasses;}wrappers.add(clazz);}//省略无关代码}private boolean isWrapperClass(Class<?> clazz) {try {clazz.getConstructor(type);return true;} catch (NoSuchMethodException e) {return false;}}
在创建扩展类时,会判断cachedWrapperClasses是否为空,如果不为空,会在原扩展类上进行包装,如【包装类[包装类(扩展类)]】。
ExrensionLoader#createExtension代码如下所示:
private T createExtension(String name) {//省略无关代码//依赖注入injectExtension(instance);Set<Class<?>> wrapperClasses = cachedWrapperClasses;//循环装饰,如果有多个装饰者,在生成一个装饰类之后,又会用这个装饰类生成下一个装饰类if (wrapperClasses != null && !wrapperClasses.isEmpty()) {for (Class<?> wrapperClass : wrapperClasses) {//先生成装饰类实例,再进行装饰类的依赖注入instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;//省略无关代码}
扩展点自动装配
加载扩展点时,自动注入依赖的扩展点。加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader在会自动注入依赖的扩展点。ExtensionLoader通过扫描扩展点实现类的所有setter方法来判定其成员。
自动注入依赖主要通过ExtensionLoader#injectExtension方法,injectExtension主要作用是对扩展类进行依赖注入,也就是Dubbo IOC。实现原理比较简单,首先通过反射获取类的所有方法,然后遍历以字符串set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例,如果找到,就设值进去。
代码如下所示:
private T injectExtension(T instance) {try {if (objectFactory != null) {//遍历扩展点的方法,如果是public的 有参的 set方法,会通过set后的名字 以及 参数类型 去查询扩展点。for (Method method : instance.getClass().getMethods()) {if (method.getName().startsWith("set")&& method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) {//..省略注解判断//参数类型Class<?> pt = method.getParameterTypes()[0];try {//参数名String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";//获取依赖的对象,AdaptiveExtensionFactory.getExtensionObject object = objectFactory.getExtension(pt, property);//反射调用set方法设置依赖if (object != null) {method.invoke(instance, object);}} //...省略catch}}}} //...省略catchreturn instance;}
扩展点自适应
扩展点自适应机制,主要做的事情就是通过@Adaptive注解在调用时通过URL参数决定调用哪一个扩展类的方法。
🌰如下:PrintService#print(URL url)注解了@Adaptive,并且指定参数名为language,然后在DubboSPIDemo#main方法中构建URL时传入了language=english,在调用print(URL url)方法时,根据参数language,选择了扩展类EnglishPrintServiceImpl#print(URL url),因为在配置文件里指定了english=top.fuyuaaa.spidemo.javaspi.EnglishPrintServiceImpl
@SPI("chinese")public interface PrintService {@Adaptive("language")void print(URL url);}public class ChinesePrintServiceImpl implements PrintService {@Overridepublic void print(URL url) {System.out.println("中文URL");}}public class EnglishPrintServiceImpl implements PrintService {@Overridepublic void print(URL url) {System.out.println("English URL");}}public class DubboSPIDemo {public static void main(String[] args) {//构建URL,指定language参数值为englishURL url = new URL("test", "test", 8080, "test", Collections.singletonMap("language", "english"));PrintService adaptiveExtension = extensionLoader.getAdaptiveExtension();adaptiveExtension.print(url);}}结果English URL
在加载自适应扩展类时,Dubbo会为拓展接口生成具有代理功能的代码。然后通过javassist或jdk编译这段代码,得到 Class 类,最后再通过反射创建代理类。如下图所示,debug获取到的PrintService其实是PrintService$Adaptive。

arthas jad 反编译出来的代码:在PrintService$Adaptive#print(URL url)方法中,会先获取参数language的值,然后根据该值去加载对应的扩展类,并调用其print方法,从而达到在调用时决定扩展类的功能。
import com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.common.extension.ExtensionLoader;import top.fuyuaaa.spidemo.PrintService;public class PrintService$Adpative implements PrintService {public void print(URL uRL) {//省略url空校验URL uRL2 = uRL;//这里有个chinese是因为在@SPI注解里默认指定了chinese,是一个缺省值。String string = uRL2.getParameter("language", "chinese");//省略string空校验PrintService printService = ExtensionLoader.getExtensionLoader(PrintService.class).getExtension(string);printService.print(uRL);}}
扩展点自动激活
使用@Activate注解,可以标记对应的扩展点默认被激活启用。该注解还可以通过传入不同的参数,设置扩展点在不同的条件下被自动激活,主要的用途是Filter和Listener等。
🌰:ProtocolFilterWrapper#buildInvokerChain会根据指定的条件获取所有激活的扩展类,然后遍历扩展类列表,形成Filter的调用链。(ProtocolFilterWrapper是Protocol的包装类。)
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {Invoker<T> last = invoker;//根据key和group条件,获取所有已激活的扩展类。List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);if (!filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {final Filter filter = filters.get(i);final Invoker<T> next = last;last = new Invoker<T>() {//省略};}}return last;}
ExtensionLoader 源码解析
ExtensionLoader是整个扩展机制的主要逻辑类,在这个类里面实现了配置的加载、扩展类缓存、自适应对象生成等所有工作。
getExtensionLoader 方法
getExtensionLoader方法的用处就是生产一个ExtensionLoader实例。
代码如下所示:ExtensionLoader类里维护了一个static Map用于缓存ExtensionLoader。getExtensionLoader方法先从map里取,取不到则创建。再来看下构造方法,主要是指定当前ExtensionLoader的类型,以及初始化objectFactory,objectFactory主要用处是在依赖注入时获取依赖的扩展点实例。
//Map<类, 类对应的ExtensionLoader>private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {//省略校验逻辑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;}private ExtensionLoader(Class<?> type) {this.type = type;objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}
getExtension 方法
getExtension(String name)方法是ExtensionLoader里最核心的方法,实现了一个普通扩展类的加载过程。
主要流程如下所示:

- 参数校验,处理默认情况
- 先根据
name从缓存里获取扩展类holder,获取不到则新建 - 如果扩展类实例不存在,则创建
getExtension(String name)代码如下所示:
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();public T getExtension(String name) {//name参数校验if (name == null || name.length() == 0)throw new IllegalArgumentException("Extension name == null");//如果是true,则返回默认的扩展类if ("true".equals(name)) {return getDefaultExtension();}//获取扩展类实例,如果不存在则新建。cachedInstances缓存了name对应的扩展类实例Holder<Object> holder = cachedInstances.get(name);if (holder == null) {cachedInstances.putIfAbsent(name, new Holder<Object>());holder = cachedInstances.get(name);}Object instance = holder.get();if (instance == null) {synchronized (holder) {instance = holder.get();if (instance == null) {//创建扩展类实例,并设置到holder中instance = createExtension(name);holder.set(instance);}}}return (T) instance;}
createExtension方法
createExtension(String name)代码如下所示,
- 从配置文件中加载所有扩展类,通过name查询扩展类
- 根据clazz从缓存里取实例,取不到则创建
- 依赖注入
- 包装
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();private T createExtension(String name) {//加载所有扩展类(不进行初始化),通过name获取扩展类Class<?> clazz = getExtensionClasses().get(name);//省略clazz空校验try {//缓存里取实例,不存在则创建实例。T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);}//依赖注入injectExtension(instance);Set<Class<?>> wrapperClasses = cachedWrapperClasses;//循环装饰,如果有多个装饰者,在生成一个装饰类之后,又会用这个装饰类生成下一个装饰类if (wrapperClasses != null && !wrapperClasses.isEmpty()) {for (Class<?> wrapperClass : wrapperClasses) {//先生成装饰类实例,再进行装饰类的依赖注入instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;} //省略异常处理}
getExtensionClasses方法
getExtensionClasses()代码如下所示,该方法用处是获取所有的的扩展类。
- 缓存里取,取不到则通过
loadExtensionClasses加载,并设置到缓存 - 加载扩展类,处理默认扩展类名,从指定配置文件加载扩展类
private Map<String, Class<?>> getExtensionClasses() {Map<String, Class<?>> classes = cachedClasses.get();if (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = loadExtensionClasses();cachedClasses.set(classes);}}}return classes;}private Map<String, Class<?>> loadExtensionClasses() {//获取SPI注解final SPI defaultAnnotation = type.getAnnotation(SPI.class);if (defaultAnnotation != null) {String value = defaultAnnotation.value();if ((value = value.trim()).length() > 0) {String[] names = NAME_SEPARATOR.split(value);if (names.length > 1) {//省略异常抛出代码}//设置默认扩展类的nameif (names.length == 1) cachedDefaultName = names[0];}}Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();//加载指定文件夹下的配置文件 META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);loadDirectory(extensionClasses, DUBBO_DIRECTORY);loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;}
关于loadDirectory方法和其内部调用的loadResource方法这里不贴代码了,主要功能就是将读取配置文件将chinese=top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImpl解析成chinese和对应的className并通过反射加载类,比较重要的是loadResource方法里调用的loadClass方法。
loadClass方法
- 处理自适应扩展类
- 处理包装扩展类
- 处理普通扩展类
代码如下所示:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {//检测clazz是否是type的实现类if (!type.isAssignableFrom(clazz)) {//省略异常抛出代码}//如果扩展类被@Adaptive注解,设置cachedAdaptiveClass缓存if (clazz.isAnnotationPresent(Adaptive.class)) {if (cachedAdaptiveClass == null) {cachedAdaptiveClass = clazz;} else if (!cachedAdaptiveClass.equals(clazz)) {//省略异常抛出代码}}//如果是包装类,缓存到包装类mapelse if (isWrapperClass(clazz)) {Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();wrappers = cachedWrapperClasses;}wrappers.add(clazz);}//普通扩展类else {//检测是否有默认的构造方法clazz.getConstructor();//如果扩展类没有指定名字,则尝试从注解@Extension中获取if (name == null || name.length() == 0) {name = findAnnotationName(clazz);if (name.length() == 0) {//省略异常抛出代码}}String[] names = NAME_SEPARATOR.split(name);if (names != null && names.length > 0) {//如果是自动激活的扩展类,缓存到cachedActivatesActivate activate = clazz.getAnnotation(Activate.class);if (activate != null) {cachedActivates.put(names[0], activate);}for (String n : names) {//缓存class和name的映射关系if (!cachedNames.containsKey(clazz)) {cachedNames.put(clazz, n);}Class<?> c = extensionClasses.get(n);//设置name和class的映射关系,extensionClasses是从loadExtensionClasses方法一直传过来的。if (c == null) {extensionClasses.put(n, clazz);} else if (c != clazz) {//省略异常抛出代码}}}}}
依赖注入,包装类逻辑在Dubbo SPI特性已经描述过了,不再解析。
getAdaptiveExtension方法
getAdaptiveExtension方法是获取自适应扩展的入口方法。
- 从缓存中获取
- 缓存获取不到则创建
代码如下所示:
//缓存自适应扩展实例private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();public T getAdaptiveExtension() {//从缓存中获取自适应扩展实例,如获取不到则创建Object instance = cachedAdaptiveInstance.get();if (instance == null) {if (createAdaptiveInstanceError == null) {synchronized (cachedAdaptiveInstance) {instance = cachedAdaptiveInstance.get();if (instance == null) {try {//创建自适应扩展实例,设置缓存instance = createAdaptiveExtension();cachedAdaptiveInstance.set(instance);} //省略异常抛出代码}}} //省略异常抛出代码}return (T) instance;}
createAdaptiveExtension方法
- 获取自适应扩展类并实例化
- 依赖注入
代码如下所示:
private T createAdaptiveExtension() {try {//获取自适应扩展类,实例化,依赖注入return injectExtension((T) getAdaptiveExtensionClass().newInstance());} //省略异常抛出代码}
getAdaptiveExtensionClass方法
- 获取所有扩展类
- 从缓存中获取,如果存在则直接返回(有两种情况下会存在缓存)
- 手动编码:在
loadClass方法里有个逻辑,如果扩展类被@Adaptive注解,则会被认为是自适应扩展类,并缓存到cachedAdaptiveClass。 - 自动生成:如果缓存中不存在,会自动创建自适应扩展类(自动生成代码,编译并加载),并缓存。
- 手动编码:在
- 不存在则自动创建自适应扩展类
代码如下所示:
private Class<?> getAdaptiveExtensionClass() {//获取所有扩展类getExtensionClasses();//如果缓存存在,则直接返回if (cachedAdaptiveClass != null) {return cachedAdaptiveClass;}//缓存不存在,创建自适应拓展类return cachedAdaptiveClass = createAdaptiveExtensionClass();}
createAdaptiveExtensionClass方法
- 创建自适应扩展类的代码
- 获取类加载器,编译器
- 编译
代码如下所示:
private Class<?> createAdaptiveExtensionClass() {//创建自适应扩展类的代码String code = createAdaptiveExtensionClassCode();//获取类加载器ClassLoader classLoader = findClassLoader();//获取编译器com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//编译return compiler.compile(code, classLoader);}
createAdaptiveExtensionClassCode方法主要就是将代码拼接起来,具体逻辑不再解析,生成的代码可见扩展点自适应小节通过arthas反编译出来的$Adaptive类。
