SPI 机制

SPI 作用

SPI 全称为 Service Provider Interface, 是一种服务发现机制.

SPI 的本质是将接口实现类的全限定名配置在文件中, 并由服务加载器读取配置文件, 加载实现类.

这样可以在运行时, 动态为接口替换实现类. 正因此特性, 我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能.

Dubbo 里面有很多个组件, 每个组件在框架中都是以接口的形式抽象出来. 具体的实现又分很多种, 在程序执行时根据用户的配置来按需取接口的实现类.

Dubbo 实现的 SPI 与 JDK 自带的 SPI 的区别

摘自官网

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现, 如果有扩展实现初始化很耗时, 但如果没用上也加载, 会很浪费资源.

  • 如果扩展点加载失败, 连扩展点的名称都拿不到了.
    比如: JDK 标准的 ScriptEngine, 通过 getName() 获取脚本类型的名称, 但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在, 导致 RubyScriptEngine 类加载失败, 这个失败原因被吃掉了, 和 ruby 对应不起来, 当用户执行 ruby 脚本时, 会报不支持 ruby, 而不是真正失败的原因.

  • 增加了对扩展点 IoC 和 AOP 的支持, 一个扩展点可以直接 setter 注入其它扩展点.

  • JDK 的 SPI 要用 for 循环, 然后 if 判断才能获取到指定的 SPI 对象, Dubbo 用指定的 key 就可以获取.

  • JDK 的 SPI 不支持默认值, Dubbo 增加了默认值的设计.

SPI 配置和使用

只有在接口打了 @SPI 注解的接口类才会去查找扩展点实现.

会依次从这几个文件中读取扩展点

1
2
3
META-INF/Dubbo/internal/   // Dubbo 内部实现的各种扩展都放在了这个目录了
META-INF/Dubbo/
META-INF/services/

比如

  1. Protocol 接口, 接口上打了 @SPI 注解, 默认拓展点名字为 Dubbo

    1
    2
    3
    4
    @SPI("Dubbo")
    public interface Protocol{

    }
  2. 查找 Dubbo-rpc-default 模块下 META-INF/Dubbo/internal/com.alibaba.Dubbo.rpc.Protocol 中的配置

    1
    Dubbo=com.alibaba.Dubbo.rpc.protocol.Dubbo.DubboProtocol
  3. 会用 ExtensionLoader 类加载实现
    Dubbo=com.alibaba.Dubbo.rpc.protocol.Dubbo.DubboProtocol

  4. 获取拓展类对象的代码示例

    1
    2
    3
    4
    5
    // 通过 ExtensionLoader.getExtensionLoader() 方法获取一个 ExtensionLoader 实例
    ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

    // 然后再通过 ExtensionLoader.getExtension() 方法获取拓展类对象
    Protocol dubboProtocol = extensionLoader.getExtension("Dubbo");

Dubbo 拓展点加载机制相关注解和组件

@SPI

@Adaptive

@Activate

@SPI 注解

@SPI 注解作用于扩展点的接口上,表明该接口是一个扩展点. 可以被 Dubbo 的 ExtentionLoader 加载, 如果没有此 ExtensionLoader 调用会异常.

@Adaptive 注解

  1. 在类上加 @Adaptive 注解的类, 会创建对应类型的 Adaptive 类 缓存起来. 代码里搜了一下, 有 AdaptiveExtensionFactory, AdaptiveCompiler 这两个类上打了 @Adaptive 注解.

  2. 在方法上加 @Adaptive 注解, 会先动态生成适配类的 Java 代码 (可以打断点在 ExtensionLoader.createAdaptiveExtensionClassCode() 中看到生成的类), 再默认用 Javassist 生成动态编译的 Adaptive 字节码.

ExtensionLoader.loadFile() 时: 会判断类名是否含有 @Adaptive 注解. 若有, 则将此类作为适配类 缓存起来. (如果一个接口的所有实现类都没有类上打上这个注解的情况, 则会用 Javassist 生成一个 Adaptive 类)

@Activate 注解

可以被框架中自动激活加载扩展, 这个注解用于配置扩展被自动激活的加载条件.

用户通过 group 和 value 配置激活条件. 被 @Activate 注解的扩展点在满足某种条件时会被激活, 它一般用来配合 filter, 声明他们的使用场景.

Wrapper

Wrapper 类同样实现了扩展点接口, 但是 Wrapper 不是扩展点的真正实现. 它的用途主要是用于从 ExtensionLoader 返回扩展点时, 包装在真正的扩展点实现外层. 即从 ExtensionLoader 中返回的实际上是 Wrapper 类的实例, Wrapper 持有了实际的扩展点实现类.

扩展点的 Wrapper 类可以有多个, 也可以根据需要新增.

通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中. 新加的 Wrapper 在所有的扩展点上添加了逻辑, 有些类似 AOP, 即 Wrapper 代理了扩展点.

ExtensionLoader

ExtensionLoader 是扩展点载入器, 用于载入 Dubbo 中的各种可配置组件, 比如: 动态代理方式 (ProxyFactory), 负载均衡策略(LoadBalance),RCP 协议(Protocol), 拦截器(Filter), 容器类型(Container), 集群方式(Cluster) 和注册中心类型 (RegistryFactory) 等.

获取 ExtensionLoader 流程

1
2
3
4
5
6
7
8
9
10
- ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class)
根据拓展点的接口, 获得拓展加载器. 如根据 Protocol 接口获取它对应的 ExtensionLoader
- ExtensionLoader.getExtensionLoader(Class<T> type) 中
- 1.type 必须是接口
- 2.type 上包含 @SPI 注解
- 3. 根据 type 尝试从 EXTENSION_LOADERS 缓存中获取 ExtensionLoader
- 4. 在 3 中获取不到, 则 new ExtensionLoader<T>(type) 创建并缓存起来
- ExtensionLoader(Class<?> type) 构造器中, 若传入的 type 不是 ExtensionFactory 类型, 则会获取 ExtensionFactory 的 adaptiveExtension 赋值到当前 ExtensionLoader 对象的 objectFactory 属性中, 这个 objectFactory 类似于 IoC 容器, 用于向拓展对象注入依赖的属性.
- // 参见 objectFactory 创建的逻辑
- 5. 返回 ExtensionLoader

获取 AdaptiveExtension 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- Protocol adaptiveExtension = loader.getAdaptiveExtension()
注解在接口的方法上, SPI 机制可以根据接口动态地生成自适应类 xxx$Adaptive, 并实例化这个类并返回
- ExtensionLoader.getAdaptiveExtension() 中
- 1. 先尝试从 cachedAdaptiveInstance 缓存中获取 adaptive 对象
- 2. 在 1 中获取不到, 则 createAdaptiveExtension() 创建 adaptive 对象并缓存起来
- ExtensionLoader.createAdaptiveExtension() 中
- 1.(T) getAdaptiveExtensionClass().newInstance()
获取 adaptive 拓展类
- ExtensionLoader.getAdaptiveExtensionClass() 中
- 1.getExtensionClasses()
获取所有拓展实现类
- ExtensionLoader.getExtensionClasses() 中
- 1. 尝试从 cachedClasses 中获取拓展实现类数组
- 2. 获取不到则 classes = loadExtensionClasses() 从配置文件中加载拓展实现类列表并缓存
- ExtensionLoader.loadExtensionClasses() 中
- 1. 从接口, 如 Protocol 接口上获取 @SPI 注解的 value 属性值, 如 `@SPI("dubbo"),value` 就是 dubbo, 作为默认拓展名
- 2. 分别从 META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services / 路径下读取配置文件, 加载拓展实现类列表
- ExtensionLoader.loadFile() 中
- 1. 根据文件名用类加载器获取 urls 数组
- 2. 遍历 urls 数组, 用流一行行读取配置文件
- 3.1. 根据配置的类名创建 Class 对象
- 3.2. 判断 class 上是否含有 @Adaptive 注解. 若有, 缓存到 cachedAdaptiveClass 中
- 3.3. 若实现类中没有 @Adaptive 注解
- 3.3.1. 判断实现类是否存在入参为接口的构造器 (比如 ProtocolFilterWrapper 类是否还有入参为 Protocol 接口的构造器, public ProtocolFilterWrapper(Protocol protocol), 若有的话说明它是 Wrapper 类), 添加到 wrappers 中
- 3.3.2. 既不是适配对象 (@Adaptive), 也不是 wrapped 的对象, 那就是扩展点的具体实现对象, 可以有多个. 缓存到 cachedNames 和 extensionClasses 中, 分别为 class -> name 的映射 和 name -> class 的映射
- 2. 在 1 中, 若配置的类上有 @Adaptive 注解, 则缓存到 cachedAdaptiveClass 中. 此处取到直接返回 cachedAdaptiveClass. 若没有 adaptive 类, 则走下面生成 adaptive 类
- 3.createAdaptiveExtensionClass()
自动生成自适应拓展的代码实现, 并编译后返回该类
- ExtensionLoader.createAdaptiveExtensionClass() 中
- 1.createAdaptiveExtensionClassCode()
生成 Adaptive 类的代码 code
- 2. 利用 dubbo 的 SPI 扩展机制获取 Compiler 的适配类, 此处为 AdaptiveCompiler 对象
- 3.compiler.compile(code, classLoader) 编译上面生成的 adaptive 代码
- AdaptiveCompiler.compile() 中
- 1.compiler = loader.getDefaultExtension() 获得默认的 Compiler 拓展对象
- 2.compiler.compile(code, classLoader) 编译类
- AbstractCompiler.compile()
- JavassistCompiler.doCompile()
- 4. 返回编译后的 class
- 2.injectExtension()
注入依赖
- // 参见 SPI IOC 流程
- 3. 创建 Wrapper 拓展对象
- // 参见 SPI AOP 流程
- 3. 返回 adaptive 对象

根据 name 获取拓展点对象的流程

1
2
3
4
5
6
7
8
9
10
11
12
- Protocol dubboProtocol = loader.getExtension("dubbo");
返回指定名字的扩展对象
- ExtensionLoader.getExtension(name) 中
- 1. 从 cachedInstances 获取对应的拓展对象
- 2. 若获取不到, 创建 Holder 对象, 并缓存
- 3. 通过 instance = createExtension(name) 填充 holder.value 属性
- 1. 之前的从配置文件中加载拓展类的逻辑
- 2. 缓存到 EXTENSION_INSTANCES 中
- 3. 注入依赖的属性 injectExtension(instance)
- ExtensionLoader.injectExtension() 中
- 1. 遍历 class 中的所有方法找到 setter 方法
- 2. 对于每一个 setter 方法 // 参见 SPI IOC 流程

objectFactory 创建的逻辑

1
2
3
4
5
6
7
- objectFactory(即 AdaptiveExtensionFactory) 创建的逻辑
- 在 ExtensionLoader 的构造方法中会判断当前传入的对象类型
- 1. 如果 type 是 ExtensionFactory 类型, 则 objectFactory 设为 null
- 2.type 不是 ExtensionFactory 类型, 则会创建 ExtensionFactory objectFactory 用于后面给 拓展对象 注入依赖的属性
- 1. 从配置文件中获取 adaptive 类的逻辑
- 2.AdaptiveExtensionFactory 构造器中
- 获取 ExtensionFactory 的 ExtensionLoader, 用 loader 获取支持的 extensions, 此处为 SPIExtensionFactory 和 SpringExtensionFactory

SPI IOC 流程

ExtensionLoader.injectExtension() 这个方法中, 会通过 IOC 机制注入拓展点对象的依赖.
ExtensionLoader.injectExtension() 被调用的地方
查看调用发现, 在创建拓展对象 和 创建自适应拓展对象 时, 都会调用该方法完成依赖注入.

原理就是:
遍历当前对象的所有方法, 找出 setter 方法, 获取参数名和参数类型. 通过 ExtensionFactory 去完成依赖注入.
ExtensionFactory 在之前 ExtensionLoader 创建时使用的是 AdaptiveExtensionFactory 的实现.
AdaptiveExtensionFactory 会调用 SPIExtensionFactory 去获取依赖的拓展对象.
SPIExtensionFactory 给当前拓展点对象注入的均是 adaptiveExtension, 这样在运行期可以动态切换依赖的具体实现.

1
2
3
4
5
6
7
8
- AdaptiveExtensionFactory 注入依赖的流程
- 当有实例调用 injectExtension(instance) 要注入依赖时, 会遍历这个对象里的所有方法, 找出 setter 方法, 如 setXxx(X xxx) 则传入参数类型 和 参数名
- AdaptiveExtensionFactory.getExtension(type, name) 中
- 1. 遍历 SPIExtensionFactory 和 SpringExtensionFactory
- 2.factory.getExtension(type, name) 获取该属性的 extension
- SPIExtensionFactory.getExtension(type, name) 中
- 1. 类型必须是接口且有 @SPI 注解, 才会通过 ExtensionLoader 加载 adaptiveExtension 拓展, 否则返回 null
- 如果有 adaptiveExtension 拓展, 则反射调用 setter 方法注入

SpiExtensionFactory.getExtension(type, name) 代码如下:

1
2
3
4
5
6
7
8
9
10
11
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { // 接口类型 且 有 @SPI 注解
// 加载拓展接口对应的 ExtensionLoader 对象
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
// 加载拓展对象
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}

SPI AOP 流程

在 ExtensionLoader.createExtension(name) 方法中, 根据 name 获取到拓展点实现类 并 通过 injectExtension() 完成依赖注入后, 会创建 Wrapper 对象.
就是获取当前对象的所有 wrapper(这些 wapper 是在读取配置文件时判断有参数为对应的接口类型的构造器 来添加的), 然后调用有参构造器一层一层的通过 injectExtension() 包装起来.

1
2
3
4
5
- SPI AOP 流程
- 1. 遍历从配置文件中加载的 wrapperClasses
- 2.1. 使用带参的构造器创建 Wrapper 对象
- 2.2. 使用 wrapper 对象调用 injectExtension() 返回 instance
- 2.3. 每遍历一次, 就将 wapper 对象嵌套了一层
1
2
- SPI @Activate 缓存到 cachedActivates 流程
- ExtensionLoader.loadFile() 里判断类上有 Activate 注解, 则添加到缓存中.

ExtensionLoader.createExtension(name) 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private T createExtension(String name) {
// 1. 获得拓展名对应的拓展实现类
Class<?> clazz = getExtensionClasses().get(name);
// ...

// 2. 注入依赖的属性
injectExtension(instance);

// 3. 创建 Wrapper 拓展对象
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {// 先实例化扩展点的实现, 再判断此时是否有此扩展点的包装类缓存, 有的话利用 wrapper 增强这个扩展点实现的功能
for (Class<?> wrapperClass : wrapperClasses) {
// 包装
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); // 包装后又赋值给 instance, 所以最终 Wrapper 是一层层嵌套的
}
}
return instance;
}

SPI @Activate 流程

1
2
3
4
5
6
7
8
9
10
- SPI @Activate 流程
- ExtensionLoader.getActivateExtension(url, values, group)
- 1. 通过 getExtensionClasses() 获得拓展实现类列表
- 2. 遍历 cachedActivates
- 2.1.isMatchGroup() 判断 activate 对象的 group 配置是否和传入的 group 匹配
- 2.2. 若上一步匹配, 则通过 getExtension(name) 获得拓展对象
- 2.3. 判断传入的 names 是否匹配, isActive(activate, url) 通过 activate.value 值判断是否激活
- 2.4. 若上面条件都满足, 添加到 list 里, 并通过 ActivateComparator 给 activate 列表排序
- ActivateComparator.compare() 排序逻辑
- 1. 获取比较的两个类上的 Activate 注解, 通过 before 或 after 属性进行排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Get activate extensions.
*
* 获得符合自动激活条件的拓展对象数组
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// 获得拓展实现类列表
getExtensionClasses();
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { // 遍历 cachedActivates
String name = entry.getKey();
Activate activate = entry.getValue();
if (isMatchGroup(group, activate.group())) { // 匹配分组
// 获得拓展对象
T ext = getExtension(name);
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) { // 判断是否激活
exts.add(ext);
}
}
}
// 排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
// ...
return exts;
}