乐于分享
好东西不私藏

第三篇・Spring 源码深度拆解:循环依赖 + 三级缓存

第三篇・Spring 源码深度拆解:循环依赖 + 三级缓存

👋 前言

上一篇我们完整走通了 Bean 生命周期:doGetBean → createBean → doCreateBean → 实例化 → 填充 → 初始化

这一篇,我们聚焦 Spring 面试最难、最高频、最易踩坑的考点 ——循环依赖。

  • 循环依赖到底是什么?
  • 三级缓存每一级存什么、为什么需要三级?
  • 为什么 Spring 只能解决「setter/field 注入」的循环依赖?
  • 构造器注入为什么解决不了?@Lazy 如何破解?
  • 从源码一步步看 Spring 是如何 “破解” 循环依赖的?

一、先搞懂:什么是循环依赖?

最常见的场景

// 类 A 依赖 B@Servicepublic classAService{    @Autowired    private BService bService;}// 类 B 依赖 A@Servicepublic classBService{    @Autowired    private 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 交叉循环依赖

原因后面源码里会逐行说清。

三、三级缓存核心定义

三级缓存全部定义在 DefaultSingletonBeanRegistry 类中,是 Spring 解决循环依赖的核心容器,按优先级从高到低排列:

缓存级别

 缓存名称  

 存储内容  

核心作用

一级缓存

 singletonObjects  

 成品 Bean(完全初始化完成,可直接使用)  

供外部获取,避免重复创建,保证单例唯一性

二级缓存

 earlySingletonObjects  

 早期暴露的 Bean 引用(实例化完成、未填充属性、未初始化)  

保证循环依赖过程中,注入的是同一个早期实例,避免重复执行三级缓存的工厂逻辑

三级缓存

 singletonFactories  

 Bean 工厂(lambda 表达式,用于生成早期引用)  

延迟生成代理对象,确保无论是否有循环依赖,Bean 最终只有一个代理实例(或原始实例)

源码佐证(DefaultSingletonBeanRegistry)
// 一级缓存:成品 Bean(key:beanName,value:成品Bean)private final Map<StringObject> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:早期暴露的 Bean 引用(未初始化)private final Map<StringObject> earlySingletonObjects = new HashMap<>(16);// 三级缓存:Bean 工厂(用于生成早期引用)private final Map<StringObjectFactory<?>> 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(未填充属性)

  1. 容器执行 preInstantiateSingletons(),遍历 BeanName,先执行 getBean("aService")
  2. 进入 doGetBean(),从一级缓存 singletonObjects 拿 A → 无(null)
  3. 标记 A 为「正在创建」,加入 singletonsCurrentlyInCreation
  4. 进入 createBean() → doCreateBean()
  5. 执行 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:

  1. Spring 发现 A 需要 B → 执行 getBean("bService"),去创建 B
  2. 进入 B 的 doGetBean(),从一级缓存拿 B → 无(null)
  3. 标记 B 为「正在创建」,加入 singletonsCurrentlyInCreation
  4. 进入 B 的 doCreateBean(),执行 createBeanInstance(),实例化 B(B 的 aService 为 null)
  5. B 实例化后,同样执行 addSingletonFactory(),将 B 的工厂放入三级缓存
  6. B 进入 populateBean(),开始填充 @Autowired 的 AService → 执行 getBean("aService")

步骤 4:B 获取 A → 从三级缓存拿到 A 的早期引用

B 执行 getBean("aService") 后,流程如下:

  1. 从一级缓存 singletonObjects 拿 A → 无
  2. 从二级缓存 earlySingletonObjects 拿 A → 无
  3. 从三级缓存 singletonFactories 拿 A 的工厂 → 有!
  4. 执行工厂的 lambda 表达式(getEarlyBeanReference()),生成 A 的早期引用(未填充属性)
  5. 将 A 的早期引用从三级缓存移到二级缓存(三级缓存移除 A,二级缓存添加 A)
  6. 将 A 的早期引用注入到 B 中 → B 的 aService 不再为 null

步骤 5:B 完成初始化 → 成为成品 Bean,放入一级缓存

  1. B 注入 A 后,继续执行 initializeBean()(Aware、前后置处理器、AOP 代理)
  2. 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);    }}
3. B 创建完成,返回给 A,注入到 A 的 bService 中 → A 的 bService 不再为 null

步骤 6:A 完成初始化 → 成为成品 Bean,放入一级缓存

  1. A 注入 B 后,继续执行 initializeBean(),完成初始化(生成 AOP 代理等)
  2. A 初始化完成,执行 addSingleton(),将 A 放入一级缓存,清空二级缓存中的 A
  3. 标记 A 为「创建完成」,从 singletonsCurrentlyInCreation 中移除

步骤 7:循环依赖破解完成

  1. 一级缓存:A(成品)、B(成品)
  2. 二级、三级缓存:A、B 均已清空
  3. 后续获取 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. 先从一级缓存拿成品 Bean    Object 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 生命周期的关联(为什么后置处理器是关键)