简述区别

Java SPI 会一次性加载和实例化所有的实现类。
Dubbo SPI则自己实现了SPI,通过名字实例化指定的实现类,并且实现了IOC、AOP和自适应扩展SPI。
Dubbo依赖SPI实现了插件化的功能,几乎所有的组件做成了基于SPI实现,并且默认提供很多可以扩展的扩展点,实现了面向功能进行拆分的对扩展开放的架构。

Java SPI

约定在ClassPath下的META-INF/services/ 目录创建一个以服务接口命名的文件,然后文件里面记录的是此jar包提供的具体实现类的加载和实例化。

示例

  1. package com.interview.demo.spi;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/8
  5. */
  6. public interface NotifyService {
  7. void send();
  8. }
  1. package com.interview.demo.spi;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/8
  5. */
  6. public class MailService implements NotifyService{
  7. @Override
  8. public void send() {
  9. System.out.println("send email");
  10. }
  11. }
  1. package com.interview.demo.spi;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/8
  5. */
  6. public class SmsService implements NotifyService{
  7. @Override
  8. public void send() {
  9. System.out.println("send sms");
  10. }
  11. }
  1. package com.interview.demo.spi;
  2. import java.util.Iterator;
  3. import java.util.ServiceLoader;
  4. /**
  5. * @Author leijs
  6. * @date 2022/4/8
  7. */
  8. public class NotifyMain {
  9. public static void main(String[] args) {
  10. ServiceLoader<NotifyService> serviceLoader = ServiceLoader.load(NotifyService.class);
  11. Iterator<NotifyService> iterator = serviceLoader.iterator();
  12. while (iterator.hasNext()) {
  13. NotifyService notifyService = iterator.next();
  14. notifyService.send();
  15. }
  16. }
  17. }

image.png
image.png
执行结果:
image.png

源码分析

image.pngimage.pngimage.pngimage.png

简单来说:就是用当前线程的ClassLoader,如果没有,就用SystemClassLoader, 然后清除一下缓存,再创建一个LazyIterator. image.png

  1. private class LazyIterator
  2. implements Iterator<S>
  3. {
  4. Class<S> service;
  5. ClassLoader loader;
  6. Enumeration<URL> configs = null;
  7. Iterator<String> pending = null;
  8. String nextName = null;
  9. private LazyIterator(Class<S> service, ClassLoader loader) {
  10. this.service = service;
  11. this.loader = loader;
  12. }
  13. private boolean hasNextService() {
  14. if (nextName != null) {
  15. return true;
  16. }
  17. if (configs == null) {
  18. try {
  19. String fullName = PREFIX + service.getName();
  20. if (loader == null)
  21. configs = ClassLoader.getSystemResources(fullName);
  22. else
  23. configs = loader.getResources(fullName);
  24. } catch (IOException x) {
  25. fail(service, "Error locating configuration files", x);
  26. }
  27. }
  28. while ((pending == null) || !pending.hasNext()) {
  29. if (!configs.hasMoreElements()) {
  30. return false;
  31. }
  32. pending = parse(service, configs.nextElement());
  33. }
  34. nextName = pending.next();
  35. return true;
  36. }
  37. private S nextService() {
  38. if (!hasNextService())
  39. throw new NoSuchElementException();
  40. String cn = nextName;
  41. nextName = null;
  42. Class<?> c = null;
  43. try {
  44. c = Class.forName(cn, false, loader);
  45. } catch (ClassNotFoundException x) {
  46. fail(service,
  47. "Provider " + cn + " not found");
  48. }
  49. if (!service.isAssignableFrom(c)) {
  50. fail(service,
  51. "Provider " + cn + " not a subtype");
  52. }
  53. try {
  54. S p = service.cast(c.newInstance());
  55. providers.put(cn, p);
  56. return p;
  57. } catch (Throwable x) {
  58. fail(service,
  59. "Provider " + cn + " could not be instantiated",
  60. x);
  61. }
  62. throw new Error(); // This cannot happen
  63. }
  64. public boolean hasNext() {
  65. if (acc == null) {
  66. return hasNextService();
  67. } else {
  68. PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
  69. public Boolean run() { return hasNextService(); }
  70. };
  71. return AccessController.doPrivileged(action, acc);
  72. }
  73. }
  74. public S next() {
  75. if (acc == null) {
  76. return nextService();
  77. } else {
  78. PrivilegedAction<S> action = new PrivilegedAction<S>() {
  79. public S run() { return nextService(); }
  80. };
  81. return AccessController.doPrivileged(action, acc);
  82. }
  83. }
  84. public void remove() {
  85. throw new UnsupportedOperationException();
  86. }
  87. }

重点看下这个LazyIterator. 其实就是Iterator的实现类。使用hasNext()来做实例的循环。
image.png
就是通过约定好的地方找到接口对应的文件,然后加载文件并解析文件里面的内容。
然后nextService()方法,
1649409026(1).png
然后通过文件里面填写的全限名加载类,并且创建其实例放入缓存之后返回实例。

缓存key:类的全限名(实现类的), value是实例

缺点

在查找扩展实现类的时候,遍历SPI的配置文件并且将全部的实现类全部实例化,假如一个实现类初始化的该过程比较耗费资源且耗时,但是你的代码又用不到,这就产生了资源的浪费
也就是没法按需加载类。

Dubbo SPI

dubbo是这样设计的,配置文件里面存放的是键值对,这里截取一个cluster的配置。
image.png
并且dubbo的SPI除了可以按需加载实现类之外,增加了IOC和AOP的特性,还有自适应扩展机制。
dubbo对配置文件的约定有以下三种:

  1. META-INF/services目录,该目录下的SPI文件是为了兼容Java SPI
  2. META-INF/dubbo 目录,存放用户自定义的SPI配置文件
  3. META-INF/dubbo/internal 该目录存放dubbo内部使用的SPI配置文件。

    示例

    注意:接口必须标注@SPI 注解,表明要用SPI机制。 ```java package com.alibaba.dubbo.rpc.cluster.spi;

import com.alibaba.dubbo.common.extension.SPI;

/**

  • @Author leijs
  • @date 2022/4/8 */ @SPI public interface MyCluster { void action(); }
  1. ```java
  2. package com.alibaba.dubbo.rpc.cluster.spi;
  3. /**
  4. * @Author leijs
  5. * @date 2022/4/8
  6. */
  7. public class MysqlCluster implements MyCluster{
  8. @Override
  9. public void action() {
  10. System.out.println("mysql action");
  11. }
  12. }
  1. package com.alibaba.dubbo.rpc.cluster.spi;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/8
  5. */
  6. public class RedisCluster implements MyCluster{
  7. @Override
  8. public void action() {
  9. System.out.println("redis action");
  10. }
  11. }
  1. package com.alibaba.dubbo.rpc.cluster.spi;
  2. import com.alibaba.dubbo.common.extension.ExtensionLoader;
  3. import org.junit.Test;
  4. /**
  5. * @Author leijs
  6. * @date 2022/4/8
  7. */
  8. public class DubboSPITest {
  9. @Test
  10. public void test() {
  11. ExtensionLoader<MyCluster> extensionLoader = ExtensionLoader.getExtensionLoader(MyCluster.class);
  12. MyCluster redisCluster = extensionLoader.getExtension("redisCluster");
  13. redisCluster.action();
  14. MyCluster mysqlCluster = extensionLoader.getExtension("mysqlCluster");
  15. mysqlCluster.action();
  16. }
  17. }

image.png
运行结果:
image.png

源码分析

重点:ExtensionLoader。
大致流程:先通过接口找到一个ExtensionLoader,然后再通过extensionLoader.getExtension(name)得到指定名字的实现类实例。

  1. private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

image.png
做了一些type等为空的判断之后,从缓存里面找是否存在这个类型的ExtensionLoader, 如果就新建一个塞入缓存,最后返回对应类的ExtensionLoader.
接下来看getExtension方法,是从类对应的ExtensionLoader中通过名字找到实例化完的实现类。

  1. public T getExtension(String name) {
  2. if (name == null || name.length() == 0)
  3. throw new IllegalArgumentException("Extension name == null");
  4. if ("true".equals(name)) {
  5. return getDefaultExtension();
  6. }
  7. Holder<Object> holder = cachedInstances.get(name);
  8. if (holder == null) {
  9. cachedInstances.putIfAbsent(name, new Holder<Object>());
  10. holder = cachedInstances.get(name);
  11. }
  12. Object instance = holder.get();
  13. if (instance == null) {
  14. synchronized (holder) {
  15. instance = holder.get();
  16. if (instance == null) {
  17. instance = createExtension(name);
  18. holder.set(instance);
  19. }
  20. }
  21. }
  22. return (T) instance;
  23. }

重点就是createExtension()

  1. private T createExtension(String name) {
  2. Class<?> clazz = getExtensionClasses().get(name);
  3. if (clazz == null) {
  4. throw findException(name);
  5. }
  6. try {
  7. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  8. if (instance == null) {
  9. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  10. instance = (T) EXTENSION_INSTANCES.get(clazz);
  11. }
  12. injectExtension(instance);
  13. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  14. if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  15. for (Class<?> wrapperClass : wrapperClasses) {
  16. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  17. }
  18. }
  19. return instance;
  20. } catch (Throwable t) {
  21. throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
  22. type + ") could not be instantiated: " + t.getMessage(), t);
  23. }
  24. }

先找实现类,判断缓存是否有这个实例,没有就反射建个实例,然后执行set方法注入,如果有找到包装类的话,再包一层。

整体逻辑很清晰,先找实现类,判断缓存是否有实例,没有就反射建个实例,然后执行 set 方法依赖注入。如果有找到包装类的话,再包一层。
到这步为止我先画个图,大家理一理,还是很简单的。
dubbo的SPI机制 - 图15
那么问题来了 getExtensionClasses() 是怎么找的呢?injectExtension() 如何注入的呢(其实我已经说了set方法注入)?为什么需要包装类呢?

getExtensionClasses

这个方法进去也是先去缓存中找,如果缓存是空的,那么调用 loadExtensionClasses,我们就来看下这个方法。
dubbo的SPI机制 - 图16
而 loadDirectory里面就是根据类名和指定的目录,找到文件先获取所有的资源,然后一个一个去加载类,然后再通过loadClass去做一下缓存操作。
dubbo的SPI机制 - 图17
可以看到,loadClass 之前已经加载了类,loadClass 只是根据类上面的情况做不同的缓存。分别有 Adaptive 、WrapperClass 和普通类这三种,普通类又将Activate记录了一下。至此对于普通的类来说整个 SPI 过程完结了。
dubbo的SPI机制 - 图18
接下来我们分别看不是普通类的几种东西是干啥用的。

Adaptive 注解 - 自适应扩展

在进入这个注解分析之前,我们需要知道 Dubbo 的自适应扩展机制。
我们先来看一个场景,首先我们根据配置来进行 SPI 扩展的加载,但是我不想在启动的时候让扩展被加载,我想根据请求时候的参数来动态选择对应的扩展。
怎么做呢?
Dubbo 通过一个代理机制实现了自适应扩展,简单的说就是为你想扩展的接口生成一个代理类,可以通过JDK 或者 javassist 编译你生成的代理类代码,然后通过反射创建实例。
这个实例里面的实现会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensionLoader.getExtensionLoader(type.class).getExtension(从参数得来的name),来获取真正的实例来调用。
我从官网搞了个例子,大家来看下。
dubbo的SPI机制 - 图19
现在大家应该对自适应扩展有了一定的认识了,我们再来看下源码,到底怎么做的。
dubbo的SPI机制 - 图20
这个注解就是自适应扩展相关的注解,可以修饰类和方法上,在修饰类的时候不会生成代理类,因为这个类就是代理类,修饰在方法上的时候会生成代理类。

Adaptive 注解在类上

比如这个 ExtensionFactory 有三个实现类,其中一个实现类就被标注了 Adaptive 注解。
image.png
image.png
在 ExtensionLoader 构造的时候就会去通过getAdaptiveExtension 获取指定的扩展类的 ExtensionFactory。
dubbo的SPI机制 - 图23
我们再来看下 AdaptiveExtensionFactory 的实现。
dubbo的SPI机制 - 图24
可以看到先缓存了所有实现类,然后在获取的时候通过遍历找到对应的 Extension。
我们再来深入分析一波 getAdaptiveExtension 里面到底干了什么。
dubbo的SPI机制 - 图25
到这里其实已经和上文分析的 getExtensionClasses中loadClass 对 Adaptive 特殊缓存相呼应上了。
image.png

Adaptive 注解在方法上

注解在方法上则需要动态拼接代码,然后动态生成类,我们以 Protocol 为例子来看一下。
dubbo的SPI机制 - 图27
Protocol 没有实现类注释了 Adaptive ,但是接口上有两个方法注解了 Adaptive ,有两个方法没有。
因此它走的逻辑应该应该是 createAdaptiveExtensionClass,
dubbo的SPI机制 - 图28
具体在里面如何生成代码的我就不再深入了,有兴趣的自己去看吧,我就把成品解析一下,就差不多了。
dubbo的SPI机制 - 图29
我美化一下给大家看看。
image.png
可以看到会生成包,也会生成 import 语句,类名就是接口加个$Adaptive,并且实现这接口,没有标记 Adaptive 注解的方法调用的话直接抛错。
我们再来看一下标注了注解的方法,我就拿 export 举例。
dubbo的SPI机制 - 图31
就像我前面说的那样,根据请求的参数,即 URL 得到具体要调用的实现类名,然后再调用 getExtension 获取。
整个自适应扩展流程如下。
dubbo的SPI机制 - 图32

WrapperClass - AOP

包装类是因为一个扩展接口可能有多个扩展实现类,而这些扩展实现类会有一个相同的或者公共的逻辑,如果每个实现类都写一遍代码就重复了,并且比较不好维护。
因此就搞了个包装类,Dubbo 里帮你自动包装,只需要某个扩展类的构造函数只有一个参数,并且是扩展接口类型,就会被判定为包装类,然后记录下来,用来包装别的实现类。
dubbo的SPI机制 - 图33
简单又巧妙,这就是 AOP 了。

injectExtension - IOC

直接看代码,很简单,就是查找 set 方法,根据参数找到依赖对象则注入。
dubbo的SPI机制 - 图34
这就是 IOC。

Activate 注解

这个注解我就简单的说下,拿 Filter 举例,Filter 有很多实现类,在某些场景下需要其中的几个实现类,而某些场景下需要另外几个,而 Activate 注解就是标记这个用的。
它有三个属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。

总结

先放个上述过程完整的图。
dubbo的SPI机制 - 图35