第三篇・Spring 源码深度拆解:循环依赖 + 三级缓存
👋 前言
上一篇我们完整走通了 Bean 生命周期:doGetBean → createBean → doCreateBean → 实例化 → 填充 → 初始化
这一篇,我们聚焦 Spring 面试最难、最高频、最易踩坑的考点 ——循环依赖。
-
循环依赖到底是什么? -
三级缓存每一级存什么、为什么需要三级? -
为什么 Spring 只能解决「setter/field 注入」的循环依赖? -
构造器注入为什么解决不了?@Lazy 如何破解? -
从源码一步步看 Spring 是如何 “破解” 循环依赖的?
一、先搞懂:什么是循环依赖?
最常见的场景
// 类 A 依赖 B@Servicepublic classAService{@Autowiredprivate BService bService;}// 类 B 依赖 A@Servicepublic classBService{@Autowiredprivate AService aService;}
-
A 创建时,需要注入 B → B 还没创建,去创建 B -
B 创建时,需要注入 A → A 还没创建,去创建 A -
陷入死循环:A ← B ← A ← B…
Spring 如何打破这个死循环?答案就是 三级缓存。
二、核心前提:Spring 只解决「单例 + setter/field 注入」的循环依赖
✅ 能解决:单例 Bean + setter 注入(@Autowired)
✅ 特殊场景能解决:构造器注入 + @Lazy 注解(通过代理延迟实例化,打断循环)
❌ 不能解决:
-
多例 Bean(@Scope (“prototype”)) -
无 @Lazy 注解的构造器注入(@Autowired 写在构造方法上) -
原型 Bean 与单例 Bean 交叉循环依赖
原因后面源码里会逐行说清。
三、三级缓存核心定义
|
缓存级别 |
缓存名称 |
存储内容 |
核心作用 |
|
一级缓存 |
singletonObjects |
成品 Bean(完全初始化完成,可直接使用) |
供外部获取,避免重复创建,保证单例唯一性 |
|
二级缓存 |
earlySingletonObjects |
早期暴露的 Bean 引用(实例化完成、未填充属性、未初始化) |
保证循环依赖过程中,注入的是同一个早期实例,避免重复执行三级缓存的工厂逻辑 |
|
三级缓存 |
singletonFactories |
Bean 工厂(lambda 表达式,用于生成早期引用) |
延迟生成代理对象,确保无论是否有循环依赖,Bean 最终只有一个代理实例(或原始实例) |
// 一级缓存:成品 Bean(key:beanName,value:成品Bean)private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:早期暴露的 Bean 引用(未初始化)private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存:Bean 工厂(用于生成早期引用)private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 正在创建中的 Bean 集合(标记哪些 Bean 正在创建,避免重复创建)private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
四、循环依赖破解全流程(源码级追踪,A ↔ B 举例)
我们以 AService 和 BService 循环依赖为例,逐行追踪源码,看三级缓存如何工作。
前提
-
A、B 都是默认单例、非懒加载、setter 注入(@Autowired) -
容器启动时,先创建 A(按 BeanName 字典序,可自行调试验证)
步骤 1:创建 A → 实例化 A(未填充属性)
-
容器执行 preInstantiateSingletons(),遍历 BeanName,先执行getBean("aService") -
进入 doGetBean(),从一级缓存singletonObjects拿 A → 无(null) -
标记 A 为「正在创建」,加入 singletonsCurrentlyInCreation -
进入 createBean()→doCreateBean() -
执行 createBeanInstance():通过构造器实例化 A(此时 A 只是个空对象,bService 为 null)
步骤 2:A 暴露早期引用到三级缓存
实例化 A 后,Spring 会主动暴露 A 的早期引用(未填充属性),存入三级缓存:
// doCreateBean() 内部代码(AbstractAutowireCapableBeanFactory)// 暴露早期引用:将 A 的工厂放入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
进入 addSingletonFactory(DefaultSingletonBeanRegistry)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 放入三级缓存this.singletonFactories.put(beanName, singletonFactory);// 清空二级缓存(避免冲突)this.earlySingletonObjects.remove(beanName);// 标记为已注册this.registeredSingletons.add(beanName);}}}
此时:三级缓存有 A 的工厂,一级、二级缓存无 A。
步骤 3:A 填充属性 → 需要注入 B
实例化 A 后,进入 populateBean(),开始填充 @Autowired 的 BService:
-
Spring 发现 A 需要 B → 执行 getBean("bService"),去创建 B -
进入 B 的 doGetBean(),从一级缓存拿 B → 无(null) -
标记 B 为「正在创建」,加入 singletonsCurrentlyInCreation -
进入 B 的 doCreateBean(),执行createBeanInstance(),实例化 B(B 的 aService 为 null) -
B 实例化后,同样执行 addSingletonFactory(),将 B 的工厂放入三级缓存 -
B 进入 populateBean(),开始填充@Autowired的 AService → 执行getBean("aService")
步骤 4:B 获取 A → 从三级缓存拿到 A 的早期引用
B 执行 getBean("aService") 后,流程如下:
-
从一级缓存 singletonObjects拿 A → 无 -
从二级缓存 earlySingletonObjects拿 A → 无 -
从三级缓存 singletonFactories拿 A 的工厂 → 有! -
执行工厂的 lambda 表达式( getEarlyBeanReference()),生成 A 的早期引用(未填充属性) -
将 A 的早期引用从三级缓存移到二级缓存(三级缓存移除 A,二级缓存添加 A) -
将 A 的早期引用注入到 B 中 → B 的 aService 不再为 null
步骤 5:B 完成初始化 → 成为成品 Bean,放入一级缓存
-
B 注入 A 后,继续执行 initializeBean()(Aware、前后置处理器、AOP 代理) -
B 初始化完成,执行 addSingleton(),将 B 放入一级缓存:
// DefaultSingletonBeanRegistryprotectedvoidaddSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// 放入一级缓存(成品 Bean)this.singletonObjects.put(beanName, singletonObject);// 清空三级、二级缓存this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
步骤 6:A 完成初始化 → 成为成品 Bean,放入一级缓存
-
A 注入 B 后,继续执行 initializeBean(),完成初始化(生成 AOP 代理等) -
A 初始化完成,执行 addSingleton(),将 A 放入一级缓存,清空二级缓存中的 A -
标记 A 为「创建完成」,从 singletonsCurrentlyInCreation中移除
步骤 7:循环依赖破解完成
-
一级缓存:A(成品)、B(成品) -
二级、三级缓存:A、B 均已清空 -
后续获取 A 或 B,直接从一级缓存拿,无循环依赖
五、关键源码拆解(断点能追到的核心方法)
1. getEarlyBeanReference(生成早期引用)
// AbstractAutowireCapableBeanFactoryprotected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;// 执行 BeanPostProcessor 后置处理(如果有 AOP 代理,这里会生成代理)if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
作用:
-
生成 Bean 的早期引用(未初始化,但可能已生成 AOP 代理) -
保证循环依赖时,注入的是同一个 Bean 引用(避免重复生成代理)
2. getSingleton(核心缓存获取逻辑)
// DefaultSingletonBeanRegistryprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 先从一级缓存拿成品 BeanObject singletonObject = this.singletonObjects.get(beanName);// 2. 一级缓存没有,且 Bean 正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 3. 从二级缓存拿早期引用singletonObject = this.earlySingletonObjects.get(beanName);// 4. 二级缓存没有,且允许早期引用(循环依赖核心开关)if (singletonObject == null && allowEarlyReference) {// 5. 从三级缓存拿工厂,生成早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 6. 移到二级缓存,清空三级缓存(避免重复生成)this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;}
关键细节:
allowEarlyReference:是否允许获取早期引用(循环依赖的核心开关,默认 true)-
流程:一级 → 二级 → 三级,层层获取,避免死循环
六、面试高频问题
1. 为什么需要三级缓存?不能用二级缓存吗?
答:为了 保证 AOP 代理的唯一性。
-
三级缓存存的是「Bean 工厂」,不是直接的 Bean 引用 -
只有当发生循环依赖时,才会执行工厂生成早期引用(可能包含 AOP 代理) -
如果只用二级缓存,提前生成代理,会导致 Bean 初始化完成后,出现「两个代理对象」(破坏单例) -
三级缓存的延迟生成机制,确保无论是否有循环依赖,Bean 最终只有一个代理对象
2. 为什么 Spring 只能解决 setter 注入的循环依赖?
答:核心是「注入时机不同」:
-
setter 注入:实例化后、初始化前 注入(先实例化 A,再注入 B) -
构造器注入:实例化时 就需要注入依赖(创建 A 的构造器时,就需要 B,此时 B 还没实例化,无法暴露早期引用) -
循环依赖破解的前提是「先实例化,再暴露早期引用」,构造器注入无法满足这个前提,所以解决不了
3. 多例 Bean 为什么不能解决循环依赖?
答:多例 Bean 每次 getBean() 都会创建新对象,没有缓存(三级缓存只针对单例):
-
A 创建时,需要 B → 新建 B -
B 创建时,需要 A → 新建 A -
新建的 A 又需要新建 B,陷入无限循环,无法通过缓存打破
4. 二级缓存的作用是什么?
答:核心作用是 保证循环依赖过程中,注入的是同一个早期实例,性能优化是附带效果:
-
当多个 Bean 依赖同一个早期实例时(如 A ↔ B、A ↔ C),B 第一次获取 A 时从三级缓存生成早期引用并移入二级缓存,C 再获取 A 时直接从二级缓存拿,避免重复执行三级缓存的工厂逻辑(尤其是避免重复生成 AOP 代理) -
若没有二级缓存,每次获取早期引用都要执行工厂逻辑,不仅低效,还可能导致实例不一致
A 创建 → 实例化 A → 暴露 A 工厂到三级缓存 → 填充属性需要 B↓B 创建 → 实例化 B → 暴露 B 工厂到三级缓存 → 填充属性需要 A↓B 获取 A → 三级缓存取 A 工厂 → 生成 A 早期引用 → 移到二级缓存 → 注入 B↓B 填充完成 → 初始化(AOP 代理)→ 成为成品 → 放入一级缓存 → 返回给 A 注入↓A 填充完成 → 初始化(若已生成代理则跳过)→ 成为成品 → 放入一级缓存 → 循环依赖破解
下篇预告
第四篇我们进入 Spring 另一个核心考点:AOP 源码全流程拆解
-
AOP 核心原理(动态代理) -
切面是如何植入到 Bean 中的? @Aspect、@Pointcut 源码解析
-
AOP 与 Bean 生命周期的关联(为什么后置处理器是关键)
夜雨聆风