Dubbo的SPI扩展机制:@SPI、@Adaptive注解与ExtensionLoader源码
Dubbo的SPI扩展机制:@SPI、@Adaptive注解与ExtensionLoader源码
适读人群:Dubbo用户、想深入理解框架扩展性设计的Java开发者 | 阅读时长:约20分钟
开篇故事
第一次读 Dubbo 源码,我在 ExtensionLoader 上卡了整整两天。
那时候我对 Dubbo 的理解只是"一个RPC框架",觉得它和 Spring Cloud 差不多。结果翻源码一看,里面有个 ExtensionLoader,和 Spring 容器的设计有几分相似,但又完全不一样——它有自己的依赖注入、自己的 AOP(Wrapper 机制)、自己的条件加载。
后来我才意识到,Dubbo 几乎把整个框架的每一个核心概念都做成了扩展点:协议(Protocol)、负载均衡(LoadBalance)、注册中心(Registry)、序列化(Serialization)、过滤器(Filter)……用户可以替换其中任何一个。
这种"一切皆可扩展"的设计哲学,依靠的就是 Dubbo 的 SPI 机制。今天把它拆开来讲清楚。
一、Dubbo SPI 与 Java SPI 的核心差异
1.1 对比一览
| 特性 | Java SPI | Dubbo SPI |
|---|---|---|
| 配置文件位置 | META-INF/services/ | META-INF/dubbo/、META-INF/dubbo/internal/、META-INF/services/ |
| 配置格式 | 一行一个类名 | name=完整类名(KV格式) |
| 按名称获取 | 不支持 | getExtension("dubbo") |
| 默认扩展 | 不支持 | @SPI("默认名称") |
| 自适应扩展 | 不支持 | @Adaptive,运行时动态选择 |
| Wrapper包装 | 不支持 | 自动检测构造器,实现AOP |
| 依赖注入 | 不支持 | setter注入,类似IoC |
| 加载策略 | 全量加载 | 按需懒加载 |
1.2 三个核心注解
@SPI:标记一个接口是 Dubbo 扩展点,并指定默认实现名称。
// 协议扩展点,默认使用 "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;
void destroy();
}@Adaptive:可以加在类上或方法上。加在类上表示这个类是手动实现的自适应扩展;加在方法上表示该方法需要动态生成代理,从 URL 参数里读取扩展名。
@SPI("javassist")
public interface ProxyFactory {
// @Adaptive加在方法上:运行时从URL里读"proxy"参数决定用哪个实现
@Adaptive({"proxy", "generic"})
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
@Adaptive({"proxy"})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}二、ExtensionLoader 源码解析
2.1 整体架构
2.2 配置文件格式
Dubbo SPI 使用 KV 格式,比 Java SPI 的纯类名列表更灵活:
文件位置:META-INF/dubbo/org.apache.dubbo.rpc.Protocol
# 格式:name=实现类全限定名
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol2.3 ExtensionLoader 核心方法
// ExtensionLoader.java 核心代码(简化版)
public class ExtensionLoader<T> {
// 接口类型
private final Class<T> type;
// 已加载的扩展缓存(name -> 实例)
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 已加载的扩展类缓存(name -> Class)
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// Wrapper类集合
private Set<Class<?>> cachedWrapperClasses;
/**
* 按名称获取扩展实例(单例,带缓存)
*/
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name is null");
}
if ("true".equals(name)) {
// "true" 是特殊名称,返回默认扩展
return getDefaultExtension();
}
final 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;
}
/**
* 创建扩展实例(核心方法)
*/
private T createExtension(String name) {
// 1. 获取扩展类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 2. 反射实例化
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 3. 依赖注入(setter注入,从其他ExtensionLoader获取依赖)
injectExtension(instance);
// 4. 用所有Wrapper包裹(AOP效果)
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
// Wrapper的构造器接受接口类型,形成包装链
instance = injectExtension((T) wrapperClass
.getConstructor(type).newInstance(instance));
}
}
// 5. 触发初始化(如果实现了Lifecycle接口)
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance create failed", t);
}
}
/**
* 依赖注入:扫描setter方法,从其他ExtensionLoader获取依赖
*/
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
// setter的参数类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
// 从ObjectFactory获取依赖(可能是另一个Dubbo扩展,也可能是Spring Bean)
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
}
} catch (Exception e) {
logger.error("Failed to inject extension via setter", e);
}
return instance;
}
}2.4 @Adaptive 自适应扩展的动态代理
@Adaptive 加在方法上时,Dubbo 会在运行时生成一个代理类,从方法参数的 URL 中读取扩展名,然后调用真正的实现:
// Dubbo 动态生成的 Protocol$Adaptive 代码(类似于这样)
public class Protocol$Adaptive implements Protocol {
@Override
public Exporter export(Invoker invoker) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("Invoker is null");
}
URL url = invoker.getUrl();
// 从URL里读取协议名,例如 "dubbo://..." 读出 "dubbo"
String extName = url.getProtocol();
if (extName == null || extName.isEmpty()) {
throw new IllegalStateException("No protocol name in URL: " + url);
}
// 按名称获取真正的Protocol实现
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension(extName);
return extension.export(invoker);
}
}三、完整代码实现
3.1 自定义 Dubbo Filter
Dubbo 的 Filter 扩展点是最常用的自定义扩展,用来做链路追踪、日志、限流等:
SPI 注册文件:src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
tracing=com.laozhang.dubbo.filter.TracingFilter
rate-limit=com.laozhang.dubbo.filter.RateLimitFilter链路追踪 Filter:
package com.laozhang.dubbo.filter;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.MDC;
import java.util.UUID;
/**
* Dubbo 链路追踪 Filter
*
* @Activate 注解说明:
* - group = PROVIDER:只在服务提供方生效
* - group = CONSUMER:只在服务消费方生效
* - 不加 group:提供方和消费方都生效
* - order:执行顺序,数字越小越先执行
*/
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -10000)
public class TracingFilter implements Filter {
private static final String TRACE_ID_KEY = "traceId";
private static final String SPAN_ID_KEY = "spanId";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 消费方:生成或传递 traceId
String traceId = RpcContext.getClientAttachment().getAttachment(TRACE_ID_KEY);
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
RpcContext.getClientAttachment().setAttachment(TRACE_ID_KEY, traceId);
}
// 设置到 MDC,让日志自动带上 traceId
String oldTraceId = MDC.get(TRACE_ID_KEY);
MDC.put(TRACE_ID_KEY, traceId);
long start = System.currentTimeMillis();
try {
Result result = invoker.invoke(invocation);
long elapsed = System.currentTimeMillis() - start;
// 记录调用日志
String interfaceName = invocation.getInvoker().getInterface().getSimpleName();
String methodName = invocation.getMethodName();
if (result.hasException()) {
logger.error("[Dubbo] {}.{} 调用异常,耗时{}ms,traceId={}",
interfaceName, methodName, elapsed, traceId, result.getException());
} else {
logger.info("[Dubbo] {}.{} 调用成功,耗时{}ms,traceId={}",
interfaceName, methodName, elapsed, traceId);
}
return result;
} finally {
// 恢复旧的 MDC 状态
if (oldTraceId != null) {
MDC.put(TRACE_ID_KEY, oldTraceId);
} else {
MDC.remove(TRACE_ID_KEY);
}
}
}
}3.2 自定义负载均衡策略
package com.laozhang.dubbo.lb;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义负载均衡:最少活跃连接数优先
* SPI name = "least-active-custom"
*/
public class LeastActiveCustomLoadBalance extends AbstractLoadBalance {
// 记录每个 invoker 的活跃请求数
private final ConcurrentHashMap<String, AtomicInteger> activeCountMap =
new ConcurrentHashMap<>();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
Invoker<T> selected = null;
int minActive = Integer.MAX_VALUE;
for (Invoker<T> invoker : invokers) {
String key = invoker.getUrl().toIdentityString();
int activeCount = activeCountMap
.computeIfAbsent(key, k -> new AtomicInteger(0))
.get();
if (activeCount < minActive) {
minActive = activeCount;
selected = invoker;
}
}
// 选中后活跃数+1
if (selected != null) {
String key = selected.getUrl().toIdentityString();
activeCountMap.get(key).incrementAndGet();
}
return selected;
}
}注册文件:META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
least-active-custom=com.laozhang.dubbo.lb.LeastActiveCustomLoadBalance在服务消费方使用自定义策略:
@DubboReference(loadbalance = "least-active-custom")
private UserService userService;四、踩坑实录
坑1:@Activate 的 group 设置不对
症状:自定义 Filter 在消费方不生效,但提供方正常。
根因:@Activate(group = CommonConstants.PROVIDER) 只在提供方生效。如果要两边都生效:
// 只提供方
@Activate(group = CommonConstants.PROVIDER)
// 只消费方
@Activate(group = CommonConstants.CONSUMER)
// 两边都生效
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER})坑2:Wrapper 类被误认为普通扩展
Dubbo Wrapper 机制:如果一个类的构造器接受接口类型作为参数,ExtensionLoader 会自动识别它为 Wrapper,用它包裹所有真正的实现(类似 AOP)。
踩坑:有人不知道这个规则,写了一个"辅助类",构造器正好接受接口类型,结果 Dubbo 把它当成 Wrapper,套在了所有实现外面,产生了意外的行为。
// Dubbo 会把这个识别为 Wrapper!
public class MyHelper implements Protocol {
private Protocol protocol; // 接受接口类型的构造器
public MyHelper(Protocol protocol) { // <-- 这是 Wrapper 的标志
this.protocol = protocol;
}
// ...
}检查方式:ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions() 不会返回 Wrapper 类名,可以此验证。
坑3:扩展类里用了 Spring Bean,但获取不到
症状:自定义 Filter 里 @Autowired 的 Spring Bean 是 null。
根因:Dubbo 的扩展类由 ExtensionLoader 创建,不在 Spring 容器管理,@Autowired 不生效。
解决方案:使用 SpringExtensionFactory,Dubbo 的依赖注入支持从 Spring 容器获取 Bean,但必须通过 setter 方式:
@Activate(group = CommonConstants.PROVIDER)
public class TracingFilter implements Filter {
// 不用 @Autowired,用 setter 注入
private RedisTemplate<String, Object> redisTemplate;
// Dubbo 的 injectExtension 会通过 setter 注入
// 它会从 SpringExtensionFactory 里找这个类型的 Spring Bean
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
}坑4:自定义扩展点发布到 JAR 后不生效
症状:本地测试没问题,打成 JAR 引入其他项目后 Filter 不生效。
根因:SPI 文件没有被打包进 JAR,或者文件路径不对。
验证方法:
# 解压 JAR,检查 SPI 文件是否存在
jar tf your-component.jar | grep META-INF/dubbo
# 预期输出类似:
# META-INF/dubbo/org.apache.dubbo.rpc.Filter五、总结与延伸
Dubbo SPI 是目前 Java 生态里设计最完整的扩展机制:
- KV 格式配置:按名称按需加载,不像 Java SPI 全量加载
- @Adaptive 自适应:运行时从 URL 动态选择实现,是 Dubbo "一切基于 URL" 设计理念的体现
- Wrapper 机制:用构造器约定替代注解,实现了极简的 AOP
- 依赖注入:setter 注入 + SpringExtensionFactory,打通了 Dubbo 和 Spring 的边界
理解了这三篇(455-457)的 SPI 三重奏,你会发现它们是同一个思想的不同实现:接口与实现解耦,通过约定好的配置文件在运行时发现实现。只是功能越来越强,设计越来越精妙。
