Java 与 Dubbo 的 SPI 机制介绍
Java SPI 机制
概念介绍
Java 原生支持 SPI 机制,具体介绍如下:
- 核心概念
- SPI(Service Provider Interface)是一种服务发现机制。
- SPI 的核心思想是定义一个接口,由多个实现类提供不同的实现方式,在系统运行时根据配置或者默认策略,动态加载并使用具体的实现类。
- SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样就可以在运行时,动态为接口替换实现类。
工作原理
- (1) 接口与实现类
- 假设有一个接口
A,它有多个实现类:A -> A1、A2、A3。
- 假设有一个接口
- (2) 配置实现类
- 可以在配置文件中指定接口
A对应使用哪个实现类。
- 可以在配置文件中指定接口
- (3) 运行时加载
- 程序启动时,会读取配置文件,根据配置信息找到对应的实现类,实例化并使用该对象。
- (4) Java 原生 SPI 机制
- Java 原生 SPI 机制的使用要求:
- 在
resources/META-INF/services/目录下,创建一个与接口全限定名相同的文件,例如:resources/META-INF/services/com.example.service.A - 文件内容的格式是一行一个实现类的全限定名(可以有多行,即支持多个不同的实现类),例如:
com.example.service.impl.A1。 - 运行时通过
ServiceLoader等工具扫描依赖的 Jar 包,在其中查找该文件,并加载指定的实现类,比如:ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
- 在
- Java 原生 SPI 机制的使用要求:
- (1) 接口与实现类
应用场景
- SPI 机制常用于插件式扩展。
- 比如:如果你在开发一个框架,可以通过 SPI 让外部开发者编写插件,扩展框架的功能,而不必修改框架的源码。
典型案例
- JDBC
- Java 标准库只定义了一套 JDBC 接口,并没有真正的实现。
- 数据库厂商(如 MySQL、Oracle)会提供自己的实现,并通过 SPI 机制声明在
resources/META-INF/services/目录中。 - 运行时,Java 会根据项目引入的数据库驱动 Jar 包,自动找到对应的 JDBC 实现类。
- JDBC
Dubbo SPI 机制
概念介绍
Dubbo 借鉴了 SPI 思想,但没有直接使用 Java 原生的 SPI 机制,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader 类可以加载指定的实现类。
工作原理
- (1) 接口声明
- 在 Dubbo 中,如果某个接口需要支持 SPI 扩展,就会加上
@SPI注解,比如Protocol接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 接口定义
public interface Protocol {
int getDefaultPort();
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
// 加载实现类
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); @SPI("dubbo")表示默认实现是dubbo。@Adaptive表示该方法会生成代理逻辑,运行时根据参数动态选择实现类。
- 在 Dubbo 中,如果某个接口需要支持 SPI 扩展,就会加上
- (2) 实现类配置
- Dubbo 在自己 Jar 包中的
resources/META-INF/dubbo/internal/路径下提供了一个配置文件,文件名是接口的全限定名,比如:1
resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
- 配置文件的内容是
key=实现类的全限定名,key 对应@SPI注解中的扩展名称,比如:1
2
3dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
- Dubbo 在自己 Jar 包中的
- (3) 默认实现加载
- 如果用户没有配置扩展,Dubbo 会根据
@SPI("dubbo")的默认值dubbo,从配置文件中加载对应的实现类DubboProtocol。 - 这也是 Dubbo 默认使用 Dubbo 协议作为 RPC 通信协议的原因。
- 如果用户没有配置扩展,Dubbo 会根据
- (4) 动态切换实现类
- 在
Protocol接口中,有两个方法加了@Adaptive注解。 - Dubbo 会在运行时生成代理类,在代理方法内部根据传入的 URL 参数的
protocol值决定使用哪个实现类。 - 如果 URL 参数中没指定协议,就用默认的
dubbo;如果指定了其他值(如http),则加载对应的实现类。
- 在
- (1) 接口声明
实现特点
- 微内核 + 可插拔:保留一个接口和多个实现,运行时可替换。
- 组件化:如
Protocol负责 RPC 调用,可以替换为自定义的 RPC 组件。 - 动态扩展:可通过 URL 参数或配置文件,在运行时动态切换实现类。
- 增强的 SPI:相比 Java 原生的 SPI 机制,Dubbo 的实现支持:
- 支持指定默认的实现类(
@SPI注解的默认值) - 运行时动态选择实现类(
@Adaptive) - 接口定义(扩展点)自动生成代理类
- 支持指定默认的实现类(
使用总结
- Dubbo SPI 本质上是一个运行时可扩展、可替换的组件机制。
- Dubbo 大量核心组件(如
Protocol、Cluster、Registry等)都是用这种 SPI 机制实现扩展的。 - 通过
@SPI注解 + 配置文件来确定默认的实现类,通过@Adaptive注解 + URL 参数来实现动态切换实现类。 - Dubbo 的 SPI 文件路径和 Java SPI 的不一样,核心目录是
resources/META-INF/dubbo/接口全限定名,Dubbo 内部使用的扩展目录是resources/META-INF/dubbo/internal/接口全限定名,文件内容是key=实现类的全限定名。
加载流程
Dubbo SPI 机制加载扩展的核心步骤:
- (1) 读取并解析配置文件
- (2) 缓存所有扩展实现类
- (3) 基于用户执行的扩展名,实例化对应的扩展实现类
- (4) 执行扩展实例属性的 IOC 注入(基于
Setter注入),以及实例化扩展的包装类,实现 AOP 特性
Dubbo SPI 机制加载扩展的整个流程:

