
ExtensionLoader、自适应扩展、包装器扩展和激活扩展,深入分析 Dubbo 的 SPI 机制,并关联到负载均衡、路由、序列化、协议和过滤器等上层特性。• Dubbo SPI 不是 JDK ServiceLoader 的简单封装。它在 JDK SPI 基础上增加了命名扩展、默认扩展、URL 驱动的自适应分发、自动依赖注入、包装器链、激活式自动装配、作用域扩展加载器、生命周期管理以及兼容性加载等能力。
• ExtensionLoader#getExtension(name) 创建命名实现时,会依次执行:注入 setter 依赖 → 用匹配的包装器类包装 → 运行后置处理器 → 初始化生命周期钩子。
• getAdaptiveExtension() 返回一个手写的 @Adaptive 类,或者动态生成的 $Adaptive 类,后者从 URL 读取扩展名并委托给真实扩展。
• 包装器扩展通过构造器形状自动识别:只要有一个以 SPI 接口类型为唯一参数的公开构造器,就会被识别为包装器。包装器广泛用于 Protocol 的过滤器、监听器、序列化、安全和调用计数等增强。
• 激活扩展通过 @Activate(group, value, order) 加上 URL 参数和用户显式配置来筛选。这是过滤器和监听器链组装的核心机制。
• Dubbo 3 起扩展查找由 ExtensionDirector 和 ScopeModel 划分作用域,框架/应用/模块边界对扩展加载有直接影响。
核心加载器Dubbo 按 SPI 接口全名加载扩展声明文件,例如:
META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter
META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.LoadBalance
META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization
当前加载策略优先级从高到低:
1. DubboInternalLoadingStrategy:META-INF/dubbo/internal/,最高优先级
2. DubboLoadingStrategy:META-INF/dubbo/,支持覆盖低优先级条目
3. ServicesLoadingStrategy:META-INF/services/,同样支持覆盖,并保持与 Service Provider 风格资源的兼容
每行资源配置格式为:
name=fully.qualified.Implementation
也可以只写类名而不显式命名,但显式命名更优,因为 Dubbo 可以将加载失败与配置的扩展名关联起来。
ExtensionLoader 会缓存已加载的资源内容、扩展类、扩展名、激活元数据、包装器类、已创建的扩展实例以及自适应实例。除非当前 LoadingStrategy 允许覆盖,否则重复名称会被拒绝。
加载器还提供类加载器控制能力:
• 可以优先使用 Dubbo 自身的类加载器加载内部扩展
• 可以限制包含/排除的包
• 可以强制特定包前缀只从 ExtensionLoader 的类加载器加载
• 有 special_spi.properties 路径用于框架专用 SPI,避免扫描大量用户类加载器导致性能或安全问题
旧版 Dubbo 常直接调用静态方法 ExtensionLoader.getExtensionLoader(type)。该方法仍存在但已废弃。现代代码通常从 ScopeModel 获取加载器:
frameworkModel.getExtensionLoader(...)
applicationModel.getExtensionLoader(...)
moduleModel.getExtensionLoader(...)
ScopeModelUtil.getExtensionLoader(..., url.getScopeModel())
ExtensionDirector 是作用域加载管理器,维护类似类加载器委托的父链:
ModuleModel -> ApplicationModel -> FrameworkModel
SPI 接口通过 @SPI(scope = ...) 声明自己的作用域。如果当前 director 无法为该作用域创建加载器,它会请求父级。这让 Protocol、Compiler、Serialization 等框架级 SPI 可以共享,而模块/应用级 SPI 可以保持隔离。
这解释了为什么自适应生成代码不使用全局静态加载器。AdaptiveClassCodeGenerator 生成如下代码:
ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), SpiType.class);
SpiType extension = (SpiType) scopeModel.getExtensionLoader(SpiType.class).getExtension(extName);
因此 URL 的 scope model 决定了自适应扩展查找哪个扩展宇宙。

getExtension(name) 是获取具体扩展的主要路径:
1. 校验加载器未销毁且名称非空
2. 将特殊名称 "true" 视为"使用默认扩展"
3. 使用按名称的 Holder 避免重复创建
4. 从已加载扩展类中解析实现类
5. 从 extensionInstances 创建或复用原始实现实例
6. 初始化前运行扩展后置处理器
7. 注入 setter 依赖
8. 初始化后运行扩展后置处理器
9. 如果启用包装,排序匹配的包装器类,依次用前一个扩展实例实例化每个包装器,注入并后置处理
10. 如果最终对象实现 Lifecycle#initialize,则调用初始化

这意味着 getExtension(name) 返回的对象通常不只是资源文件里的实现类,而可能是经过一个或多个包装器装饰后的对象。
Dubbo SPI 注入基于 setter 方法:
• 方法必须为 public
• 方法名必须以 set 开头
• 必须只有一个非基本类型参数
• @DisableInject 可跳过注入
• 作用域感知和扩展访问器感知的 setter 会在适当情况下被跳过,避免意外注入
被注入对象通过自适应 ExtensionInjector 解析。实践中,这让 SPI 实现可以接收其他 SPI 扩展或 Spring Bean,而无需硬编码创建路径。
自适应扩展是 Dubbo 的 URL 驱动分发层。
SPI 可以通过在实现类上标注 @Adaptive 提供手写自适应类。如果没有找到,ExtensionLoader 会在首次调用 getAdaptiveExtension() 时生成一个。
生成要求至少有一个接口方法标注了 @Adaptive。对于每个自适应方法,生成代码会:
1. 直接或间接(通过参数的 getUrl() 方法)定位 URL 参数
2. 如果方法有 Invocation 参数,读取调用方法名
3. 从 @Adaptive("...") 声明的 URL 参数构建 extName
4. 如果配置了 @SPI 默认扩展,则回退到默认值
5. 如果无法解析扩展名则抛出错误
6. 从 URL 的 ScopeModel 找到正确作用域的 ExtensionLoader
7. 按 extName 获取具体扩展
8. 将原始方法调用委托给该扩展
典型示例:
负载均衡:LoadBalance 是 @SPI(RandomLoadBalance.NAME),其 select 方法是 @Adaptive("loadbalance")。生成的自适应负载均衡器读取方法级或 URL 级的 loadbalance 参数,默认为 random。
协议:Protocol 是 @SPI(value = "dubbo", scope = FRAMEWORK)。其 export 和 refer 方法是自适应的,因此 url.getProtocol() 选择 dubbo、tri、grpc、injvm 或其他协议。
序列化:Serialization 有自适应的 serialize 和 deserialize 方法。URL 的序列化参数决定使用 hessian2、fastjson2 还是其他已注册序列化器。
Transporter、Dispatcher、Exchanger 以及许多注册中心/配置中心工厂都遵循相同模式。

生成的自适应源码通过 Compiler SPI 编译。默认编译器 SPI 是 javassist;AdaptiveCompiler 本身是一个自适应编译器,会选择配置的编译器或默认编译器。在 Native Image 模式下,ExtensionLoader 尝试加载预生成的 InterfaceName$Adaptive 类而不是运行时编译源码。
包装器扩展通过结构特征自动识别:类有一个以 SPI 接口类型为唯一参数的公开构造器。不需要特殊标记,但 @Wrapper 可以约束其应用条件。
@Wrapper 支持:
• matches:只包装选定的扩展名
• mismatches:排除选定的扩展名
• order:绝对包装顺序
ExtensionLoader#createExtension 按 WrapperComparator 对包装器类排序,反转排序后的列表,然后按迭代顺序包装。最终返回的对象是一个嵌套的装饰器链。
重要的 Protocol 包装器示例:
• ProtocolFilterWrapper:用提供者/消费者 Filter 链包装非注册中心协议的 export/refer 路径
• ProtocolListenerWrapper:用激活的监听器包装导出器和调用器
• ProtocolSerializationWrapper、ProtocolSecurityWrapper 和 InvokerCountWrapper 也注册为 Protocol 包装器
• 具体协议如 DubboProtocol、TripleProtocol、GrpcProtocol、RestProtocol 和 InjvmProtocol 注册为命名 Protocol 扩展
这就是为什么调用 getAdaptiveExtension(Protocol).refer(type, url) 不会简单实例化 DubboProtocol 或 TripleProtocol。自适应协议选择命名协议,加载器返回包装器装饰后的协议实例。

包装顺序是行为的敏感部分。靠近链外层的包装器在入口时更早看到调用,在出口时更晚看到返回。这就是为什么 Dubbo 将包装顺序从 @Activate 移到了专用的 @Wrapper 注解。
激活扩展用于类列表链的自动扩展选择。
@Activate 提供:
• group:侧别或逻辑分组,如 consumer 或 provider
• value:URL 参数键,或 key:value 形式的键值对,必须存在才激活
• order:绝对顺序,值越小越靠前
• onClass:可选的类存在守卫
ExtensionLoader#getActivateExtension(url, key, group) 工作流程:
1. 如果提供了 key,从 url.getParameter(key) 读取显式名称
2. 加载并缓存所有激活元数据
3. 添加分组匹配、名称未被显式排除、URL 激活键匹配的默认激活扩展
4. 尊重 -name 排除和 -default 移除所有默认值
5. 如果显式名称包含 default,将默认扩展放在该位置
6. 否则添加显式名称并按 ActivateComparator 排序完整集合

这是 Dubbo 过滤器模型的核心。DefaultFilterChainBuilder 调用:
getActivateExtension(url, "service.filter", "provider")
getActivateExtension(url, "reference.filter", "consumer")
然后从列表末尾向原始调用器构建链。同样的激活机制用于监听器链、状态检查器、路由工厂、条件匹配器、REST 扩展、线协议和其他扩展列表。
LoadBalance 是带自适应 select 方法的命名 SPI。资源文件映射:
random=RandomLoadBalance
roundrobin=RoundRobinLoadBalance
leastactive=LeastActiveLoadBalance
consistenthash=ConsistentHashLoadBalance
shortestresponse=ShortestResponseLoadBalance
adaptive=AdaptiveLoadBalance
集群调用器可以调用自适应负载均衡器,让 URL 或方法参数决定具体算法。这保持集群调用逻辑与算法实现解耦。
路由工厂和状态路由工厂是 SPI。RouterChain 通过 getActivateExtension(url, ROUTER_KEY) 获取激活的路由工厂,然后构建路由列表。条件路由、标签路由、脚本路由、应用级服务发现路由和 mock 路由都插接到这个模型。
Serialization 是框架作用域 SPI。每个序列化模块贡献自己的资源:
hessian2=org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization
fastjson2=org.apache.dubbo.common.serialize.fastjson2.FastJson2Serialization
wrapper=org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper
远程编解码器向序列化扩展加载器请求由 URL/协议元数据或序列化 ID 命名的序列化器。这保持协议编解码器与序列化器实现分离。
Protocol 既是自适应的又是重度包装的。资源文件在 dubbo-rpc-api 和 dubbo-cluster 注册包装器,协议模块注册实际协议:
dubbo=DubboProtocol
tri=TripleProtocol
grpc=GrpcProtocol
rest2=RestProtocol
injvm=InjvmProtocol
自适应协议按 URL 协议选择,包装器链在实际协议周围添加过滤器、监听器、序列化/安全行为和计数器。
Filter 实现是激活式而非单一命名扩展。内置过滤器用 @Activate 声明分组和顺序,用户可以通过 service.filter 和 reference.filter 等 URL 参数添加/移除过滤器。这就是为什么过滤器行为可以自然地与提供者/消费者侧和方法级 URL 配置组合。
1. 类加载器歧义:同一 SPI 资源可能从多个类加载器可见。Dubbo 通过 ScopeModel 类加载器绑定、加载策略和特殊 SPI 限制来缓解。
2. 重复名称:除非加载策略支持覆盖,否则重复扩展名会被拒绝。这防止意外遮蔽,但如果依赖贡献冲突资源可能破坏启动。
3. 包装器顺序敏感性:包装器顺序改变可观察行为。协议过滤器、监听器、安全和序列化包装器都在同一条调用路径上。
4. 意外注入:公开 setter 可能触发 SPI 注入。对于不是依赖注入点的 setter,使用 @DisableInject。
5. 自适应键错误:错误的 @Adaptive 键会导致生成类选择错误扩展或抛出 "failed to get extension name from url"。
6. 激活意外:@Activate(value = ...) 依赖 URL 参数。一个配置键可能隐式启用过滤器或检查器。
7. Native Image 约束:运行时代码生成并不总是可用。Native 模式依赖预生成的自适应类和反射元数据。
8. 包装器上的生命周期:ExtensionLoader 初始化最终返回的实例,但注释警告如果包装器本身不实现 Lifecycle,生命周期实现可能被包装器隐藏。
相关提交展示了 Dubbo SPI 如何从静态扩展加载器演化为作用域化、优化、更安全的扩展运行时:
提交 | 时间 | 说明 |
|---|---|---|
8879afac25 | 2018-08-16 | 增加 @Adaptive 在找不到指定扩展时尝试默认扩展的能力 |
3cf801bb9d | 2018-11-01 | 添加关闭 SPI 自动注入的能力,@DisableInject |
7a53a1b3b9 | 2019-02-11 | 重构自适应扩展类代码生成,提取 AdaptiveClassCodeGenerator |
3e78bd8867 | 2019-10-21 | 用 Dubbo ExtensionLoader 替换 ServiceLoader |
7234078e8c | 2020-04-29 | 为 ExtensionLoader 添加覆盖规则 |
ee4d90910a | 2021-06-01 | 修复 SPI 包装器的空指针异常 |
89aac3c2a4 | 2021-07-13 | 修复激活扩展顺序 |
ff39bf36c3 | 2021-09-02 | 添加 3.0 多实例支持 |
23a4ff0a0d | 2021-10-10 | 修复创建 SPI 自适应类的并发问题 |
5ab9afdf0c | 2021-10-29 | 支持销毁作用域 bean 和 SPI 扩展实例 |
335bf5ae52 | 2021-11-06 | 减少获取扩展时的对象创建 |
78c7225a82 | 2021-12-16 | 将包装器类顺序从 @Activate 改为 @Wrapper |
80599857a8 | 2022-02-18 | 在 ExtensionLoader 中添加包含包过滤器 |
4be39e4744 | 2022-07-12 | 重构 ExtensionInjector 及相关 |
659249d07d | 2022-07-27 | 修复 SPI 类部分 setXXX 方法注入问题 |
7cf5fe42f2 | 2024-04-13 | 使用 ReentrantLock 加载扩展类 |
ab07a4b155 | 2025-04-07 | 修复生成包装器对象时的初始化问题 |
49e5c5e551 | 2025-06-18 | 替换 ConcurrentHashMap#computeIfAbsent 避免潜在死锁 |
演进方向一致:保持扩展模型的表达力,同时收紧类加载器边界、顺序、并发、生命周期和注入安全性。
审查或添加 Dubbo SPI 扩展时:
1. 确认 SPI 接口标注了 @SPI 且作用域正确
2. 在正确目录添加资源文件。框架提供的扩展用 META-INF/dubbo/internal/,外部扩展包用 META-INF/dubbo/ 或 META-INF/services/
3. 选择稳定的扩展名,避免与内置名称冲突
4. 如果扩展应由 URL 选择,在 SPI 方法上标注 @Adaptive 并验证 URL 键与实际配置匹配
5. 如果扩展是包装器,验证构造器形状、@Wrapper 的 matches/mismatches 和顺序
6. 如果扩展是自动激活的,验证分组、激活键、顺序以及通过 -name 的显式移除行为
7. 审查公开 setter。对不应接收 SPI/Spring 注入的 setter 添加 @DisableInject
8. 检查生命周期:如果被包装对象需要初始化或销毁,确保最终返回对象仍暴露所需生命周期行为
9. 对于 Native Image 支持,检查自适应类和反射描述符是否生成或注册