Apache Dubbo 的 SPI(Service Provider Interface)机制 是其插件化架构的核心,它不仅借鉴了 Java 原生的 SPI 机制,还进行了大量增强和优化,使得 Dubbo 可以灵活地支持各种协议、序列化方式、注册中心等组件。
一、Java 原生 SPI
Java 原生 SPI 允许开发者通过接口 + 配置文件的方式动态加载实现类。例如:
- 定义一个接口:
com.example.MyService
- 实现类:
com.example.impl.MyServiceImpl
- 在
META-INF/services/com.example.MyService
文件中写入:com.example.impl.MyServiceImpl
使用时通过 ServiceLoader.load(MyService.class)
获取所有实现。
缺点:
- 无法按需加载:一次性加载所有实现。
- 不支持依赖注入。
- 没有扩展名概念。
- 性能差、功能单一。
二、Dubbo SPI 的优势
Dubbo 对 SPI 进行了增强,主要特性包括:
特性 | 描述 |
---|---|
按名称加载 | 支持根据扩展名获取指定实现 |
自适应扩展 | 支持自动生成适配类(@Adaptive) |
自动包装 | 支持装饰器模式(Wrapper 类) |
IOC & AOP | 支持依赖注入与拦截器链 |
扩展缓存 | 提高性能,避免重复加载 |
三、Dubbo SPI 的核心类:ExtensionLoader
ExtensionLoader<T>
是 Dubbo SPI 的核心类,用于加载某个接口的所有扩展实现。
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = loader.getExtension("dubbo"); // 根据名称加载
主要方法:
方法 | 说明 |
---|---|
getExtensionLoader(Class<T> type) | 获取某个接口的扩展加载器 |
getExtension(String name) | 获取指定名称的扩展实例 |
getAdaptiveExtension() | 获取自适应扩展(自动代理) |
getDefaultExtensionName() | 获取默认扩展名(在 @SPI 注解中定义) |
hasExtension(String name) | 判断是否存在该扩展名 |
四、Dubbo SPI 的配置文件位置
Dubbo 的扩展配置文件路径为:
META-INF/dubbo/
META-INF/dubbo/internal/
META-INF/services/
例如对于 org.apache.dubbo.rpc.Protocol
接口,在 META-INF/dubbo/org.apache.dubbo.rpc.Protocol
中内容如下:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
五、@SPI 注解的作用
@SPI
注解用于标记一个接口是 Dubbo 的扩展接口,并可以指定默认实现。
@SPI("dubbo")
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;
}
上面表示,默认实现是 "dubbo"
,即 DubboProtocol
。
六、@Adaptive 注解与自适应扩展
@Adaptive
注解用于生成一个自适应扩展类,这个类会根据运行时参数(如 URL)决定调用哪个具体的实现。
示例:
@Adaptive
public void destroy() {
String extName = getUrl().getParameter("protocol", "dubbo");
getExtension(extName).destroy(); // 动态选择实现
}
自动生成代码示例(简化):
public class Protocol$Adaptive implements Protocol {
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
String protocolName = invoker.getUrl().getParameter("protocol", "dubbo");
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName);
return protocol.export(invoker);
}
}
七、Wrapper 自动包装机制
Dubbo 支持 Wrapper 类,用于对扩展进行增强,类似于装饰器模式。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
this.protocol = protocol;
}
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return protocol.export(new FilterInvokerListenerExporter(invoker));
}
}
如果某个实现类有构造函数传入接口类型,则会被认为是一个 Wrapper。
八、IOC 和 AOP 支持
Dubbo 的 SPI 支持自动注入依赖(类似 Spring 的 IOC),也支持 AOP 增强。
1. IOC 注入
public class MyProtocol implements Protocol {
private LoadBalance loadBalance;
public void setLoadBalance(LoadBalance loadBalance) {
this.loadBalance = loadBalance;
}
}
Dubbo 会自动查找并注入 LoadBalance
的实现。
2. AOP 支持
Dubbo 支持通过 Wrapper 实现 AOP 功能,比如日志、监控等。
九、Dubbo SPI 加载流程
+-----------------------------+
| ExtensionLoader.getExtensionLoader(Interface.class) |
+-----------------------------+
↓
+-----------------------------+
| 加载 META-INF/dubbo/xxx 文件 |
+-----------------------------+
↓
+-----------------------------+
| 解析 key=value,构建扩展类 |
+-----------------------------+
↓
+-----------------------------+
| 创建 Wrapper 或普通类实例 |
+-----------------------------+
↓
+-----------------------------+
| 如果是 Adaptive,生成代理类 |
+-----------------------------+
↓
+-----------------------------+
| 返回扩展实例(含 IOC/AOP) |
+-----------------------------+
十、源码分析(关键类)
1. ExtensionLoader.getExtension(String name)
public T getExtension(String name) {
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name); // 创建扩展实例
holder.set(instance);
}
}
}
return (T) instance;
}
2. createExtension(String name)
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name); // 获取类
T instance = (T) clazz.newInstance(); // 创建实例
injectExtension(instance); // 注入依赖
for (Object wrapper : wrapperInstances) {
instance = injectExtension((T) wrapper); // 包装
}
return instance;
}
十一、总结
特性 | Dubbo SPI | Java SPI |
---|---|---|
按名称加载 | ✅ | ❌ |
自适应扩展 | ✅ | ❌ |
自动包装 | ✅ | ❌ |
IOC 支持 | ✅ | ❌ |
AOP 支持 | ✅ | ❌ |
性能优化 | ✅ | ❌ |
多配置目录 | ✅ | ❌ |
Dubbo 的 SPI 是整个框架插件化的基石,理解其原理有助于深入掌握 Dubbo 的架构设计和源码实现。