Spring 循环依赖与三级缓存:我终于敢说把这事儿讲透了

"Spring 是怎么解决循环依赖的?三级缓存到底是干嘛的?能不能只用两级?"

今天用大白话 + 真实执行顺序 + 代码级拆解,彻底把这事儿掰开揉碎讲清楚。读完你不仅能答面试,还能知道什么时候循环依赖会失败、什么时候可以干掉三级缓存。

一、先说结论

问题 答案
Spring 能解决循环依赖吗? 能!但仅限 setter 注入 + 单例 + 非 AOP 动态代理的情况
必须三级缓存吗? 不必须!两级缓存就够了,第三级是为了"提前曝光动态代理对象"
能不能关三级缓存? 可以,加 spring.main.allow-circular-references=true 后可降级为二级

二、循环依赖到底长啥样?

java 复制代码
@Component
class A {
    @Autowired
    private B b;
}

@Component
class B {
    @Autowired
    private A a;
}

A 要 B,B 要 A,互相等着对方先创建完 → 死锁?

三、三级缓存到底是啥?

java 复制代码
// Spring 容器内部维护了三个 Map
private final Map<String, Object> singletonObjects       = new HashMap<>();      // 一级缓存:成品单例
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(); // 三级缓存:对象工厂(最关键!)
private final Map<String, Object> earlySingletonObjects  = new HashMap<>();      // 二级缓存:提前暴露的半成品

四、完整创建流程时间线(A → B → A)

步骤 正在创建 操作 放进了哪个缓存
1 A doGetBean("a") → 开始创建 A
2 A createBean → 实例化(new A())
3 A 把"能造A的工厂"扔进三级缓存(提前曝光) → singletonFactories
4 A 正在给 A 注入属性 → 发现需要 B → 暂停 A,去创建 B
5 B 同理实例化 B → 把"能造B的工厂"扔进三级缓存 → singletonFactories
6 B 给 B 注入属性 → 发现需要 A → 去容器里找 A
7 B 先查一级缓存 → 没有 查二级缓存 → 没有 查三级缓存 → 拿到 A 的工厂!
8 B 执行 factory.getObject() → 真正触发 A 的动态代理(如果需要) → earlySingletonObjects(二级)
9 B B 拿到"半成品 A"注入成功 → B 创建完成 → 放入一级缓存 → singletonObjects
10 A 回到 A 的注入 → 拿到已经完成的 B → A 也创建完成 → 放入一级缓存 → singletonObjects

关键点:B 在创建过程中通过三级缓存拿到了"还在创建中的 A"的早期引用,从而打破循环。

五、三级缓存到底哪级最重要?

答案是:第三级 singletonFactories

它存的是 ObjectFactory<?>,作用是"允许在对象完全初始化前就暴露一个可以生成代理对象的工厂"。

为什么需要这一层?因为如果 A 需要被 AOP 代理(@Async、@Transactional 等),代理对象必须在属性注入阶段就得准备好。如果只暴露原始对象,注入给 B 的是原生 A,后面再代理就晚了,B 拿到的永远不是代理对象。

补充:第一级缓存就是填充完整的A,第二级缓存就是未填充B的A;(比较好理解不过多解释)

关键源码:

java 复制代码
// AbstractAutowireCapableBeanFactory.java (Spring 源码)

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // 1. 创建 A 的原始对象(new A())
    Object beanInstance = createBeanInstance(beanName, mbd, args).getWrappedInstance();

    // 2. 判断是否需要提前暴露(单例 + 允许循环依赖 + 正在创建)
    boolean earlySingletonExposure = (mbd.isSingleton() && 
                                     this.allowCircularReferences && 
                                     isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        // ✅ 3. 把一个 Lambda 表达式 放入三级缓存!
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
    }

    // 4. 继续注入属性(这时会去创建 B)
    populateBean(beanName, mbd, beanInstance);
    // ...
}

addSingletonFactory做了什么?

java 复制代码
// DefaultSingletonBeanRegistry.java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // ✅ 三级缓存:Map<String, ObjectFactory<?>>
            this.singletonFactories.put(beanName, singletonFactory); // ← Lambda 被存进去了!
        }
    }
}

这段Lambda怎么被拿出来的,做了什么?

java 复制代码
// DefaultSingletonBeanRegistry.java

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 一级缓存没找到(A 还没初始化完)
    Object singletonObject = this.singletonObjects.get(beanName);
    
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2. 二级缓存也没找到
            singletonObject = this.earlySingletonObjects.get(beanName);
            
            if (singletonObject == null && allowEarlyReference) {
                // ✅ 3. 从三级缓存取出 Lambda!
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // ✅ 4. 调用 getObject() → 执行 Lambda!
                    singletonObject = singletonFactory.getObject(); // ← 生成早期引用

                    // ✅ 5. 放进二级缓存(升级!)
                    this.earlySingletonObjects.put(beanName, singletonObject);

                    // ✅ 6. 移除三级缓存(Lambda 只用一次)
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

getObject()的时候,Lambda被执行。

java 复制代码
// 就是之前放进去的 Lambda:
() -> getEarlyBeanReference("a", mbd, rawA)

// getEarlyBeanReference 会判断是否需要 AOP 代理
// 如果没有 AOP,直接返回 rawA(原始 A 对象)
// 如果有 @Transactional,就返回代理对象

六、哪些情况循环依赖会失败?

场景 是否能解决 原因
setter 注入 + 单例 三级缓存完美解决
构造器注入 不能 实例化阶段就要对方,缓存里根本没有
@Async / @Transactional 代理 三级缓存专门为动态代理设计
prototype 原型作用域 不能 每次都新创建,没法提前暴露
@Lazy 懒加载 实际注入的是代理对象
多例 + @Autowired 不能 每次 getBean 都新创建,死循环

七、能不能只用二级缓存?

yaml 复制代码
# application.yml
spring:
  main:
    allow-circular-references: true   # 开启后,Spring 内部会降级用二级缓存

实测结果:普通 setter 循环依赖依然能解决,但一旦涉及 AOP 代理就会报错 BeanCurrentlyInCreationException

一句话总结:第三级缓存就是为了代理而生的,就是提前把产生代理对象的逻辑放在第三级缓存,如果需要代理就提前代理放进二级缓存。

相关推荐
0和1的舞者21 分钟前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
Wang153024 分钟前
Java多线程死锁排查
java·计算机网络
嘟嘟MD33 分钟前
程序员副业 | 2025年12月复盘
后端·创业
小小星球之旅1 小时前
CompletableFuture学习
java·开发语言·学习
利刃大大1 小时前
【SpringBoot】Spring事务 && @Transactional详解 && Spring事务失效问题
spring boot·spring·事务
jiayong231 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
皮皮林5512 小时前
告别 OOM:EasyExcel 百万数据导出最佳实践(附开箱即用增强工具类)
java
..过云雨2 小时前
17-2.【Linux系统编程】线程同步详解 - 条件变量的理解及应用
linux·c++·人工智能·后端
Da Da 泓2 小时前
多线程(七)【线程池】
java·开发语言·线程池·多线程
To Be Clean Coder2 小时前
【Spring源码】getBean源码实战(三)
java·mysql·spring