Dubbo 的 @Service 注解干了两件事:把类注册为 Spring Bean(给 @Autowired 用)+ 注册 ServiceBean(给 dubbo:// 调用用)。前者是 Spring 的活,后者是 Dubbo 的活,互不干扰。
一篇源码级的梳理,搞懂这条线上的所有细节。
版本说明 :本文基于 Dubbo 2.6.8 源码(
com.alibaba.dubbo),所有源码引用均来自dubbo-2.6.8-sources.jar反编译验证。
引言
阅读前提:本文阅读需要 Spring IoC 基础 + 了解 Dubbo @Service。
在微服务项目中,常有这样的代码结构:
- 接口
A和B定义在同一个模块 - 实现类
C(实现A)和D(实现B)均标注@com.alibaba.dubbo.config.annotation.Service - 在
C中通过 Spring 的@Autowired注入了接口B,并调用其方法

这段代码在启动时能正常工作吗?如果 C 和 D 都在同一 JVM 中,b.methodB() 走的是本地调用还是 Dubbo RPC?
答案是:能正常工作,且走的是纯本地方法调用,不经过 Dubbo 代理。但这个看似简单的结论背后,涉及 Spring IoC 和 Dubbo RPC 两套独立机制的精确配合。
本文以 Dubbo 2.6.8 为蓝本,逐层剖析其实现原理。
💗💗💗您的点赞、收藏、评论是博主输出优质文章的的动力!!!💗💗💗
欢迎在评论区与博主沟通交流!!大量优质博文关注一波不亏!👇🏻 👇🏻 👇🏻
文章目录
-
- 引言
- 一、场景还原
-
- [1.1 Demo 项目代码](#1.1 Demo 项目代码)
- [1.2 Spring + Dubbo 配置](#1.2 Spring + Dubbo 配置)
- [1.3 启动验证](#1.3 启动验证)
- 二、核心机制全景
- [三、路径一:Dubbo 如何将 @Service 类注册为 Spring Bean](#三、路径一:Dubbo 如何将 @Service 类注册为 Spring Bean)
-
- [3.1 入口:< dubbo:annotation> 的解析](#3.1 入口:< dubbo:annotation> 的解析)
- [3.2 核心角色:ServiceAnnotationBeanPostProcessor](#3.2 核心角色:ServiceAnnotationBeanPostProcessor)
- [3.3 ServiceBean 的并行注册](#3.3 ServiceBean 的并行注册)
- [3.4 自定义扫描器的设计](#3.4 自定义扫描器的设计)
- [3.5 Bean 名称的生成逻辑](#3.5 Bean 名称的生成逻辑)
- [3.6 小结:Bean 注册全链路](#3.6 小结:Bean 注册全链路)
- [四、路径二:@Autowired 如何完成注入](#四、路径二:@Autowired 如何完成注入)
-
- [4.1 注入链路的源码级还原](#4.1 注入链路的源码级还原)
- [4.2 为什么 `context:component-scan` 仍然是必需的](#4.2 为什么
context:component-scan仍然是必需的)
- [五、路径三:Dubbo 服务的并行导出(对 @Autowired 无影响但值得了解)](#五、路径三:Dubbo 服务的并行导出(对 @Autowired 无影响但值得了解))
- 六、关键问题的深度解答
-
- [6.1 `b.methodB()` 到底走的是什么路径?](#6.1
b.methodB()到底走的是什么路径?) - [6.2 为什么 @Autowired 注入的是接口类型但能工作?](#6.2 为什么 @Autowired 注入的是接口类型但能工作?)
- [6.3 @Reference 和 @Autowired 在同一 JVM 中的行为差异](#6.3 @Reference 和 @Autowired 在同一 JVM 中的行为差异)
- [6.4 循环依赖](#6.4 循环依赖)
- [6.1 `b.methodB()` 到底走的是什么路径?](#6.1
- 七、Dubbo版本演进
- 八、实战建议
-
- [8.1 配置清单](#8.1 配置清单)
- [8.2 注解选择决策树](#8.2 注解选择决策树)
- [8.3 常见陷阱](#8.3 常见陷阱)
- 九、总结
- 参考资料
一、场景还原
1.1 Demo 项目代码
java
// === 接口 A ===
package com.example.service;
public interface A {
void methodA();
}
// === 接口 B ===
package com.example.service;
public interface B {
void methodB();
}
// === 实现类 C:实现 A,注入 B ===
package com.example.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.example.service.A;
import com.example.service.B;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class C implements A {
@Autowired
private B b;
@Override
public void methodA() {
System.out.println("=== C.methodA executed ===");
System.out.println("准备调用 B.methodB()...");
b.methodB();
}
}
// === 实现类 D:实现 B ===
package com.example.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.example.service.B;
@Service
public class D implements B {
@Override
public void methodB() {
System.out.println("=== D.methodB executed ===");
System.out.println("当前线程: " + Thread.currentThread().getName());
System.out.println("实际类型: " + this.getClass().getName());
}
}
1.2 Spring + Dubbo 配置
xml
<!-- 启用 Spring 注解支持(@Autowired 等) -->
<context:component-scan base-package="com.example.service"/>
<!-- Dubbo 服务注解扫描(仅扫描实现类所在的 impl 子包) -->
<dubbo:annotation package="com.example.service.impl"/>
<dubbo:application name="dubbo-autowired-demo"/>
<!-- 注册中心已注释,零外部依赖即可运行 -->
<!-- <dubbo:registry address="zookeeper://127.0.0.1:2181"/> -->
<dubbo:protocol name="dubbo" port="20880"/>
1.3 启动验证
java
public class Main {
public static void main(String[] args) {
System.out.println("=== 启动 Spring 容器 ===");
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("\n=== 容器启动完成,从容器中获取 A 的实现 Bean ===");
A a = context.getBean(A.class);
System.out.println("A 实际类型: " + a.getClass().getName());
System.out.println("\n=== 调用 a.methodA() ===");
a.methodA();
System.out.println("\n=== 演示完成,关闭容器 ===");
context.close();
}
}
输出 :

从"当前线程: main"和"实际类型: com.example.service.impl.D"可以看出:调用发生在主线程,b 直接指向 D 实例,未经过任何代理层。
二、核心机制全景
要理解为什么这段代码能工作,需要先区分两套完全独立的机制:

关键洞察 :①~④ 是 Spring IoC 层面的行为,与 Dubbo RPC 完全无关。⑤ 只是把 C 和 D 暴露为可供远程 消费者调用的服务,对 C 内部通过 @Autowired 注入 B 的本地调用不产生任何影响。两条路径各司其职,互不干扰。
下面逐一深入剖析。
三、路径一:Dubbo 如何将 @Service 类注册为 Spring Bean
3.1 入口:< dubbo:annotation> 的解析
Dubbo 2.6.8 的 XML Schema 中,<dubbo:annotation> 由 AnnotationBeanDefinitionParser 解析。这个类是 Spring 的 AbstractSingleBeanDefinitionParser 子类,负责将 XML 元素转换为 BeanDefinition。
真实源码 (com.alibaba.dubbo.config.spring.schema.AnnotationBeanDefinitionParser):
java
public class AnnotationBeanDefinitionParser
extends AbstractSingleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder) {
// 1. 提取 package 属性,支持逗号分隔的多个包
String packageToScan = element.getAttribute("package");
String[] packagesToScan =
trimArrayElements(commaDelimitedListToStringArray(packageToScan));
// 2. 作为构造参数传递给 ServiceAnnotationBeanPostProcessor
builder.addConstructorArgValue(packagesToScan);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 3. 同时注册 ReferenceAnnotationBeanPostProcessor(处理 @Reference 注入)
registerReferenceAnnotationBeanPostProcessor(parserContext.getRegistry());
}
@Override
protected Class<?> getBeanClass(Element element) {
return ServiceAnnotationBeanPostProcessor.class; // 核心:注册这个 BPP
}
}
解析结果 :向 Spring 容器注册了一个 ServiceAnnotationBeanPostProcessor 实例,构造参数为 "com.example.service.impl"。同时注册了 ReferenceAnnotationBeanPostProcessor(处理 @Reference,本文暂不展开)。
3.2 核心角色:ServiceAnnotationBeanPostProcessor
ServiceAnnotationBeanPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口。这决定了它的执行时机:
Spring 容器刷新顺序 :
invokeBeanFactoryPostProcessors()→registerBeanPostProcessors()→ ... →finishBeanFactoryInitialization()
BeanDefinitionRegistryPostProcessor 在第一阶段被调用,此时所有 BeanDefinition 都可以被修改或新增,但 Bean 实例尚未创建。这是 Dubbo"抢"在 Spring 实例化 Bean 之前注册自己的 Bean 的窗口。
以下是其核心方法的真实源码(已精简注释):
java
public class ServiceAnnotationBeanPostProcessor
implements BeanDefinitionRegistryPostProcessor,
EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {
private final Set<String> packagesToScan; // 来自 XML 的 package 属性
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
registerServiceBeans(resolvedPackagesToScan, registry);
}
}
private void registerServiceBeans(Set<String> packagesToScan,
BeanDefinitionRegistry registry) {
// 步骤1:创建自定义扫描器
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 步骤2:确定 BeanNameGenerator
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
// 步骤3:设置过滤器------仅匹配 @Service 注解
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
// 步骤4:遍历每个包,执行扫描与注册
for (String packageToScan : packagesToScan) {
scanner.scan(packageToScan); // 注册 @Service Bean
Set<BeanDefinitionHolder> holders =
findServiceBeanDefinitionHolders(scanner, packageToScan,
registry, beanNameGenerator);
for (BeanDefinitionHolder holder : holders) {
registerServiceBean(holder, registry, scanner); // 注册 ServiceBean
}
}
}
}
执行逻辑概括(三步走):
- scan :扫描类路径,找到所有标注
@Service的类,注册为 Spring Bean - find :重新定位刚注册的 Bean,封装为
BeanDefinitionHolder(含 Bean 名称) - registerServiceBean :为每个
@ServiceBean 额外注册一个ServiceBean包装定义
第三步值得展开------它揭示了 Dubbo 如何"附加"RPC 能力而不改变 Bean 本身。
3.3 ServiceBean 的并行注册
registerServiceBean() 的真实源码如下:
java
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder,
BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
Class<?> beanClass = resolveClass(beanDefinitionHolder);
Service service = findAnnotation(beanClass, Service.class);
// 解析接口类型(从 @Service 的 interfaceClass/interfaceName 或自动推断)
Class<?> interfaceClass = resolveServiceInterfaceClass(beanClass, service);
// 原始 Bean 的名称(如 "c")
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
// 构建 ServiceBean 的 BeanDefinition,ref 指向原始 Bean
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, interfaceClass, annotatedServiceBeanName);
// 生成 ServiceBean 的名称,格式: ServiceBean:<接口全限定名>
String beanName = generateServiceBeanName(service, interfaceClass,
annotatedServiceBeanName);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) {
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
}
}
generateServiceBeanName() 使用 ServiceBeanNameBuilder(真实源码):
java
class ServiceBeanNameBuilder {
private static final String SEPARATOR = ":";
public String build() {
StringBuilder beanNameBuilder = new StringBuilder("ServiceBean").append(SEPARATOR);
append(beanNameBuilder, interfaceClassName); // 如 com.example.service.A
append(beanNameBuilder, version);
append(beanNameBuilder, group);
// 移除末尾 SEPARATOR
String rawBeanName = beanNameBuilder.substring(0, beanNameBuilder.length() - 1);
return environment.resolvePlaceholders(rawBeanName);
}
}
以 Demo 项目为例 ,dubbo:annotation package="com.example.service.impl" 触发扫描后,Spring 容器中新增以下 BeanDefinition:
| Bean 名称 | 类型 | 作用 |
|---|---|---|
c |
com.example.service.impl.C |
业务 Bean,@Autowired 的注入来源和注入目标 |
d |
com.example.service.impl.D |
业务 Bean,@Autowired 的注入来源 |
ServiceBean:com.example.service.A |
ServiceBean |
Dubbo 导出用,ref 指向 c |
ServiceBean:com.example.service.B |
ServiceBean |
Dubbo 导出用,ref 指向 d |
#mermaid-svg-rVEj25KuzynL5Kt4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rVEj25KuzynL5Kt4 .error-icon{fill:#552222;}#mermaid-svg-rVEj25KuzynL5Kt4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rVEj25KuzynL5Kt4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rVEj25KuzynL5Kt4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rVEj25KuzynL5Kt4 .marker.cross{stroke:#333333;}#mermaid-svg-rVEj25KuzynL5Kt4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rVEj25KuzynL5Kt4 p{margin:0;}#mermaid-svg-rVEj25KuzynL5Kt4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster-label text{fill:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster-label span{color:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster-label span p{background-color:transparent;}#mermaid-svg-rVEj25KuzynL5Kt4 .label text,#mermaid-svg-rVEj25KuzynL5Kt4 span{fill:#333;color:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 .node rect,#mermaid-svg-rVEj25KuzynL5Kt4 .node circle,#mermaid-svg-rVEj25KuzynL5Kt4 .node ellipse,#mermaid-svg-rVEj25KuzynL5Kt4 .node polygon,#mermaid-svg-rVEj25KuzynL5Kt4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rVEj25KuzynL5Kt4 .rough-node .label text,#mermaid-svg-rVEj25KuzynL5Kt4 .node .label text,#mermaid-svg-rVEj25KuzynL5Kt4 .image-shape .label,#mermaid-svg-rVEj25KuzynL5Kt4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-rVEj25KuzynL5Kt4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rVEj25KuzynL5Kt4 .rough-node .label,#mermaid-svg-rVEj25KuzynL5Kt4 .node .label,#mermaid-svg-rVEj25KuzynL5Kt4 .image-shape .label,#mermaid-svg-rVEj25KuzynL5Kt4 .icon-shape .label{text-align:center;}#mermaid-svg-rVEj25KuzynL5Kt4 .node.clickable{cursor:pointer;}#mermaid-svg-rVEj25KuzynL5Kt4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rVEj25KuzynL5Kt4 .arrowheadPath{fill:#333333;}#mermaid-svg-rVEj25KuzynL5Kt4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rVEj25KuzynL5Kt4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rVEj25KuzynL5Kt4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rVEj25KuzynL5Kt4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rVEj25KuzynL5Kt4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rVEj25KuzynL5Kt4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster text{fill:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 .cluster span{color:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rVEj25KuzynL5Kt4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rVEj25KuzynL5Kt4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-rVEj25KuzynL5Kt4 .icon-shape,#mermaid-svg-rVEj25KuzynL5Kt4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rVEj25KuzynL5Kt4 .icon-shape p,#mermaid-svg-rVEj25KuzynL5Kt4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rVEj25KuzynL5Kt4 .icon-shape .label rect,#mermaid-svg-rVEj25KuzynL5Kt4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rVEj25KuzynL5Kt4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rVEj25KuzynL5Kt4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rVEj25KuzynL5Kt4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Spring 容器
@Autowired B b
ref
ref
export() → Invoker → NettyServer :20880
export() → Invoker → NettyServer :20880
Bean: c
类型: com.example.service.impl.C
Bean: d
类型: com.example.service.impl.D
Bean: ServiceBean:com.example.service.A
类型: ServiceBean
ref → c
Bean: ServiceBean:com.example.service.B
类型: ServiceBean
ref → d
外部消费者
@Autowired 走的是 C → D 这条实线路径(纯 Spring IoC),而 ServiceBean 到外部消费者的路径(虚线)是 Dubbo RPC 的职责。两条路径在同一套 Bean 之上叠加,但互不干扰。
3.4 自定义扫描器的设计
DubboClassPathBeanDefinitionScanner 的真实源码:
java
package com.alibaba.dubbo.config.spring.context.annotation;
public class DubboClassPathBeanDefinitionScanner
extends ClassPathBeanDefinitionScanner {
// 四参数构造器:开放 useDefaultFilters 控制
public DubboClassPathBeanDefinitionScanner(
BeanDefinitionRegistry registry,
boolean useDefaultFilters,
Environment environment,
ResourceLoader resourceLoader) {
super(registry, useDefaultFilters);
setEnvironment(environment);
setResourceLoader(resourceLoader);
// 确保 Spring 内部注解处理器已注册
// (AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor 等)
registerAnnotationConfigProcessors(registry);
}
// 三参数构造器:useDefaultFilters = false(禁用 Spring 默认过滤器)
public DubboClassPathBeanDefinitionScanner(
BeanDefinitionRegistry registry,
Environment environment,
ResourceLoader resourceLoader) {
this(registry, false, environment, resourceLoader);
}
// 将 protected 方法暴露为 public,供外部调用
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
return super.doScan(basePackages);
}
@Override
public boolean checkCandidate(String beanName, BeanDefinition beanDefinition)
throws IllegalStateException {
return super.checkCandidate(beanName, beanDefinition);
}
}
设计要点分析:
① useDefaultFilters = false
Spring 的 ClassPathBeanDefinitionScanner 默认使用 useDefaultFilters = true,会自动包含 @Component、@Repository、@Service、@Controller 等 Spring 标准原型注解。Dubbo 将其关闭,然后在 registerServiceBeans() 中手动添加仅匹配 com.alibaba.dubbo.config.annotation.Service 的过滤器。
这样做的好处:
- 精确控制 :只有 Dubbo 的
@Service类才会被纳入 Dubbo 管理,避免误扫纯 Spring Bean - 解耦:不依赖 Spring 的注解体系,为未来支持其他 IoC 框架留有余地
② registerAnnotationConfigProcessors(registry)
这个调用确保了 Spring 的内部基础设施 Bean(最关键的:AutowiredAnnotationBeanPostProcessor)被注册。这意味着:即使开发者在 XML 中只配置了 <dubbo:annotation> 而忘记了 <context:annotation-config/> 或 <context:component-scan>,@Autowired 依然可以工作------Dubbo 做了兜底。
注意 :这个"兜底"不完整。
registerAnnotationConfigProcessors不会注册ConfigurationClassPostProcessor,因此@Configuration+@Bean等 Java Config 方式不会生效。实践中仍建议显式配置<context:component-scan>。
③ 方法的可见性提升
doScan() 和 checkCandidate() 在父类中是 protected,Dubbo 的重写将它们暴露为 public。这使得 ServiceAnnotationBeanPostProcessor 可以在扫描完成后调用 checkCandidate() 来检查 ServiceBean 的 BeanDefinition 是否与已有 Bean 冲突。
3.5 Bean 名称的生成逻辑
resolveBeanNameGenerator() 方法决定扫描到的类在 Spring 容器中的 Bean 名称:
java
private BeanNameGenerator resolveBeanNameGenerator(BeanDefinitionRegistry registry) {
BeanNameGenerator beanNameGenerator = null;
// 优先尝试从容器中获取已注册的 BeanNameGenerator
// (由 ConfigurationClassPostProcessor 处理 @ComponentScan 时注册)
if (registry instanceof SingletonBeanRegistry) {
SingletonBeanRegistry sbr = (SingletonBeanRegistry) registry;
beanNameGenerator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
}
// 降级:新建 AnnotationBeanNameGenerator
if (beanNameGenerator == null) {
beanNameGenerator = new AnnotationBeanNameGenerator();
}
return beanNameGenerator;
}
实际行为:
- 若存在
context:component-scan(Demo 中的情况),Spring 已注册了AnnotationBeanNameGenerator的单例,Dubbo 会复用它------保证命名规则一致 - 若不存在,Dubbo 自行创建
AnnotationBeanNameGenerator------行为与 Spring 默认一致
AnnotationBeanNameGenerator 的默认行为(来自 Spring Framework 源码):
- 若注解有
value属性(如@Service("myName")),使用该值 - 否则调用
Introspector.decapitalize(shortClassName),将类名首字母小写
因此 :类 C → Bean 名 c,类 D → Bean 名 d(与 @Component 扫描的行为完全一致)。
常见误解纠正 :有观点认为 Dubbo 的 Scanner "不会调用
BeanNameGenerator.buildDefaultBeanName,而是直接使用类名"。从源码可知此说法不成立------annotationBeanNameGenerator.generateBeanName()内部调用的正是buildDefaultBeanName(),产出的名称首字母是小写的。
3.6 小结:Bean 注册全链路

四、路径二:@Autowired 如何完成注入
这一部分完全在 Spring Framework 层面运作,Dubbo 不参与也不感知。但由于 Dubbo 的 Scanner 已经把 C 和 D 注册进了 Spring 容器,@Autowired 的注入流程自然就能找到它们。
4.1 注入链路的源码级还原

4.2 为什么 context:component-scan 仍然是必需的
不少开发者会犯一个配置错误:认为 dubbo:annotation 已经能扫描到 @Service 类并注册为 Bean,就不再需要 context:component-scan。
Demo 项目的实际配置是两者并存:
xml
<context:component-scan base-package="com.example.service"/> <!-- 不要删除 -->
<dubbo:annotation package="com.example.service.impl"/>
这看似配置了两次扫描,但它们各司其职:
| 配置 | 扫描的注解 | 作用 |
|---|---|---|
context:component-scan |
Spring 原生注解(@Component、@Service、@Repository、@Controller) |
激活 AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor 等 Spring 基础设施;也扫描普通 Spring Bean |
dubbo:annotation |
com.alibaba.dubbo.config.annotation.Service |
将 Dubbo 的 @Service 类注册为 Spring Bean + 导出 Dubbo 服务 |
虽然如 3.4 节所述,Dubbo 的 Scanner 构造函数中调用了 registerAnnotationConfigProcessors() 作为兜底,但出于以下原因,不建议 省略 context:component-scan:
registerAnnotationConfigProcessors()不注册ConfigurationClassPostProcessor,导致@Configuration、@Bean、@ComponentScan等 Java Config 特性不可用- 语义清晰:两个注解体系本就各管各的,分别声明体现意图
- Dubbo 官方文档推荐的用法即为两者并存
五、路径三:Dubbo 服务的并行导出(对 @Autowired 无影响但值得了解)
当 ServiceBean(如 ServiceBean:com.example.service.A)完成属性注入后,Spring 调用其 afterPropertiesSet(),触发 Dubbo 服务导出链:

关键点 :ref 字段是 ServiceBean 的一个属性,它指向了真实的业务 Bean(即 c)。Dubbo 在收到远程调用时,通过 ref 拿到 Bean 实例,再通过反射调用目标方法。这个 ref 和 C 中的 @Autowired private B b 是两回事------前者是 Dubbo 的引用,后者是 Spring 的依赖注入。
六、关键问题的深度解答
6.1 b.methodB() 到底走的是什么路径?
java
// C.methodA() 中
b.methodB();
从字节码层面看:
// 编译结果
aload_0
getfield #b : Lcom/example/service/B;
invokevirtual com/example/service/B.methodB()V
运行时 b 指向的实际对象是 D 的实例(new D()),JVM 通过虚方法分派找到 D.methodB() 并执行。全程没有代理、没有反射、没有网络 I/O。这是标准的 Java 多态调用,与以下场景没有任何区别:
java
B b = new D();
b.methodB(); // 普通的多态调用
Dubbo 的 @Service 注解在此过程中完全没有参与。
6.2 为什么 @Autowired 注入的是接口类型但能工作?
这是 Spring IoC 的基本能力:按类型自动装配时,Spring 查找所有与声明类型兼容的 Bean。
@Autowired
private B b; // 声明类型: B
// Spring 查找: 谁 implements B? → D
// 结果: 注入 D 的实例
这与接口本身是否标注 @Service 无关------接口 B 甚至不需要任何注解。
6.3 @Reference 和 @Autowired 在同一 JVM 中的行为差异
当 C 和 D 在同一 JVM 中时,两种注入方式都可以工作:
@Autowired private B b |
@Reference private B b |
|
|---|---|---|
| 底层机制 | Spring IoC 容器直接注入 D 实例 | Dubbo 创建 JDK 动态代理,调用走 InjvmProtocol |
| 调用路径 | b.methodB() → D.methodB()(直接) |
b.methodB() → InvokerInvocationHandler.invoke() → InjvmProtocol → D.methodB() |
| 性能 | 无开销 | 有少量代理开销(~几十纳秒) |
| 是否依赖注册中心 | 否 | 默认走 injvm,也不经过注册中心 |
| 拆分服务时 | 需要修改代码(改用 @Reference) |
仅需调整配置 |
Dubbo 的同进程优化 :当 Consumer 检测到 Provider 在同一 JVM 时,默认使用 InjvmProtocol(本地协议),不走网络。但这仍然经过 Dubbo 的 Filter 链、Mock 等机制,与 @Autowired 的直接调用有本质区别。
java
// @Reference 注入时,实际对象的结构
b → Proxy($Proxy123)
└─ InvokerInvocationHandler
└─ InjvmExporter → D 实例
6.4 循环依赖
java
@Service
class C implements A {
@Autowired B b;
}
@Service
class D implements B {
@Autowired A a;
}
Spring 对单例 Bean 的字段注入循环依赖,会通过三级缓存机制解决:
- 创建
c→ 提前曝光(三级缓存singletonFactories)→ 注入b - 创建
d→ 需要a→ 从三级缓存中获取c的早期引用 → 注入 →d创建完成 c拿到完成的d,注入完成
如果是构造器注入 ,三级缓存无法介入,会抛出 BeanCurrentlyInCreationException,此时需要 @Lazy。
七、Dubbo版本演进
@Service 注解让类同时成为"Spring Bean"和"Dubbo 服务"的方式,在 2.6.x 内部发生过重要变更。
| 版本 | Bean 注册方式 | @Service 是否组合 Spring @Service |
|---|---|---|
| 2.5.7 ~ 2.6.4 | @Service 通过元注解 @org.springframework.stereotype.Service 让 Spring 的 @ComponentScan 自动发现 |
✅ 是(继承 Spring 注解) |
| 2.6.5 ~ 2.6.12 | 引入 ServiceAnnotationBeanPostProcessor + DubboClassPathBeanDefinitionScanner 主动扫描 |
❌ 否(完全解耦) |
| 2.7.0+ | 沿用 2.6.5 的设计,包名迁至 org.apache.dubbo |
❌ 否 |
重构动机(基于 Dubbo 2.6.5 Release Notes 及代码演进):
- 解耦 Spring :2.6.5 之前
@Service通过@Component元注解与 Spring 强绑定,导致无法移植到其他 IoC 容器 - 避免包名歧义 :项目同时存在
org.springframework.stereotype.Service和com.alibaba.dubbo.config.annotation.Service,IDE 自动导入时容易选错 - 精细化控制 :自定义 Scanner 可以加入更多逻辑(如条件过滤、配置合并),而不受 Spring 的
@ComponentScan限制 - ServiceBean 的统一注册 :2.6.5 后的机制在扫描时就注册
ServiceBean,不再依赖后续的InitializingBean回调来"发现"接口类型
验证方式 :对比 2.6.4 和 2.6.8 的
@Service注解定义:
bash# 2.6.4: @Service 上有 @org.springframework.stereotype.Service 元注解 javap -verbose com/alibaba/dubbo/config/annotation/Service.class | grep "springframework" # 2.6.8: 无上述元注解 javap -verbose com/alibaba/dubbo/config/annotation/Service.class | grep "springframework" # (无输出)
八、实战建议
8.1 配置清单
xml
<!-- 必选:激活 Spring 注解处理 -->
<context:component-scan base-package="com.example"/>
<!-- 必选:激活 Dubbo @Service 扫描 & @Reference 注入 -->
<dubbo:annotation package="com.example.service.impl"/>
<!-- 必选:Dubbo 基础配置 -->
<dubbo:application name="your-app"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="20880"/>
8.2 注解选择决策树
同一个模块内,C 需要调用 B 的方法
│
├─ C 和 B 的实现 D 都在当前应用?
│ │
│ └─ 是 → 用 @Autowired
│ · 无代理开销
│ · 代码简洁
│ · 但将来拆服务需要改代码
│
└─ B 的实现在其他应用?
│
└─ 是 → 用 @Reference
· 走 Dubbo RPC(网络调用)
· 有负载均衡、容错等治理能力
8.3 常见陷阱
① 两个包扫描路径不一致导致 Bean 找不到
xml
<!-- 错误:Dubbo 扫描 impl 包,但 C 和 D 在 service 包 -->
<dubbo:annotation package="com.example.service.impl"/>
<!-- 但类在 com.example.service 下 -->
<!-- 正确:扫描所有包含 @Service 注解类的包 -->
<dubbo:annotation package="com.example.service"/>
② 跨服务误用 @Autowired
java
// 错误:B 的实现在其他服务,本地容器中没有 B 的实现
@Autowired
private B b; // → NoSuchBeanDefinitionException
// 正确
@Reference
private B b;
③ Dubbo 版本升级引起包名变化
java
// 2.6.x → 2.7.x/3.x 迁移
// 旧: com.alibaba.dubbo.config.annotation.Service
// 新: org.apache.dubbo.config.annotation.Service
④ 多实现时 @Autowired 的歧义
如果同一个接口有多个实现类(如 D1 implements B 和 D2 implements B),且都在 Dubbo 扫描路径内:
java
@Autowired
private B b; // → NoUniqueBeanDefinitionException: expected single but found 2
解决方案:
java
@Autowired
@Qualifier("d1") // 按 Bean 名称限定
private B b;
九、总结
回到最初的问题:C 中通过 @Autowired 注入 B,能否正常调用 D 的方法?
答案:能。
但要真正理解"为什么能",需要把握以下三个层次:
| 层次 | 机制 | 关键角色 |
|---|---|---|
| ① Bean 注册 | Dubbo 2.6.8 通过 ServiceAnnotationBeanPostProcessor + DubboClassPathBeanDefinitionScanner 主动扫描 @Service 类并注册到 Spring 容器,不依赖 Spring 的 @Component 元注解 |
AnnotationBeanDefinitionParser(XML 解析)→ ServiceAnnotationBeanPostProcessor(BPP)→ DubboClassPathBeanDefinitionScanner(自定义扫描) |
| ② 依赖注入 | Spring 的 AutowiredAnnotationBeanPostProcessor 按类型匹配,寻找 B 的实现类 → 找到 D → 反射注入 |
Spring Framework 标准 DI 流程,Dubbo 全程不参与 |
| ③ 方法调用 | b.methodB() 是 JVM 内部的虚方法调用,目标对象就是 D 的实例,无代理、无 RPC |
Java 多态机制 |
一个实用的记忆模型:
Dubbo 的
@Service= 注册 Spring Bean(以便被@Autowired) + 注册 ServiceBean(以便被dubbo://调用)。前者是给 Spring 看的,后者是给 Dubbo 看的。@Autowired只管前者。
参考资料
- Dubbo 2.6.8 源码 ------ 本文核心源码引用来源
- Spring Framework 4.3.x Reference ------
@Autowired注入流程 - Dubbo 注解配置官方文档
- Dubbo 2.6.5 Release Notes ------
@Service解耦 Spring 元注解的版本
感 谢 各 位 大 佬 的 阅 读,随 手 点 赞,日 薪 过 万~! !!