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

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

相关推荐
coderCatIce2 小时前
Spring AOP 核心知识笔记
后端
better_liang2 小时前
每日Java面试场景题知识点之-单例模式
java·单例模式·设计模式·面试·企业级开发
海上彼尚2 小时前
Go之路 - 5.go的流程控制
开发语言·后端·golang
okseekw2 小时前
递归:不止是 “自己调用自己”,看完这篇秒懂
java·后端
温宇飞2 小时前
Drizzle ORM:类型安全的数据库开发
后端
琢磨先生David2 小时前
Java算法题:移除数组中的重复项
java·数据结构·算法
SEO-狼术2 小时前
ASP.NET Zero v15.0.0 adds full .NET
后端·asp.net·.net
艾斯Felix2 小时前
SearXNG使用之引擎连接超时,响应成功但是空数据
后端
Java天梯之路2 小时前
手撸 Spring 简易版 AOP
java·spring boot·面试