SPI

简介

SPI 的全称是Service Provider Interface,是一种服务提供发现机制。

Java SPI

Java SPI使用了策略模式,一个接口多种实现。我们只声明接口,具体的实现并不在程序中直接确定,而是由程序之外的配置掌控,用于具体实现的装配。具体步骤如下:

  1. 定义一个接口及对应的方法。
  2. 编写该接口的一个实现类。
  3. META-INF/services/目录下,创建一个以接口全路径命名的文件,如com.test.spi.PrintService0
  4. 文件内容为具体实现类的全路径名,如果有多个,则用分行符分隔。
  5. 在代码中通过java.util.ServiceLoader来加载具体的实现类。

Java SPI 示例

  1. service
  2. public interface PrintService {
  3. void print();
  4. }
  5. 实现类1
  6. public class ChinesePrintServiceImpl implements PrintService {
  7. @Override
  8. public void print() {
  9. System.out.println("中文");
  10. }
  11. }
  12. 实现类2
  13. public class EnglishPrintServiceImpl implements PrintService {
  14. @Override
  15. public void print() {
  16. System.out.println("English");
  17. }
  18. }
  19. 文件
  20. META-INF/services/top.fuyuaaa.spidemo.PrintService
  21. 文件内容
  22. top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImpl
  23. top.fuyuaaa.spidemo.javaspi.EnglishPrintServiceImpl
  24. 测试类
  25. public class JavaSPIDemo {
  26. public static void main(String[] args) {
  27. ServiceLoader<PrintService> printServices = ServiceLoader.load(PrintService.class);
  28. printServices.forEach(PrintService::print);
  29. }
  30. }
  31. 结果
  32. 中文
  33. English

Dubbo SPI

Java SPI相比,Dubbo SPI做了一定的改进和优化。Dubbo SPI 改进了Java SPI以下几个问题。

  1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  2. 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  3. Dubbo增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

Dubbo SPI 示例

  1. @SPI("chinese")
  2. public interface PrintService {
  3. void print();
  4. }
  5. 文件
  6. META-INF/dubbo/top.fuyuaaa.spidemo.PrintService
  7. 文件内容
  8. chinese=top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImpl
  9. english=top.fuyuaaa.spidemo.javaspi.EnglishPrintServiceImpl
  10. 测试类
  11. public class DubboSPIDemo {
  12. public static void main(String[] args) {
  13. ExtensionLoader<PrintService> extensionLoader = ExtensionLoader.getExtensionLoader(PrintService.class);
  14. PrintService defaultPrintService = extensionLoader.getDefaultExtension();
  15. defaultPrintService.print();
  16. }
  17. }
  18. 结果
  19. 中文

Dubbo SPI 特性

扩展点自动包装

ExtensionLoader在加载扩展时,如果扩展类存在 一个参数为扩展点构造函数,则该类会被识别成Wrapper(包装)类。

🌰如下:PrintServiceWrapper实现了扩展点PrintService,也与其他扩展类进行了一样的配置,但是PrintServiceWrapper有一个参数为PrintService的构造函数,此时PrintServiceWrapper会被识别称为一个Wrapper类,包装在真正的扩展点之外,有点类似AOP。当通过ExtensionLoader.getExtensionLoader(PrintService.class)获取扩展类时,得到的其实是包装类PrintServiceWrapper,并且包装类可以存在多个,如【包装类[包装类(扩展类)]】。

  1. public class PrintServiceWrapper implements PrintService {
  2. private final PrintService printService;
  3. public PrintServiceWrapper(PrintService printService) {
  4. this.printService = printService;
  5. }
  6. @Override
  7. public void print() {
  8. System.out.println("做一下前置工作");
  9. printService.print();
  10. System.out.println("做一下后置工作");
  11. }
  12. }
  13. 在文件
  14. META-INF/dubbo/top.fuyuaaa.spidemo.PrintService
  15. 添加内容
  16. wrapperTest=top.fuyuaaa.spidemo.PrintServiceWrapper

在加载扩展类时,会判断是否为包装类,如果是包装类,则会将其加入到cachedWrapperClasses

ExtensionLoader#loadClass代码如下所示:

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
  2. //省略无关代码
  3. //判断是否包装类
  4. else if (isWrapperClass(clazz)) {
  5. Set<Class<?>> wrappers = cachedWrapperClasses;
  6. if (wrappers == null) {
  7. cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
  8. wrappers = cachedWrapperClasses;
  9. }
  10. wrappers.add(clazz);
  11. }
  12. //省略无关代码
  13. }
  14. private boolean isWrapperClass(Class<?> clazz) {
  15. try {
  16. clazz.getConstructor(type);
  17. return true;
  18. } catch (NoSuchMethodException e) {
  19. return false;
  20. }
  21. }

在创建扩展类时,会判断cachedWrapperClasses是否为空,如果不为空,会在原扩展类上进行包装,如【包装类[包装类(扩展类)]】。

ExrensionLoader#createExtension代码如下所示:

  1. private T createExtension(String name) {
  2. //省略无关代码
  3. //依赖注入
  4. injectExtension(instance);
  5. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  6. //循环装饰,如果有多个装饰者,在生成一个装饰类之后,又会用这个装饰类生成下一个装饰类
  7. if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  8. for (Class<?> wrapperClass : wrapperClasses) {
  9. //先生成装饰类实例,再进行装饰类的依赖注入
  10. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  11. }
  12. }
  13. return instance;
  14. //省略无关代码
  15. }

扩展点自动装配

加载扩展点时,自动注入依赖的扩展点。加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader在会自动注入依赖的扩展点。ExtensionLoader通过扫描扩展点实现类的所有setter方法来判定其成员。

自动注入依赖主要通过ExtensionLoader#injectExtension方法,injectExtension主要作用是对扩展类进行依赖注入,也就是Dubbo IOC。实现原理比较简单,首先通过反射获取类的所有方法,然后遍历以字符串set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例,如果找到,就设值进去。

代码如下所示:

  1. private T injectExtension(T instance) {
  2. try {
  3. if (objectFactory != null) {
  4. //遍历扩展点的方法,如果是public的 有参的 set方法,会通过set后的名字 以及 参数类型 去查询扩展点。
  5. for (Method method : instance.getClass().getMethods()) {
  6. if (method.getName().startsWith("set")
  7. && method.getParameterTypes().length == 1
  8. && Modifier.isPublic(method.getModifiers())) {
  9. //..省略注解判断
  10. //参数类型
  11. Class<?> pt = method.getParameterTypes()[0];
  12. try {
  13. //参数名
  14. String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
  15. //获取依赖的对象,AdaptiveExtensionFactory.getExtension
  16. Object object = objectFactory.getExtension(pt, property);
  17. //反射调用set方法设置依赖
  18. if (object != null) {
  19. method.invoke(instance, object);
  20. }
  21. } //...省略catch
  22. }
  23. }
  24. }
  25. } //...省略catch
  26. return instance;
  27. }

扩展点自适应

扩展点自适应机制,主要做的事情就是通过@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

  1. @SPI("chinese")
  2. public interface PrintService {
  3. @Adaptive("language")
  4. void print(URL url);
  5. }
  6. public class ChinesePrintServiceImpl implements PrintService {
  7. @Override
  8. public void print(URL url) {
  9. System.out.println("中文URL");
  10. }
  11. }
  12. public class EnglishPrintServiceImpl implements PrintService {
  13. @Override
  14. public void print(URL url) {
  15. System.out.println("English URL");
  16. }
  17. }
  18. public class DubboSPIDemo {
  19. public static void main(String[] args) {
  20. //构建URL,指定language参数值为english
  21. URL url = new URL("test", "test", 8080, "test", Collections.singletonMap("language", "english"));
  22. PrintService adaptiveExtension = extensionLoader.getAdaptiveExtension();
  23. adaptiveExtension.print(url);
  24. }
  25. }
  26. 结果
  27. English URL

在加载自适应扩展类时,Dubbo会为拓展接口生成具有代理功能的代码。然后通过javassistjdk编译这段代码,得到 Class 类,最后再通过反射创建代理类。如下图所示,debug获取到的PrintService其实是PrintService$Adaptive

【Dubbo】扩展点加载机制 - 图1

arthas jad 反编译出来的代码:在PrintService$Adaptive#print(URL url)方法中,会先获取参数language的值,然后根据该值去加载对应的扩展类,并调用其print方法,从而达到在调用时决定扩展类的功能。

  1. import com.alibaba.dubbo.common.URL;
  2. import com.alibaba.dubbo.common.extension.ExtensionLoader;
  3. import top.fuyuaaa.spidemo.PrintService;
  4. public class PrintService$Adpative implements PrintService {
  5. public void print(URL uRL) {
  6. //省略url空校验
  7. URL uRL2 = uRL;
  8. //这里有个chinese是因为在@SPI注解里默认指定了chinese,是一个缺省值。
  9. String string = uRL2.getParameter("language", "chinese");
  10. //省略string空校验
  11. PrintService printService = ExtensionLoader.getExtensionLoader(PrintService.class).getExtension(string);
  12. printService.print(uRL);
  13. }
  14. }

扩展点自动激活

使用@Activate注解,可以标记对应的扩展点默认被激活启用。该注解还可以通过传入不同的参数,设置扩展点在不同的条件下被自动激活,主要的用途是FilterListener等。

🌰:ProtocolFilterWrapper#buildInvokerChain会根据指定的条件获取所有激活的扩展类,然后遍历扩展类列表,形成Filter的调用链。(ProtocolFilterWrapperProtocol的包装类。)

  1. private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
  2. Invoker<T> last = invoker;
  3. //根据key和group条件,获取所有已激活的扩展类。
  4. List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
  5. if (!filters.isEmpty()) {
  6. for (int i = filters.size() - 1; i >= 0; i--) {
  7. final Filter filter = filters.get(i);
  8. final Invoker<T> next = last;
  9. last = new Invoker<T>() {
  10. //省略
  11. };
  12. }
  13. }
  14. return last;
  15. }

ExtensionLoader 源码解析

ExtensionLoader是整个扩展机制的主要逻辑类,在这个类里面实现了配置的加载、扩展类缓存、自适应对象生成等所有工作。

getExtensionLoader 方法

getExtensionLoader方法的用处就是生产一个ExtensionLoader实例。

代码如下所示:ExtensionLoader类里维护了一个static Map用于缓存ExtensionLoadergetExtensionLoader方法先从map里取,取不到则创建。再来看下构造方法,主要是指定当前ExtensionLoader的类型,以及初始化objectFactoryobjectFactory主要用处是在依赖注入时获取依赖的扩展点实例。

  1. //Map<类, 类对应的ExtensionLoader>
  2. private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
  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. }
  12. private ExtensionLoader(Class<?> type) {
  13. this.type = type;
  14. objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
  15. }

getExtension 方法

getExtension(String name)方法是ExtensionLoader里最核心的方法,实现了一个普通扩展类的加载过程。

主要流程如下所示:

【Dubbo】扩展点加载机制 - 图2

  1. 参数校验,处理默认情况
  2. 先根据name从缓存里获取扩展类holder,获取不到则新建
  3. 如果扩展类实例不存在,则创建

getExtension(String name)代码如下所示:

  1. private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
  2. public T getExtension(String name) {
  3. //name参数校验
  4. if (name == null || name.length() == 0)
  5. throw new IllegalArgumentException("Extension name == null");
  6. //如果是true,则返回默认的扩展类
  7. if ("true".equals(name)) {
  8. return getDefaultExtension();
  9. }
  10. //获取扩展类实例,如果不存在则新建。cachedInstances缓存了name对应的扩展类实例
  11. Holder<Object> holder = cachedInstances.get(name);
  12. if (holder == null) {
  13. cachedInstances.putIfAbsent(name, new Holder<Object>());
  14. holder = cachedInstances.get(name);
  15. }
  16. Object instance = holder.get();
  17. if (instance == null) {
  18. synchronized (holder) {
  19. instance = holder.get();
  20. if (instance == null) {
  21. //创建扩展类实例,并设置到holder中
  22. instance = createExtension(name);
  23. holder.set(instance);
  24. }
  25. }
  26. }
  27. return (T) instance;
  28. }

createExtension方法

createExtension(String name)代码如下所示,

  1. 从配置文件中加载所有扩展类,通过name查询扩展类
  2. 根据clazz从缓存里取实例,取不到则创建
  3. 依赖注入
  4. 包装
  1. private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
  2. private T createExtension(String name) {
  3. //加载所有扩展类(不进行初始化),通过name获取扩展类
  4. Class<?> clazz = getExtensionClasses().get(name);
  5. //省略clazz空校验
  6. try {
  7. //缓存里取实例,不存在则创建实例。
  8. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  9. if (instance == null) {
  10. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  11. instance = (T) EXTENSION_INSTANCES.get(clazz);
  12. }
  13. //依赖注入
  14. injectExtension(instance);
  15. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  16. //循环装饰,如果有多个装饰者,在生成一个装饰类之后,又会用这个装饰类生成下一个装饰类
  17. if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  18. for (Class<?> wrapperClass : wrapperClasses) {
  19. //先生成装饰类实例,再进行装饰类的依赖注入
  20. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  21. }
  22. }
  23. return instance;
  24. } //省略异常处理
  25. }

getExtensionClasses方法

getExtensionClasses()代码如下所示,该方法用处是获取所有的的扩展类。

  1. 缓存里取,取不到则通过loadExtensionClasses加载,并设置到缓存
  2. 加载扩展类,处理默认扩展类名,从指定配置文件加载扩展类
  1. private Map<String, Class<?>> getExtensionClasses() {
  2. Map<String, Class<?>> classes = cachedClasses.get();
  3. if (classes == null) {
  4. synchronized (cachedClasses) {
  5. classes = cachedClasses.get();
  6. if (classes == null) {
  7. classes = loadExtensionClasses();
  8. cachedClasses.set(classes);
  9. }
  10. }
  11. }
  12. return classes;
  13. }
  14. private Map<String, Class<?>> loadExtensionClasses() {
  15. //获取SPI注解
  16. final SPI defaultAnnotation = type.getAnnotation(SPI.class);
  17. if (defaultAnnotation != null) {
  18. String value = defaultAnnotation.value();
  19. if ((value = value.trim()).length() > 0) {
  20. String[] names = NAME_SEPARATOR.split(value);
  21. if (names.length > 1) {
  22. //省略异常抛出代码
  23. }
  24. //设置默认扩展类的name
  25. if (names.length == 1) cachedDefaultName = names[0];
  26. }
  27. }
  28. Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
  29. //加载指定文件夹下的配置文件 META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/
  30. loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
  31. loadDirectory(extensionClasses, DUBBO_DIRECTORY);
  32. loadDirectory(extensionClasses, SERVICES_DIRECTORY);
  33. return extensionClasses;
  34. }

关于loadDirectory方法和其内部调用的loadResource方法这里不贴代码了,主要功能就是将读取配置文件将chinese=top.fuyuaaa.spidemo.javaspi.ChinesePrintServiceImpl解析成chinese和对应的className并通过反射加载类,比较重要的是loadResource方法里调用的loadClass方法。

loadClass方法

  1. 处理自适应扩展类
  2. 处理包装扩展类
  3. 处理普通扩展类

代码如下所示:

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
  2. //检测clazz是否是type的实现类
  3. if (!type.isAssignableFrom(clazz)) {
  4. //省略异常抛出代码
  5. }
  6. //如果扩展类被@Adaptive注解,设置cachedAdaptiveClass缓存
  7. if (clazz.isAnnotationPresent(Adaptive.class)) {
  8. if (cachedAdaptiveClass == null) {
  9. cachedAdaptiveClass = clazz;
  10. } else if (!cachedAdaptiveClass.equals(clazz)) {
  11. //省略异常抛出代码
  12. }
  13. }
  14. //如果是包装类,缓存到包装类map
  15. else if (isWrapperClass(clazz)) {
  16. Set<Class<?>> wrappers = cachedWrapperClasses;
  17. if (wrappers == null) {
  18. cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
  19. wrappers = cachedWrapperClasses;
  20. }
  21. wrappers.add(clazz);
  22. }
  23. //普通扩展类
  24. else {
  25. //检测是否有默认的构造方法
  26. clazz.getConstructor();
  27. //如果扩展类没有指定名字,则尝试从注解@Extension中获取
  28. if (name == null || name.length() == 0) {
  29. name = findAnnotationName(clazz);
  30. if (name.length() == 0) {
  31. //省略异常抛出代码
  32. }
  33. }
  34. String[] names = NAME_SEPARATOR.split(name);
  35. if (names != null && names.length > 0) {
  36. //如果是自动激活的扩展类,缓存到cachedActivates
  37. Activate activate = clazz.getAnnotation(Activate.class);
  38. if (activate != null) {
  39. cachedActivates.put(names[0], activate);
  40. }
  41. for (String n : names) {
  42. //缓存class和name的映射关系
  43. if (!cachedNames.containsKey(clazz)) {
  44. cachedNames.put(clazz, n);
  45. }
  46. Class<?> c = extensionClasses.get(n);
  47. //设置name和class的映射关系,extensionClasses是从loadExtensionClasses方法一直传过来的。
  48. if (c == null) {
  49. extensionClasses.put(n, clazz);
  50. } else if (c != clazz) {
  51. //省略异常抛出代码
  52. }
  53. }
  54. }
  55. }
  56. }

依赖注入,包装类逻辑在Dubbo SPI特性已经描述过了,不再解析。

getAdaptiveExtension方法

getAdaptiveExtension方法是获取自适应扩展的入口方法。

  1. 从缓存中获取
  2. 缓存获取不到则创建

代码如下所示:

  1. //缓存自适应扩展实例
  2. private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
  3. public T getAdaptiveExtension() {
  4. //从缓存中获取自适应扩展实例,如获取不到则创建
  5. Object instance = cachedAdaptiveInstance.get();
  6. if (instance == null) {
  7. if (createAdaptiveInstanceError == null) {
  8. synchronized (cachedAdaptiveInstance) {
  9. instance = cachedAdaptiveInstance.get();
  10. if (instance == null) {
  11. try {
  12. //创建自适应扩展实例,设置缓存
  13. instance = createAdaptiveExtension();
  14. cachedAdaptiveInstance.set(instance);
  15. } //省略异常抛出代码
  16. }
  17. }
  18. } //省略异常抛出代码
  19. }
  20. return (T) instance;
  21. }

createAdaptiveExtension方法

  1. 获取自适应扩展类并实例化
  2. 依赖注入

代码如下所示:

  1. private T createAdaptiveExtension() {
  2. try {
  3. //获取自适应扩展类,实例化,依赖注入
  4. return injectExtension((T) getAdaptiveExtensionClass().newInstance());
  5. } //省略异常抛出代码
  6. }

getAdaptiveExtensionClass方法

  1. 获取所有扩展类
  2. 从缓存中获取,如果存在则直接返回(有两种情况下会存在缓存)
    1. 手动编码:在loadClass方法里有个逻辑,如果扩展类被@Adaptive注解,则会被认为是自适应扩展类,并缓存到cachedAdaptiveClass
    2. 自动生成:如果缓存中不存在,会自动创建自适应扩展类(自动生成代码,编译并加载),并缓存。
  3. 不存在则自动创建自适应扩展类

代码如下所示:

  1. private Class<?> getAdaptiveExtensionClass() {
  2. //获取所有扩展类
  3. getExtensionClasses();
  4. //如果缓存存在,则直接返回
  5. if (cachedAdaptiveClass != null) {
  6. return cachedAdaptiveClass;
  7. }
  8. //缓存不存在,创建自适应拓展类
  9. return cachedAdaptiveClass = createAdaptiveExtensionClass();
  10. }

createAdaptiveExtensionClass方法

  1. 创建自适应扩展类的代码
  2. 获取类加载器,编译器
  3. 编译

代码如下所示:

  1. private Class<?> createAdaptiveExtensionClass() {
  2. //创建自适应扩展类的代码
  3. String code = createAdaptiveExtensionClassCode();
  4. //获取类加载器
  5. ClassLoader classLoader = findClassLoader();
  6. //获取编译器
  7. com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  8. //编译
  9. return compiler.compile(code, classLoader);
  10. }

createAdaptiveExtensionClassCode方法主要就是将代码拼接起来,具体逻辑不再解析,生成的代码可见扩展点自适应小节通过arthas反编译出来的$Adaptive类。

参考

Dubbo官网

《深入理解Apache Dubbo与实战》