0源码基础学习Spring源码系列(二)——Spring如何解决循环依赖
字数 1610 2025-08-11 17:40:26

Spring循环依赖解决机制详解

一、循环依赖概述

循环依赖是指两个或多个Bean相互依赖,形成闭环的情况。例如:

  • A依赖B
  • B依赖C
  • C依赖A

如果不解决循环依赖问题,会导致无限递归调用,最终导致OOM错误。

1.1 Spring支持的循环依赖类型

Spring只能解决以下情况的循环依赖:

  • 单例Bean:必须是单例模式的Bean
  • 属性或setter注入:通过属性注入或setter方法注入的依赖
  • 非构造器注入:不能是通过构造器注入的依赖

二、三级缓存机制

Spring通过三级缓存解决循环依赖问题:

2.1 三级缓存定义

// 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放提前曝光的原始Bean对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放Bean工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2.2 三级缓存的作用

  1. singletonObjects(一级缓存)

    • 存放完全初始化好的Bean
    • 从该缓存中取出的Bean可以直接使用
  2. earlySingletonObjects(二级缓存)

    • 存放提前曝光的单例对象
    • 存放原始的Bean对象(尚未填充属性)
  3. singletonFactories(三级缓存)

    • 存放单例对象工厂
    • 用于解决循环依赖

三、解决循环依赖的流程

以A依赖B,B依赖A为例:

  1. 尝试从缓存中加载A,发现A不存在
  2. 实例化A(半成品)
  3. 将实例化完成的A放入第三级缓存中
  4. 装配属性B
  5. 尝试从缓存中加载B,发现B不存在
  6. 实例化B
  7. 将实例化完成的B放入第三级缓存中
  8. 装配属性A
  9. 尝试从缓存中加载A,发现A存在于三级缓存中
    • 将A从三级缓存中移除
    • 放入二级缓存中
    • 将A赋值给B
  10. B装配属性完成
  11. 初始化B,并将B从三级缓存中移除,放入一级缓存
  12. 返回第4步,此时A的属性也装配完成
  13. 初始化A,并将A放入一级缓存

四、代理对象与循环依赖

4.1 代理对象的问题

当存在代理对象时,简单的两级缓存无法解决问题:

  • 代理对象是在源对象初始化完成后才创建的
  • 如果B依赖的是A的代理对象,而A还在创建过程中,直接返回原始A对象会导致问题

4.2 Spring的解决方案

Spring通过ObjectFactorygetEarlyBeanReference方法解决代理对象问题:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    // 向三级缓存中添加ObjectFactory
    this.singletonFactories.put(beanName, singletonFactory);
    // ...
}

当其他Bean需要依赖当前Bean时,会调用ObjectFactorygetObject()方法,最终调用:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    // 如果需要代理,则返回代理对象
    return wrapIfNecessary(bean, beanName, null);
}

4.3 二级缓存的作用

二级缓存的主要作用是避免多次创建代理对象

  • 每次调用getObject()方法创建代理对象时,都会产生新的代理对象
  • 二级缓存保存第一次创建的代理对象,后续直接从二级缓存获取
  • 保证单例模式下代理对象的唯一性

五、关键代码分析

5.1 获取单例Bean的核心方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存中获取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 从二级缓存中获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 双重检查
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 3. 从三级缓存中获取ObjectFactory
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 调用getObject()方法获取Bean
                            singletonObject = singletonFactory.getObject();
                            // 放入二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从三级缓存中移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

5.2 提前曝光Bean工厂

boolean earlySingletonExposure = (mbd.isSingleton() && 
                                this.allowCircularReferences &&
                                isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

六、常见问题解答

6.1 为什么需要三级缓存而不是两级?

  • 普通Bean:两级缓存确实可以解决问题
  • 代理Bean:需要三级缓存来延迟代理对象的创建
    • 三级缓存保存的是ObjectFactory,可以按需创建代理对象
    • 二级缓存保存第一次创建的代理对象,保证唯一性

6.2 代理对象何时被初始化?

  • 代理对象内部持有目标对象的引用
  • 当原始对象初始化完成后,代理对象也相当于完成了初始化
  • 方法调用会委托给原始对象执行

6.3 什么时候返回代理对象?

在Bean初始化完成后,会检查是否需要返回代理对象:

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        // ...
    }
}
  • 如果Bean没有被后置处理器修改,则返回二级缓存中的代理对象
  • 否则返回原始对象(可能会抛出异常)

七、总结

Spring通过三级缓存机制优雅地解决了循环依赖问题:

  1. 三级缓存分工明确

    • 一级缓存:完整Bean
    • 二级缓存:原始Bean或代理Bean
    • 三级缓存:Bean工厂
  2. 代理对象处理

    • 通过ObjectFactory延迟创建代理对象
    • 保证代理对象的单例性
  3. 设计思想

    • 优先保证普通Bean的创建流程
    • 特殊处理循环依赖和代理对象的情况
    • 通过缓存机制提高性能

理解Spring的循环依赖解决机制,有助于我们更好地设计Bean之间的依赖关系,避免潜在的循环依赖问题。

Spring循环依赖解决机制详解 一、循环依赖概述 循环依赖是指两个或多个Bean相互依赖,形成闭环的情况。例如: A依赖B B依赖C C依赖A 如果不解决循环依赖问题,会导致无限递归调用,最终导致OOM错误。 1.1 Spring支持的循环依赖类型 Spring只能解决以下情况的循环依赖: 单例Bean :必须是单例模式的Bean 属性或setter注入 :通过属性注入或setter方法注入的依赖 非构造器注入 :不能是通过构造器注入的依赖 二、三级缓存机制 Spring通过三级缓存解决循环依赖问题: 2.1 三级缓存定义 2.2 三级缓存的作用 singletonObjects(一级缓存) 存放完全初始化好的Bean 从该缓存中取出的Bean可以直接使用 earlySingletonObjects(二级缓存) 存放提前曝光的单例对象 存放原始的Bean对象(尚未填充属性) singletonFactories(三级缓存) 存放单例对象工厂 用于解决循环依赖 三、解决循环依赖的流程 以A依赖B,B依赖A为例: 尝试从缓存中加载A,发现A不存在 实例化A(半成品) 将实例化完成的A放入第三级缓存中 装配属性B 尝试从缓存中加载B,发现B不存在 实例化B 将实例化完成的B放入第三级缓存中 装配属性A 尝试从缓存中加载A,发现A存在于三级缓存中 将A从三级缓存中移除 放入二级缓存中 将A赋值给B B装配属性完成 初始化B,并将B从三级缓存中移除,放入一级缓存 返回第4步,此时A的属性也装配完成 初始化A,并将A放入一级缓存 四、代理对象与循环依赖 4.1 代理对象的问题 当存在代理对象时,简单的两级缓存无法解决问题: 代理对象是在源对象初始化完成后才创建的 如果B依赖的是A的代理对象,而A还在创建过程中,直接返回原始A对象会导致问题 4.2 Spring的解决方案 Spring通过 ObjectFactory 和 getEarlyBeanReference 方法解决代理对象问题: 当其他Bean需要依赖当前Bean时,会调用 ObjectFactory 的 getObject() 方法,最终调用: 4.3 二级缓存的作用 二级缓存的主要作用是 避免多次创建代理对象 : 每次调用 getObject() 方法创建代理对象时,都会产生新的代理对象 二级缓存保存第一次创建的代理对象,后续直接从二级缓存获取 保证单例模式下代理对象的唯一性 五、关键代码分析 5.1 获取单例Bean的核心方法 5.2 提前曝光Bean工厂 六、常见问题解答 6.1 为什么需要三级缓存而不是两级? 普通Bean :两级缓存确实可以解决问题 代理Bean :需要三级缓存来延迟代理对象的创建 三级缓存保存的是 ObjectFactory ,可以按需创建代理对象 二级缓存保存第一次创建的代理对象,保证唯一性 6.2 代理对象何时被初始化? 代理对象内部持有目标对象的引用 当原始对象初始化完成后,代理对象也相当于完成了初始化 方法调用会委托给原始对象执行 6.3 什么时候返回代理对象? 在Bean初始化完成后,会检查是否需要返回代理对象: 如果Bean没有被后置处理器修改,则返回二级缓存中的代理对象 否则返回原始对象(可能会抛出异常) 七、总结 Spring通过三级缓存机制优雅地解决了循环依赖问题: 三级缓存分工明确 : 一级缓存:完整Bean 二级缓存:原始Bean或代理Bean 三级缓存:Bean工厂 代理对象处理 : 通过 ObjectFactory 延迟创建代理对象 保证代理对象的单例性 设计思想 : 优先保证普通Bean的创建流程 特殊处理循环依赖和代理对象的情况 通过缓存机制提高性能 理解Spring的循环依赖解决机制,有助于我们更好地设计Bean之间的依赖关系,避免潜在的循环依赖问题。