共计 2105 个字符,预计需要花费 6 分钟才能阅读完成。
一、前言
在使用 Spring 时,你是否遇到过如下错误?
Requested bean is currently in creation: Is there an unresolvable circular reference?
这说明你踩到了 循环依赖 的坑。但你可能不知道的是,Spring 为了解决这个问题,内置了一套精妙的机制 —— 三级缓存机制。
本文将深入源码、图示原理、揭示 Spring 是如何解决构造器注入无法处理、但 setter 注入能正常工作的循环依赖问题。
二、什么是循环依赖?
1. 定义
循环依赖是指在依赖注入过程中,两个或多个 Bean 相互引用,形成闭环。
例如:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
当 Spring 实例化 A 时,需要注入 B,而 B 又需要注入 A,此时就出现了“鸡生蛋,蛋生鸡”的困境。
Spring 能解决哪些循环依赖?
注入方式 | 是否支持循环依赖 |
---|---|
构造器注入 | ❌ 不支持 |
Setter 注入 | ✅ 支持 |
@Autowired 字段注入 | ✅ 支持 |
三、Spring 是怎么解决循环依赖的?
1. 三级缓存结构
Spring 利用 三级缓存机制 解决循环依赖问题。它们存在于 DefaultSingletonBeanRegistry
中:
/** 一级缓存:成品 Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
/** 二级缓存:早期暴露的 Bean(不含依赖注入和增强) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
/** 三级缓存:暴露 ObjectFactory,用于创建早期 Bean 引用 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
流程图:

四、源码分析:如何一步步解决循环依赖
源码位置:AbstractAutowireCapableBeanFactory#doCreateBean
1.创建 Bean 实例(仅构造方法)
instanceWrapper = createBeanInstance(beanName, mbd, args);
这时的 Bean 是裸实例,尚未依赖注入。
2.添加到三级缓存
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
注册一个 ObjectFactory
,供后续在 B 中注入 A 时使用。
3.属性填充(依赖注入)
populateBean(beanName, mbd, instanceWrapper);
此时如果 B 中需要 A,Spring 调用 getBean("A")
,先查缓存:
- 一级缓存查不到
- 二级缓存查不到
- 查三级缓存 → 调用 ObjectFactory 创建早期引用(原始 A)
于是 B 顺利拿到 A 的早期引用,完成注入。
4.初始化 Bean(初始化方法、增强处理)
此时 B 中的 A 是一个未完成初始化的早期 Bean(可能未增强、未执行 init 方法),不过完成了循环。
五、三层缓存对比总结
缓存层级 | 类型 | 作用说明 |
---|---|---|
singletonObjects | 一级缓存 | 存放完全初始化完成的 Bean(成品) |
earlySingletonObjects | 二级缓存 | 存放早期暴露的 Bean 引用(成品前) |
singletonFactories | 三级缓存 | 存放生成早期 Bean 的工厂(ObjectFactory) |
缓存流程图:
singletonFactories (三级缓存)
↓ 创建早期引用
earlySingletonObjects (二级缓存)
↓ 完成初始化后
singletonObjects (一级缓存)
六、构造器注入为何不支持循环依赖?
因为构造器注入在构造方法中就需要依赖注入完成,而 Spring 只有在构造方法执行后才能暴露 ObjectFactory,因此无法提前注入。
执行构造器时 Bean 尚未被暴露到三级缓存,因此会抛出循环依赖异常。
另外注意:Spring Boot 2.6+ 默认禁用循环依赖
如需开启,则需要加上配置
spring.main.allow-circular-references=true
七、关注后续专栏
本文是《Spring 源码解析系列》专栏的第 4 篇,后续将发布:
- 《Spring 中的事件监听机制(ApplicationEvent)源码与实战》
附录:核心源码入口
AbstractBeanFactory#getBean
DefaultSingletonBeanRegistry#getSingleton
AbstractAutowireCapableBeanFactory#doCreateBean
addSingletonFactory()
populateBean()
如果本文对你有帮助,欢迎点赞、评论、收藏!我是 李卷卷,专注Java相关知识输出。感谢阅读!