Spring 框架中的循环依赖是指多个 Bean 之间形成依赖闭环的情况,例如 Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。Spring 通过三级缓存机制解决单例模式下属性注入的循环依赖问题,但无法处理构造器注入和非单例场景(如原型作用域)。以下是详细解析:
一、循环依赖的类型与限制
-
构造器循环依赖
// 构造器相互依赖示例 @Component public class A { private B b; public A(B b) { this.b = b; } // 构造器依赖 B } @Component public class B { private A a; public B(A a) { this.a = a; } // 构造器依赖 A }
- 结果:Spring 容器启动时会直接抛出
BeanCurrentlyInCreationException
异常,因为构造器需要在对象实例化前完成,无法通过提前暴露对象引用解决。
- 结果:Spring 容器启动时会直接抛出
-
属性/Setter 方法循环依赖
使用@Autowired
或 XML 配置的 Setter 方法注入时,Spring 可通过三级缓存解决单例 Bean 的循环依赖。 -
非单例 Bean 的循环依赖
原型(Prototype)作用域的 Bean 无法解决循环依赖,因为每次请求都会创建新实例,无法通过缓存机制处理。
二、三级缓存机制的原理
Spring 通过以下三级缓存解决单例属性循环依赖:
缓存层级 | 名称 | 描述 |
---|---|---|
一级缓存 | singletonObjects | 存储完全初始化后的单例 Bean |
二级缓存 | earlySingletonObjects | 存储早期曝光的 Bean(未完成属性注入) |
三级缓存 | singletonFactories | 存储 Bean 工厂,用于生成早期对象代理(含 AOP 处理逻辑) |
解决流程(以 A←→B 循环依赖为例):
1. 实例化 A → 生成原始对象 A,并将 A 的 ObjectFactory 存入三级缓存
2. 注入 A 的属性时发现依赖 B → 触发 B 的创建
3. 实例化 B → 生成原始对象 B,并将 B 的 ObjectFactory 存入三级缓存
4. 注入 B 的属性时发现依赖 A → 从三级缓存获取 A 的早期引用(可能经过 AOP 代理)
5. B 完成属性注入和初始化 → 存入一级缓存
6. A 继续注入 B → 完成 A 的初始化 → 存入一级缓存
- 关键点:三级缓存(
singletonFactories
)通过 ObjectFactory 延迟生成早期引用,确保即使 Bean 被代理(如事务管理),也能正确注入。
三、流程图解
四、注意事项与限制
- 仅支持单例模式:原型 Bean 的循环依赖无法解决。
- AOP 代理的影响:若循环依赖的 Bean 需要动态代理(如事务管理),三级缓存中的 ObjectFactory 会提前生成代理对象。
- Spring Boot 2.6+ 的默认限制:
默认关闭循环依赖支持,需通过配置spring.main.allow-circular-references=true
开启。
五、源码核心逻辑
// AbstractAutowireCapableBeanFactory 中的 doCreateBean 方法
protected Object doCreateBean(...) {
// 1. 实例化 Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 将 Bean 的 ObjectFactory 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 3. 属性注入(可能触发依赖 Bean 的创建)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化(执行 @PostConstruct 等方法)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
- 关键方法:
addSingletonFactory
将对象工厂加入三级缓存,getEarlyBeanReference
处理 AOP 代理。
六、常见误区澄清
- 误区 1:只有 Setter 注入能解决循环依赖。
事实:字段注入(@Autowired
)同样适用,关键在于注入方式是否为属性注入。 - 误区 2:三级缓存是为了提高性能。
事实:三级缓存的核心作用是处理 AOP 代理对象的循环依赖,而非性能优化。