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

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

相关推荐
㳺三才人子3 小时前
初探 Flask
后端·python·flask·html
星栈独行3 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下3 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.4 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易4 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0074 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶4 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl5 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬5 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar
云烟成雨TD6 小时前
Spring AI Alibaba 1.x 系列【62】时光旅行(Time-Travel)
java·人工智能·spring