一文彻底搞懂 Dubbo SPI 底层原理
我会用最通俗、最底层、最清晰的方式,把 Dubbo SPI 从是什么 → 为什么要用 → 底层源码流程 → 核心机制讲透,让你一次彻底吃透。
一、先搞懂:什么是 SPI?
SPI(Service Provider Interface):服务发现机制,接口+配置文件+实现类,程序运行时动态加载接口的实现类,实现模块化、可插拔、可扩展。
简单说:
- 定义一个接口
- 写多个实现类
- 在配置文件里写:
key=实现类全类名 - 运行时通过
key拿到对应实现类
Java 自带 JDK SPI,但它有 3 个致命缺陷:
- 一次性加载所有实现,浪费资源
- 不能根据
key按需获取实现 - 不支持 IOC、AOP、默认值、排序等扩展
所以 Dubbo 重写了一套更强大的 SPI,解决了所有问题。
二、Dubbo SPI 核心定位
Dubbo SPI = 可插拔扩展机制
Dubbo 90% 以上的功能(协议、注册中心、负载均衡、序列化、过滤器…)都是基于 SPI 实现的。
它的核心能力:
- 按需加载,不浪费资源
- key-value 配置,按名称获取实现
- 支持默认实现
- 支持 IOC 依赖注入
- 支持 AOP 包装类(Wrapper)
- 支持排序、自动激活、条件加载
三、Dubbo SPI 使用规范(快速入门)
1. 接口上加注解
@SPI("random") // 默认实现为 random
public interface LoadBalance {
String select(List<Invoker> invokers);
}
2. 配置文件路径(固定)
META-INF/dubbo/接口全类名
内容格式:
random=com.xxx.RandomLoadBalance
roundrobin=com.xxx.RoundRobinLoadBalance
3. 获取实现
// 获取扩展加载器
ExtensionLoader<LoadBalance> loader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
// 按 name 获取
LoadBalance lb = loader.getExtension("roundrobin");
// 获取默认实现
LoadBalance defaultLb = loader.getDefaultExtension();
四、Dubbo SPI 底层原理(核心!逐行拆解)
这是全文最关键部分,我会从类结构 → 缓存 → 加载流程 → 注入 → 包装完整拆解。
核心类:ExtensionLoader
Dubbo SPI 唯一入口,一个接口对应一个 ExtensionLoader 实例。
核心流程(底层 8 步)
getExtension(name)
→ 查缓存
→ 无则加载配置文件
→ 解析类
→ 实例化
→ IOC 依赖注入
→ AOP 包装(Wrapper)
→ 返回最终对象
下面逐段拆解。
1. 多级缓存(Dubbo 高效的关键)
Dubbo SPI 大量使用缓存,避免重复加载、反射创建。
主要缓存:
- cachedInstances:
name → 扩展类实例(最终对象) - cachedClasses:
name → 扩展类 Class - cachedWrappers:包装类列表
- cachedAdaptiveInstance:自适应拓展对象
2. 加载配置文件(底层扫描路径)
Dubbo 会扫描 3 个固定路径:
META-INF/dubbo/
META-INF/dubbo/internal/ (Dubbo 内部使用)
META-INF/services/ (兼容 JDK SPI)
解析规则:
- 忽略 # 注释
- 格式
key=value - 重复 key 后面覆盖前面
3. 实例化扩展类
简单调用:
clazz.newInstance()
4. IOC 依赖注入(自动装配)
Dubbo 会自动扫描扩展类的 set 方法,自动注入其他扩展。
例如:
public class XxxLoadBalance {
private RegistryService registryService;
// 自动注入
public void setRegistryService(RegistryService registryService) {
this.registryService = registryService;
}
}
底层:
injectExtension(instance)
自动找到 set 方法 → 从 ExtensionLoader 获取对应扩展 → 注入。
5. AOP 包装(Wrapper 机制)
包装类 = 增强器,类似 Filter,对目标扩展进行功能增强。
规则:
- 包装类持有目标接口对象
- 构造方法参数是接口类型
- 自动被识别为 Wrapper
示例:
public class LoadBalanceWrapper implements LoadBalance {
private final LoadBalance delegate;
// 包装类构造器
public LoadBalanceWrapper(LoadBalance delegate) {
this.delegate = delegate;
}
@Override
public String select(List<Invoker> invokers) {
System.out.println("before");
String res = delegate.select(invokers);
System.out.println("after");
return res;
}
}
最终返回对象:
wrapper1(wrapper2(target))
责任链模式。
6. @Adaptive 自适应扩展(最核心黑科技)
Dubbo 最牛的机制:运行时动态决定使用哪个实现。
作用:
- 不在编译期固定实现类
- 执行时根据URL 参数动态选择
例如:
@SPI
public interface LoadBalance {
@Adaptive("loadbalance")
String select(URL url, ...);
}
调用时:
- URL 携带
loadbalance=roundrobin - 自动选择对应实现
底层原理:
- 动态生成代码 + 编译加载
- 生成
$Adaptive类 - 从 URL 取参数 → 找对应扩展 → 调用
7. @Activate 自动激活(条件加载)
用于过滤器、路由等场景:
- 根据条件自动激活
- 支持按
group、value、order排序
例如:
@Activate(group = CONSUMER)
public class MonitorFilter implements Filter {
}
五、Dubbo SPI 完整流程图
getExtension(name)
↓
从 cachedInstances 拿缓存
↓
无 → 创建扩展
↓
加载配置文件 → 解析 cachedClasses
↓
实例化扩展类
↓
IOC 注入依赖(injectExtension)
↓
AOP 包装(Wrapper 包装)
↓
放入 cachedInstances
↓
返回最终对象
六、Dubbo SPI vs JDK SPI
| 特性 | JDK SPI | Dubbo SPI |
|---|---|---|
| 配置格式 | 全类名 | key=全类名 |
| 获取方式 | 迭代器 | 按 name 获取 |
| 懒加载 | ❌ 全部加载 | ✅ 按需加载 |
| IOC | ❌ | ✅ |
| AOP | ❌ | ✅ |
| 默认值 | ❌ | ✅ @SPI("xxx") |
| 自适应 | ❌ | ✅ @Adaptive |
| 条件激活 | ❌ | ✅ @Activate |
七、总结:Dubbo SPI 本质
一句话总结:
Dubbo SPI = 可插拔 + 按需加载 + IOC + AOP + 自适应 + 缓存 = Dubbo 微内核灵魂
它的核心价值:
- 让 Dubbo 高度模块化
- 支持第三方自定义扩展
- 运行时动态选择策略
- 性能极高(全缓存)
总结
- SPI = 接口扩展 + 配置发现
- Dubbo SPI 解决 JDK SPI 所有缺陷
- 核心流程:加载 → 实例化 → 注入 → 包装 → 缓存
- 两大黑科技:@Adaptive(动态选择)、Wrapper(AOP 增强)
- Dubbo 整体架构完全基于 SPI 实现可插拔
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号