欢迎光临
我们一直在努力

1.3 三级缓存的终极奥义:Spring 如何用 3 个 Map 解决循环依赖?

三级缓存的终极奥义:Spring 如何用 3 个 Map 解决循环依赖?

🎯 本文定位:Spring IoC 容器核心第三篇,从源码角度彻底搞懂为什么是"三级"而不是"两级",源码基于v6.2.0

一、面试题引入

高频面试问题:

  • Spring 是如何解决循环依赖的?
  • 为什么要用三级缓存?两级不行吗?
  • 构造器注入的循环依赖为什么解决不了?
  • 如果 A 依赖 B,B 依赖 A,创建流程是怎样的?

为什么这道题能区分候选人水平:

大部分人知道"三级缓存"这个词,但说不清楚每一级缓存存的是什么、为什么第三级要存 ObjectFactory 而不是直接存对象、AOP 代理如何与三级缓存配合。这些才是面试官真正想考察的。


二、功能与用法(知其然)

2.1 什么是循环依赖

@Component
public class A {
@Autowired
private B b; // A 依赖 B
}

@Component
public class B {
@Autowired
private A a; // B 依赖 A
}

如果没有特殊处理:

  • 创建 A → 发现需要 B → 去创建 B
  • 创建 B → 发现需要 A → 去创建 A
  • 创建 A → 发现需要 B → …(死循环)
  • 2.2 Spring 支持的循环依赖场景

    场景是否支持原因
    单例 + Setter/字段注入 ✅ 支持 可以先创建对象,后填充属性
    单例 + 构造器注入 ❌ 不支持 构造器执行时对象尚未创建完成
    原型 Bean ❌ 不支持 原型 Bean 不走缓存
    @Async + 循环依赖 ⚠️ 需特殊处理 @Async 代理可能破坏依赖关系

    2.3 如何检测循环依赖问题

    当出现不支持的循环依赖时,Spring 会抛出异常:

    BeanCurrentlyInCreationException: Error creating bean with name 'a':
    Requested bean is currently in creation: Is there an unresolvable circular reference?

    2.4 解决构造器循环依赖的方法

    方法一:使用 @Lazy 延迟加载

    @Component
    public class A {
    private final B b;

    public A(@Lazy B b) { // 🔥 注入的是 B 的代理,首次使用时才真正创建
    this.b = b;
    }
    }

    方法二:使用 Setter 注入替代构造器注入

    @Component
    public class A {
    private B b;

    @Autowired
    public void setB(B b) {
    this.b = b;
    }
    }

    方法三:使用 ObjectProvider 延迟获取

    @Component
    public class A {
    private final ObjectProvider<B> bProvider;

    public A(ObjectProvider<B> bProvider) {
    this.bProvider = bProvider;
    }

    public void doSomething() {
    B b = bProvider.getObject(); // 使用时才获取
    }
    }


    三、源码实现拆解(知其所以然)

    3.1 三级缓存的定义

    源码路径:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    /**
    * 一级缓存:存放完全初始化好的 Bean
    * Cache of singleton objects: bean name to bean instance
    */

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /**
    * 三级缓存:存放 Bean 工厂对象,用于生成早期引用
    * Creation-time registry of singleton factories: bean name to ObjectFactory
    */

    private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);

    /**
    * 二级缓存:存放早期暴露的 Bean(尚未填充属性)
    * Cache of early singleton objects: bean name to bean instance
    */

    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    /**
    * 正在创建中的 Bean 名称集合
    * Names of beans that are currently in creation
    */

    private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet(16);
    }

    3.2 三级缓存的核心流程

    ┌──────────────────────────────────────────────────────────────────────────────┐
    │ 三级缓存工作流程 │
    ├──────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ 创建 Bean A │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 1. 将 A 标记为"正在创建" │ │
    │ │ singletonsCurrentlyInCreation.add("a") │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 2. 实例化 A(调用构造器) │ │
    │ │ 此时 A 是"半成品" │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 3. 将 A 的 ObjectFactory 放入三级缓存 │ ← 关键步骤 │
    │ │ singletonFactories.put("a", () -> …) │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 4. 填充属性,发现需要 B │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 5. 去获取 B,发现 B 不存在,开始创建 B │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 6. 创建 B:实例化 → 放入三级缓存 → 填充属性 │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 7. B 填充属性时发现需要 A,去获取 A │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 8. getSingleton("a"): │ ← 核心逻辑 │
    │ │ 一级缓存没有 │ │
    │ │ → 检查 A 正在创建中 │ │
    │ │ → 二级缓存没有 │ │
    │ │ → 从三级缓存获取 ObjectFactory │ │
    │ │ → 调用 getObject() 获得 A 的早期引用 │ │
    │ │ → 放入二级缓存,删除三级缓存 │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 9. B 获得 A 的早期引用,完成属性填充 │ │
    │ │ B 完成初始化,放入一级缓存 │ │
    │ └─────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │ 10. 回到 A,获得 B,完成属性填充 │ │
    │ │ A 完成初始化,放入一级缓存 │ │
    │ └─────────────────────────────────────────────┘ │
    │ │
    └──────────────────────────────────────────────────────────────────────────────┘

    3.3 getSingleton() 方法源码

    // DefaultSingletonBeanRegistry.java

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 🔥 第一步:从一级缓存获取(完整 Bean)
    Object singletonObject = this.singletonObjects.get(beanName);

    // 如果一级缓存没有,且该 Bean 正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 🔥 第二步:从二级缓存获取(早期引用)
    singletonObject = this.earlySingletonObjects.get(beanName);

    if (singletonObject == null && allowEarlyReference) {
    // 需要加锁保证并发安全
    if (!this.singletonLock.tryLock()) {
    return null;
    }
    try {
    // Double Check
    singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null) {
    // 🔥 第三步:从三级缓存获取 ObjectFactory
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
    // 🔥 调用 ObjectFactory.getObject() 获取早期引用
    singletonObject = singletonFactory.getObject();

    // 升级到二级缓存,删除三级缓存
    if (this.singletonFactories.remove(beanName) != null) {
    this.earlySingletonObjects.put(beanName, singletonObject);
    }
    else {
    singletonObject = this.singletonObjects.get(beanName);
    }
    }
    }
    }
    }
    finally {
    this.singletonLock.unlock();
    }
    }
    }
    return singletonObject;
    }

    3.4 三级缓存中的 ObjectFactory 是什么?

    在 doCreateBean() 中放入三级缓存:

    // AbstractAutowireCapableBeanFactory.java

    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {

    // … 实例化 Bean …

    // 判断是否需要提前暴露
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
    // 🔥 放入三级缓存的是一个 Lambda 表达式
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // … 属性填充、初始化 …
    }

    getEarlyBeanReference() 方法:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    // 🔥 这里可能返回 AOP 代理对象
    exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
    }
    }
    return exposedObject;
    }

    3.5 为什么是三级缓存?两级不行吗?

    假设只有两级缓存:

    // 假设:实例化后直接放入二级缓存
    Map<String, Object> singletonObjects; // 一级:完整 Bean
    Map<String, Object> earlySingletonObjects; // 二级:早期引用

    问题来了:当 A 被 AOP 增强时:

    1. 创建 A,实例化后放入二级缓存(此时是原始对象)
    2. A 填充属性,需要 B
    3. 创建 B,B 需要 A,从二级缓存拿到 A 的原始对象
    4. B 完成创建
    5. A 完成初始化,在 postProcessAfterInitialization 生成代理
    6. ❌ 问题:B 持有的是 A 的原始对象,不是代理对象!

    三级缓存的解决方案:

    1. 创建 A,实例化后放入三级缓存(存的是 ObjectFactory)
    2. A 填充属性,需要 B
    3. 创建 B,B 需要 A
    4. 从三级缓存获取 A 的 ObjectFactory,调用 getEarlyBeanReference()
    🔥 这时会提前生成 A 的代理对象
    5. 将代理对象放入二级缓存,B 拿到的是代理
    6. B 完成创建
    7. A 完成初始化,检查二级缓存,发现已有代理,直接使用

    3.6 AOP 与三级缓存的配合

    AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor:

    // AbstractAutoProxyCreator.java

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 🔥 记录已经提前创建过代理
    this.earlyProxyReferences.put(cacheKey, bean);
    // 🔥 返回代理对象
    return wrapIfNecessary(bean, beanName, cacheKey);
    }

    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 🔥 如果已经通过 getEarlyBeanReference 创建过代理,就不再创建
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    return wrapIfNecessary(bean, beanName, cacheKey);
    }
    }
    return bean;
    }

    核心设计:通过 earlyProxyReferences 这个 Map 避免重复创建代理。


    四、设计动机分析(知其所以必然)

    4.1 为什么不在实例化后直接创建代理?

    如果直接创建代理:

    // ❌ 假设这样设计
    Object bean = createBeanInstance();
    Object proxy = createProxy(bean); // 实例化后立即创建代理
    earlySingletonObjects.put(beanName, proxy);

    问题:

  • 性能浪费:没有循环依赖时,提前创建代理是不必要的
  • 违反设计原则:AOP 代理应该在 Bean 初始化完成后创建
  • 破坏生命周期:代理创建时机被打乱
  • 4.2 ObjectFactory 的妙处

    ObjectFactory 是惰性求值的:

    // 放入三级缓存时,只是保存了 Lambda,没有执行
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

    只有当真正发生循环依赖时,才会调用 getObject() 创建早期引用。

    4.3 为什么要有二级缓存?

    二级缓存的作用是避免重复调用 ObjectFactory:

    假设 A 被 B、C、D 三个 Bean 依赖:

    没有二级缓存:
    – B 获取 A → 调用 ObjectFactory → 创建代理 1
    – C 获取 A → 调用 ObjectFactory → 创建代理 2
    – D 获取 A → 调用 ObjectFactory → 创建代理 3
    ❌ 问题:创建了三个不同的代理对象!

    有二级缓存:
    – B 获取 A → 调用 ObjectFactory → 创建代理 → 放入二级缓存
    – C 获取 A → 从二级缓存获取 → 同一个代理
    – D 获取 A → 从二级缓存获取 → 同一个代理
    ✅ 始终是同一个对象

    4.4 Spring 6.x 的变化

    在 Spring 6.2.0 中,三级缓存使用了 ConcurrentHashMap:

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    并增加了 singletonLock 来保护关键操作:

    private final Lock singletonLock = new ReentrantLock();

    这相比老版本的 synchronized 同步块,提供了更细粒度的并发控制。


    五、局限性与扩展

    5.1 无法解决的循环依赖

    场景原因
    构造器注入 构造器执行时对象还未创建,无法放入缓存
    原型 Bean 原型 Bean 不使用缓存
    @Async Bean 的循环依赖 @Async 代理在 postProcessAfterInitialization 创建,可能导致不一致

    5.2 @Async 循环依赖的特殊处理

    @Component
    public class A {
    @Autowired
    private B b;

    @Async
    public void asyncMethod() { }
    }

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

    可能报错:

    Bean with name 'a' has been injected into other beans [b] in its raw version
    as part of a circular reference, but has eventually been wrapped.

    解决方案:

    // 方式一:使用 @Lazy
    @Autowired
    @Lazy
    private A a;

    // 方式二:设置 allowRawInjectionDespiteWrapping
    @Bean
    public static BeanFactoryPostProcessor allowRawInjection() {
    return beanFactory -> {
    ((DefaultListableBeanFactory) beanFactory)
    .setAllowRawInjectionDespiteWrapping(true);
    };
    }

    5.3 如何关闭循环依赖支持

    @SpringBootApplication
    public class Application {
    public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Application.class);
    app.setAllowCircularReferences(false); // 关闭循环依赖
    app.run(args);
    }
    }

    或在配置文件中:

    spring:
    main:
    allow-circular-references: false


    六、面试回答分层

    P6 回答(功能用法层)

    “Spring 通过三级缓存解决单例 Bean 的 Setter/字段注入循环依赖。一级缓存存完整 Bean,二级缓存存早期暴露的半成品 Bean,三级缓存存 ObjectFactory。当 A 依赖 B、B 依赖 A 时,A 创建过程中会先把自己放入三级缓存,创建 B 时 B 能从缓存获取 A 的早期引用,从而打破循环。构造器注入的循环依赖无法解决,可以用 @Lazy 延迟加载解决。”

    P7 回答(实现原理层)

    “三级缓存的核心是 DefaultSingletonBeanRegistry 中的三个 Map。关键方法是 getSingleton():先查一级缓存,没有且 Bean 正在创建中则查二级缓存,还没有就从三级缓存获取 ObjectFactory 并调用 getObject() 获取早期引用。三级缓存存的是 Lambda 表达式 () -> getEarlyBeanReference(),这个方法会调用 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(),如果有 AOP 增强,AbstractAutoProxyCreator 会在这里提前创建代理对象。”

    P8 回答(设计思想层)

    “三级缓存的设计体现了惰性求值思想:正常情况下 AOP 代理在 postProcessAfterInitialization 创建,只有发生循环依赖时才通过 getEarlyBeanReference() 提前创建。ObjectFactory 的存在让框架可以延迟决策是否需要创建代理。二级缓存的意义是缓存复用,保证多个 Bean 依赖同一个正在创建的 Bean 时,拿到的是同一个早期引用。这种设计在不影响正常流程的前提下,优雅地处理了边界情况。Spring 6.x 改用 ConcurrentHashMap + ReentrantLock 替代 synchronized,体现了对并发场景的优化。”


    七、面试必杀一句话

    三级缓存的精髓:一级存成品、二级存半成品、三级存制造半成品的工厂。ObjectFactory 的惰性求值让 AOP 代理可以"按需提前创建",二级缓存保证多次获取返回同一对象。没有三级缓存,发生循环依赖时 B 拿到的会是 A 的原始对象而非代理,这是两级缓存无法解决的根本问题。


    八、关键源码速查表

    类/方法源码路径作用
    singletonObjects DefaultSingletonBeanRegistry 一级缓存
    earlySingletonObjects DefaultSingletonBeanRegistry 二级缓存
    singletonFactories DefaultSingletonBeanRegistry 三级缓存
    getSingleton(name, allowEarlyReference) DefaultSingletonBeanRegistry 三级缓存查找逻辑
    addSingletonFactory() DefaultSingletonBeanRegistry 放入三级缓存
    getEarlyBeanReference() AbstractAutowireCapableBeanFactory 获取早期引用
    AbstractAutoProxyCreator spring-aop AOP 代理创建
    singletonsCurrentlyInCreation DefaultSingletonBeanRegistry 正在创建的 Bean

    下一篇预告:《BeanDefinition:Spring 的"设计图纸"是如何画出来的?》—— 深入剖析 Spring 如何将 XML/注解转化为 BeanDefinition

    赞(0)
    未经允许不得转载:171主机测评 » 1.3 三级缓存的终极奥义:Spring 如何用 3 个 Map 解决循环依赖?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址