Dubbo 2.7 Spring容器整合与核心接口体系
学习目标
完成本章后,你能够:
- 解释Spring自定义XML标签的NamespaceHandler + BeanDefinitionParser扩展机制
- 说明ServiceBean与ReferenceBean在Spring容器中的生命周期节点
- 阐述Invocation、Invoker、Exporter、Directory、Result等核心接口的抽象分层
- 理解Dubbo注解驱动配置的工作方式
1. Dubbo与Spring的XML整合
1.1 Spring自定义标签的扩展体系
Spring容器能理解<dubbo:service/>、<dubbo:reference/>等自定义标签,关键在于Spring的标准XML扩展机制:
bash
Spring自定义标签三步走:
① 编写XSD文件(dubbo.xsd)------定义新标签的语法结构
② 实现NamespaceHandler ------ 负责"发现新标签 → 分派解析"
③ 实现BeanDefinitionParser ------ 负责"解析标签 → 生成BeanDefinition"
配置文件引路:
- META-INF/spring.handlers(标签处理器映射)
- META-INF/spring.schemas(XSD文件映射)
java
/**
* spring.handlers 文件内容:
* http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
*
* spring.schemas 文件内容:
* http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
*/
1.2 DubboNamespaceHandler源码
java
/**
* DubboNamespaceHandler ------ 标签处理器注册中心
*
* 继承Spring的 NamespaceHandlerSupport 基类
* 核心方法 init() 中注册所有标签对应的解析器
*
* Spring调用链:
* XmlBeanDefinitionReader → NamespaceHandlerResolver →
* 查找 spring.handlers → DubboNamespaceHandler.init() →
* 注册 parser → 解析器.parse() → BeanDefinition
*/
public class DubboNamespaceHandlerSource extends NamespaceHandlerSupport {
static {
// 防止多次引入Dubbo Jar包导致版本冲突
Version.checkDuplicate(DubboNamespaceHandler.class);
}
/**
* init() 是Spring容器创建BeanDefinition时的入口点
* 在此注册所有Dubbo标签的解析器
*
* 每个标签对应一个XML元素:
* <dubbo:application/> → ApplicationConfig.class 的解析
* <dubbo:service/> → ServiceBean.class 的解析
* <dubbo:reference/> → ReferenceBean.class 的解析
*/
@Override
public void init() {
// ===== Dubbo基础配置标签 =====
registerBeanDefinitionParser("application",
new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module",
new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry",
new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center",
new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report",
new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor",
new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics",
new DubboBeanDefinitionParser(MetricsConfig.class, true));
// ===== Provider/Consumer配置标签 =====
registerBeanDefinitionParser("provider",
new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer",
new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol",
new DubboBeanDefinitionParser(ProtocolConfig.class, true));
// ===== 服务暴露与引用标签(核心标签) =====
// 参数true:表示此Bean是"ID引用"(其他标签可以通过id引用它)
registerBeanDefinitionParser("service",
new DubboBeanDefinitionParser(ServiceBean.class, true));
// 参数false:表示此Bean生成自己的代理,不是被引用对象
registerBeanDefinitionParser("reference",
new DubboBeanDefinitionParser(ReferenceBean.class, false));
// ===== 注解支持标签 =====
registerBeanDefinitionParser("annotation",
new AnnotationBeanDefinitionParser());
}
}
1.3 DubboBeanDefinitionParser.parse 核心逻辑
java
/**
* DubboBeanDefinitionParser ------ 标签属性解析器
*
* 负责将XML标签的属性映射到对应的Java Bean属性
* 例如:<dubbo:application name="my-app"/>
* → ApplicationConfig.setName("my-app")
*
* 解析模式(支持多级属性设值):
* <dubbo:protocol name="dubbo" port="20880">
* <dubbo:parameter key="threads" value="200"/>
* </dubbo:protocol>
*
* → ProtocolConfig.name = "dubbo"
* → ProtocolConfig.port = 20880
* → ProtocolConfig.parameters = {"threads": "200"}
*/
public class BeanDefinitionParserDetail {
/**
* parse的核心策略:
*
* 1. 普通属性 → 直接设置
* <dubbo:application name="xxx"/>
* → BeanDefinitionBuilder.addPropertyValue("name", "xxx")
*
* 2. 子标签属性 → 嵌套Bean
* <dubbo:service>
* <dubbo:method name="sayHello" timeout="1000"/>
* </dubbo:service>
* → ServiceBean.methods = [MethodConfig(name="sayHello", timeout=1000)]
*
* 3. id属性特殊处理
* - <dubbo:service id="xxx"/> → id作为Bean在Spring容器中的名称
* - 如果没有id → 默认用 interface 属性值
* - 如果都没指定 → dubbo自动按顺序生成名称
*
* 4. id引用处理(属性值为其他Bean的id)
* <dubbo:service ref="myServiceBean"/>
* → ServiceBean.ref = RuntimeBeanReference("myServiceBean")
* (不直接赋值,等容器启动后再从容器中查找)
*/
/**
* 多协议配置解析:
*
* <dubbo:service protocol="dubbo,rest"/>
* → ServiceBean.protocols = {"dubbo", "rest"}
* → doExportUrls() 时会为每个协议创建一个URL并分别暴露
*/
}
2. Bean生命周期回调
2.1 ServiceBean------Provider容器感知
java
/**
* ServiceBean 实现了多个Spring生命周期感知接口
*
* 生命周期回调链(Spring容器启动→停止):
*
* afterPropertiesSet() → 设置Provider属性
* ↓
* setApplicationContext() → 获取Spring容器引用(用于将ServiceBean自身注册)
* ↓
* onApplicationEvent(ContextRefreshedEvent) → ★ 触发服务导出!
* ↓
* destroy() → 取消导出
*/
public class ServiceBeanLifecycle
extends ServiceConfig<Object>
implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>,
BeanNameAware, ApplicationEventPublisherAware {
/**
* Spring Bean属性设置完成后的回调
* 在此可以做一些初始化检查,但不要在此导出服务
*/
@Override
public void afterPropertiesSet() throws Exception {
// 检查provider配置,确保必要的属性已设置
if (getProvider() == null) {
// 如果没有显式配置provider,从全局PROVIDER_CONFIG获取
setProvider(ApplicationModel.getConfigManager()
.getDefaultProvider()
.orElseGet(() -> {
ProviderConfig providerConfig = new ProviderConfig();
getModuleConfigManager().addConfig(providerConfig);
return providerConfig;
}));
}
}
/**
* ★ 最重要的回调:Spring容器刷新完毕
*
* ContextRefreshedEvent 在以下时机触发:
* - Root WebApplicationContext 初始化完成时
* - ClassPathXmlApplicationContext.refresh() 完成时
*
* 为什么选择这个时机导出服务?
* 1. 所有Spring Bean都已初始化完毕
* 2. 依赖的其他Service可能也已就绪
* 3. 避免服务过早暴露导致调用方访问到一个未完全初始化的Provider
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 防止重复导出(根容器和子容器各触发一次事件)
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: "
+ getInterface());
}
// === 触发服务导出 ===
export();
}
}
/**
* 服务导出------支持延迟导出
*
* export() → checkAndUpdateSubConfigs() → shouldExport() → doExport()
*/
public synchronized void export() {
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return; // 不需要导出(例如dubbo:service没有指定interface)
}
// === 延迟导出处理 ===
if (shouldDelay()) {
// delay > 0 → 延迟指定毫秒后导出
DELAY_EXPORT_EXECUTOR.schedule(
this::doExport,
getDelay(),
TimeUnit.MILLISECONDS
);
} else {
// delay = -1 或 0 → 立即导出
doExport();
}
}
/**
* Bean销毁时的回调------清理资源
*/
@Override
public void destroy() throws Exception {
// 取消所有协议的导出
// unexport() → 从注册中心注销 → 关闭Netty Server
unexport();
}
}
2.2 ReferenceBean------Consumer容器感知
java
/**
* ReferenceBean ------ Consumer端的核心Bean
*
* 关键接口:
* - FactoryBean:使其返回的不是自身,而是远程代理对象
* - InitializingBean:初始化配置
* - DisposableBean:销毁连接
*
* 与ServiceBean的关键区别:
* - ServiceBean 在 ContextRefreshedEvent 中主动触发 export
* - ReferenceBean 的初始化是懒加载的:第一次调用 getObject() 时才 init()
*/
public class ReferenceBeanLifecycle<T>
extends ReferenceConfig<T>
implements FactoryBean, ApplicationContextAware,
InitializingBean, DisposableBean {
/**
* FactoryBean.getObject() ------ 返回的是Dubbo动态代理,而非ReferenceBean自身
*
* Spring容器中的表现:
* - context.getBean("&greetingService") → ReferenceBean 实例
* - context.getBean("greetingService") → Dubbo动态代理(透明远程调用)
*/
@Override
public Object getObject() {
return get();
}
/**
* FactoryBean.getObjectType() ------ 告知Spring返回对象的类型
*/
@Override
public Class<?> getObjectType() {
return getInterfaceClass();
}
/**
* FactoryBean.isSingleton() ------ 远程代理是单例的
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* 初始化回调 ------ 并非在此处做引用初始化
* 实际的引用初始化是懒加载的
*/
@Override
public void afterPropertiesSet() throws Exception {
// 检查consumer配置
if (getConsumer() == null) {
getModuleConfigManager()
.getDefaultConsumer()
.ifPresent(this::setConsumer);
}
}
/**
* 懒加载初始化 ------ 第一次调用时触发
*
* 为什么懒加载?
* - 避免启动顺序依赖(Consumer甚至可以在Provider之前启动)
* - 减少启动时的资源消耗
* - 某些Consumer可能在整个生命周期中不会被调用
*/
public synchronized T get() {
checkAndUpdateSubConfigs();
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init(); // ★ 首次调用时初始化
}
return ref;
}
/**
* 初始化引用------构建远程代理
*/
private void init() {
if (initialized) {
return;
}
initialized = true;
// 1. 构建URL(包含所有配置参数)
Map<String, String> map = new HashMap<>();
map.put(SIDE_KEY, CONSUMER_SIDE);
// 2. 创建代理
ref = createProxy(map);
// 3. 注册Consumer模型(供Admin展示)
ApplicationModel.initConsumerModel(serviceKey, consumerModel);
}
}
3. 核心接口体系
3.1 Invocation------单次调用上下文
java
/**
* Invocation接口 ------ 封装一次RPC调用的所有信息
*
* 地位:Dubbo中最小的调用描述单元
* 行为:Consumer构造 → 跨JVM传输 → Provider接收
*/
public interface InvocationDetail {
/** 目标方法名,如 "getUser" */
String getMethodName();
/** 方法参数类型列表,如 [Long.class, String.class] */
Class<?>[] getParameterTypes();
/** 方法实参值列表,如 [12345L, "张三"] */
Object[] getArguments();
/**
* 隐式参数(attachments)
*
* 不体现在方法签名中,但在RPC调用上下文中传递的附加信息
* 类似于HTTP Header之于HTTP请求
*
* 典型内容:
* - traceId / spanId:分布式链路追踪
* - path:服务路径
* - interface:接口全限定名
* - timeout:超时重写值
* - token / userId:身份透传
*/
Map<String, String> getAttachments();
/** 获取当前关联的Invoker */
Invoker<?> getInvoker();
/** 设置隐性参数 */
void setAttachment(String key, String value);
}
3.2 Invoker------可执行实体
java
/**
* Invoker接口 ------ Dubbo最核心的领域模型
*
* 抽象层次:
* Invoker 是一个"可以被 execute 的对象"(统称可执行体)
*
* 三层Invoker体系:
*
* ① Cluster层Invoker ------ 聚合多个Provider的Invoker
* - MockClusterInvoker, FailoverClusterInvoker
* - 负责容错、降级、路由选择
*
* ② Protocol层Invoker ------ 指向具体Provider的Invoker
* - DubboInvoker(TCP连接)
* - InjvmInvoker(JVM直通)
*
* ③ Proxy层Invoker ------ 已被Proxy包装,在Provider端执行
* - AbstractProxyInvoker
*/
public interface InvokerDetail<T> {
/**
* 获取该Invoker关联的Service接口
* 如:GreetingService.class
*/
Class<T> getInterface();
/**
* ★ 核心方法:执行远程调用
*
* @param invocation 调用上下文(方法名+参数)
* @return 调用结果(Result对象,包含返回值或异常信息)
* @throws RpcException 调用过程异常
*/
Result invoke(Invocation invocation) throws RpcException;
/**
* 获取关联的URL ------ 包含所有配置参数
* URL是Dubbo的"身份证",通过它可以获取任何配置信息
*/
URL getUrl();
/**
* 检测该Provider是否可用
* - DubboInvoker中检测Channel是否打开
* - ClusterInvoker中检测是否有可用清单
*/
boolean isAvailable();
/**
* 销毁资源
* - DubboInvoker:断开TCP连接
* - ClusterInvoker:销毁所有子Invoker
*/
void destroy();
}
3.3 Exporter------暴露的服务引用
java
/**
* Exporter接口 ------ 已暴露的服务抽象
*
* 生命:export() 创建 → unexport() 销毁
* 作用:维护对Invoker的引用,提供下线能力
*/
public interface ExporterDetail<T> {
/**
* 获取内部被暴露的Invoker
*
* 示例:
* DubboExporter.getInvoker() → 返回Provider端的AbstractProxyInvoker
* (包括了GreetingServiceImpl的包装)
*/
Invoker<T> getInvoker();
/**
* 反暴露(服务下线)
*
* 执行的动作:
* 1. 从注册中心移除Provider节点
* 2. 关闭Exporter关联的Netty Server
* 3. 释放关联的连接池资源
*/
void unexport();
}
3.4 Directory------Invoker目录
java
/**
* Directory接口 ------ Provider列表的抽象
*
* "目录"概念的来源------就像电话本:
* - 你可以从电话本获取所有可用的联系人(Provider)
* - 电话本可以随时更新(来自注册中心的通知)
*
* 实现层级:
* - StaticDirectory:固定的Provider名单(直连模式/不变化)
* - RegistryDirectory:动态的Provider名单(来自注册中心的拉取+推送)
*/
public interface DirectoryDetail<T> {
/**
* 获取可用的Provider端点类型
*/
Class<T> getInterface();
/**
* ★ 核心方法:获取当前所有可用的Invoker列表
*
* RegistryDirectory中的返回值:
* - 来自注册中心的最新Provider列表
* - 已经过滤了Router(路由) + 已转换为Invoker
*
* 调用链中的位置:
* FailoverClusterInvoker.invoke()
* → directory.list(Invocation)
* ← 返回可用Provider的Invoker列表
* → loadBalance.select(invokers, invocation)
* ← 从中选出一个Invoker
* → Invoker.invoke(invocation)
*/
List<Invoker<T>> list(Invocation invocation) throws RpcException;
/**
* 是否还有有效的Provider
*/
boolean isAvailable();
/**
* 销毁------取消对注册中心的监听
*/
void destroy();
}
3.5 Result------调用结果
java
/**
* Result接口 ------ RPC调用的结果抽象
*
* 设计与传统的 try-catch模型不同:
* Result 对象本身包含"成功值"或"失败异常"
* 调用方统一通过 Result 处理,不需要区分正常流程和异常流程
*/
public interface ResultDetail {
/**
* ★ 获取实际返回值
*
* 与 Future.get() 类似------但语义更丰富:
* - 如果调用成功:返回实际的方法返回值
* - 如果调用异常:重新抛出原始异常
*
* 内部实现:
* if (exception != null) throw exception;
* else return result;
*/
Object getValue();
/**
* 是否有消费端的异步回调需要处理
*/
boolean hasException();
/**
* 获取异常信息(如果调用失败)
* null 表示调用成功
*/
Throwable getException();
/**
* 重新创建一个Result对象(用于Filter链中的Result转换)
* 返回新的Result,包含修改后的返回值
*/
Result recreate();
/**
* 获取关联的Response回调信息
* 用于异步回调的处理
*/
Object getResult();
/**
* 获取相关联的attachments(Provider→Consumer传递的隐藏信息)
*/
Map<String, String> getAttachments();
}
4. 注解驱动的配置方式
java
/**
* Dubbo @EnableDubbo 注解的执行原理
*
* @EnableDubbo 是一个聚合注解,内部导入
* DubboConfigConfigurationRegistrar 和 DubboConfigConfigurationSelector
*
* 执行过程:
*
* ① Spring Boot启动 → 检测@EnableDubbo注解
* ② DubboComponentScanRegistrar → 扫描@Service注解的类
* ③ ServiceAnnotationBeanPostProcessor → Bean后置处理
* → 发现@Service注解 → 创建ServiceBean
* → ServiceBean.export() → 服务导出
*
* ④ ReferenceAnnotationBeanPostProcessor → Bean后置处理
* → 发现@Reference注解 → 创建ReferenceBean
* → ReferenceBean.getObject() → 返回远程代理
*
* 这就是为什么在Spring Boot中只需要加@EnableDubbo注解
* 就能完全替代XML配置------背后是一系列的Spring容器扩展点
*/
@EnableDubbo
@SpringBootApplication
public class AnnotationDrivenApp {
@Reference(version = "1.0.0")
private UserService userService; // ← 自动创建代理
// @Service 注解的Provider端
@org.apache.dubbo.config.annotation.Service
@Component
public static class UserServiceImpl implements UserService {
// ...
}
}
本章总结
本章涵盖Dubbo与Spring生态的深度融合:
| 整合层面 | Spring扩展点 | Dubbo实现 |
|---|---|---|
| XML标签 | NamespaceHandler + BeanDefinitionParser | DubboNamespaceHandler + DubboBeanDefinitionParser |
| Bean生命周期 | InitializingBean/DisposableBean | ServiceBean/ReferenceBean |
| 容器事件 | ApplicationListener(ContextRefreshedEvent) | Provider服务导出入口 |
| FactoryBean | FactoryBean.getObject() | Consumer远程代理创建 |
| 注解驱动 | BeanPostProcessor | @Service/@Reference/@EnableDubbo |
| 核心接口 | Invocation/Invoker/Exporter/Directory/Result | 贯穿Dubbo全链路 |
核心要点:
- ServiceBean监听ContextRefreshedEvent触发服务导出
- ReferenceBean实现FactoryBean,懒加载初始化
- Invoker是Dubbo最核心的可执行抽象,一切调用皆Invoker.invoke()