深入分析spring如何解决循环依赖的

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


Spring 解决循环依赖的核心机制:三级缓存

Spring 通过 三级缓存提前暴露未完成初始化的 Bean 的机制解决循环依赖问题,具体流程如下:

1. 循环依赖的典型场景

假设存在两个 Bean 的相互依赖:

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

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

在初始化时,Spring 会尝试创建 A → 发现 A 依赖 B → 创建 B → 发现 B 依赖 A → 此时 A 尚未初始化完成,形成循环依赖。


2. 三级缓存的结构

Spring 通过三个缓存区管理 Bean 的创建状态:

缓存名称 存储内容 作用
singletonObjects 完全初始化完成的单例 Bean 最终对外暴露的 Bean 实例
earlySingletonObjects 提前暴露的未完成初始化的 Bean(半成品) 解决循环依赖时,避免重复创建代理对象
singletonFactories Bean 的工厂对象(ObjectFactory 生成早期 Bean 的引用(可能生成代理对象,如 AOP 场景)

3. 循环依赖解决流程(以 A → B → A 为例)

步骤 1:创建 Bean A

  1. 实例化 A
    调用 A 的构造函数,生成一个未设置属性的原始对象(此时 a.b = null)。

  2. 将 A 的工厂对象存入三级缓存
    Spring 将 A 的工厂对象(用于生成早期引用)存入 singletonFactories

    java 复制代码
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

步骤 2:填充 A 的属性(依赖 B)

  1. 发现 A 依赖 B
    Spring 尝试从容器中获取 B,但 B 尚未创建,触发 B 的创建流程。

步骤 3:创建 Bean B

  1. 实例化 B
    调用 B 的构造函数,生成原始对象(此时 b.a = null)。
  2. 将 B 的工厂对象存入三级缓存
    类似 A 的流程,将 B 的工厂对象存入 singletonFactories

步骤 4:填充 B 的属性(依赖 A)

  1. 发现 B 依赖 A

    Spring 尝试获取 A:

    • singletonObjects 查找 → 不存在。
    • earlySingletonObjects 查找 → 不存在。
    • singletonFactories 获取 A 的工厂对象 → 生成 A 的早期引用(可能被 AOP 代理)。
    • 将 A 的早期引用存入 earlySingletonObjects,并从 singletonFactories 移除。
  2. 将 A 的早期引用注入到 B

    此时 B 的 a 属性指向 A 的早期引用(未完成初始化的对象)。

步骤 5:完成 B 的初始化

  1. 执行 B 的初始化方法 (如 @PostConstructInitializingBean)。
  2. 将 B 放入 singletonObjects ,并从 earlySingletonObjectssingletonFactories 中移除。

步骤 6:完成 A 的初始化

  1. 将 B 的完整实例注入到 A
  2. 执行 A 的初始化方法
  3. 将 A 放入 singletonObjects ,并从 earlySingletonObjects 移除。

4. 关键实现细节

  • 提前暴露对象

    在 Bean 实例化后、属性填充前,将工厂对象存入三级缓存,允许其他 Bean 获取其早期引用。

  • 处理代理对象

    如果 Bean 需要被代理(如 AOP),getEarlyBeanReference() 会通过 SmartInstantiationAwareBeanPostProcessor(如 AbstractAutoProxyCreator)生成代理对象,确保最终注入的是同一个代理实例。

  • 循环依赖的检测

    在创建 Bean 时,Spring 会将当前正在创建的 Bean 名称存储在 singletonsCurrentlyInCreation 集合中。若在依赖查找时发现该 Bean 已在创建中,则触发循环依赖处理逻辑。


5. 无法解决的循环依赖场景

  • 构造器注入的循环依赖

    若两个 Bean 均通过构造器注入对方,则无法解决:

    java 复制代码
    public class A {
        public A(B b) { /* ... */ }
    }
    public class B {
        public B(A a) { /* ... */ }
    }

    原因:构造器注入需在实例化阶段完成,但此时 Bean 尚未放入三级缓存,无法提前暴露引用。

  • 原型作用域(Prototype)的循环依赖

    Spring 不会缓存原型 Bean,因此无法通过三级缓存解决循环依赖。


6. 源码核心逻辑

  • DefaultSingletonBeanRegistry
    管理三级缓存的核心代码:

    java 复制代码
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

7. 最佳实践与设计建议

  1. 避免循环依赖

    循环依赖通常是设计缺陷的标志,应通过重构代码(如引入中间层、使用事件驱动)消除。

  2. 优先使用 Setter 注入

    若必须使用循环依赖,Setter 注入比字段注入更安全(显式管理依赖关系)。

  3. 监控与告警

    通过 Spring 的 BeanCurrentlyInCreationException 日志发现潜在循环依赖问题。


总结

机制 实现要点
三级缓存 通过 singletonFactories 提前暴露未完成初始化的 Bean,打破循环依赖链。
代理处理 结合 BeanPostProcessor 确保 AOP 代理对象的唯一性。
作用域限制 仅支持单例 Bean 的循环依赖,构造器注入和原型 Bean 无法解决。
设计意义 循环依赖应作为临时解决方案,长期需通过代码优化消除。
相关推荐
一路向北North1 小时前
IDEA加载项目时依赖无法更新
java·ide·intellij-idea
振鹏Dong2 小时前
超大规模数据场景(思路)——面试高频算法题目
算法·面试
uhakadotcom2 小时前
Python 与 ClickHouse Connect 集成:基础知识和实践
算法·面试·github
uhakadotcom2 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
小萌新上大分2 小时前
SpringCloudGateWay
java·开发语言·后端·springcloud·springgateway·cloudalibaba·gateway网关
uhakadotcom2 小时前
使用Python获取Google Trends数据:2025年详细指南
后端·面试·github
uhakadotcom2 小时前
使用 Python 与 Google Cloud Bigtable 进行交互
后端·面试·github
uhakadotcom2 小时前
使用 Python 与 BigQuery 进行交互:基础知识与实践
算法·面试
uhakadotcom2 小时前
使用 Hadoop MapReduce 和 Bigtable 进行单词统计
算法·面试·github
直视太阳3 小时前
springboot+easyexcel实现下载excels模板下拉选择
java·spring boot·后端