深入分析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 无法解决。
设计意义 循环依赖应作为临时解决方案,长期需通过代码优化消除。
相关推荐
丘山子1 分钟前
Python 布尔运算的优雅实践
后端·python·面试
XMYX-01 分钟前
Java HTTPS 请求失败排查与证书导入全过程
java·https
北_鱼2 分钟前
设计模式1:创建型模式
java·设计模式·软件工程·代码规范·设计规范
汪子熙14 分钟前
理解 SSH Agent 的工作原理与应用场景
后端
苏琢玉24 分钟前
如何优雅地处理多种电商优惠规则?我用 PHP 封装了一个 Promotion Engine
后端·php·composer
惜鸟24 分钟前
Mockito 的常见核心功能及注意事项
java·mockito
豌豆花下猫26 分钟前
Python 潮流周刊#113:用虚拟线程取代 async/await
后端·python·ai
武子康27 分钟前
大数据-58 Kafka 消息发送全流程详解:序列化、分区策略与自定义实现
大数据·后端·kafka
福大大架构师每日一题28 分钟前
2025-08-02:最多 K 个元素的子数组的最值之和。用go语言,给定一个整数数组 nums 和一个正整数 k,请找出所有长度最多为 k 的连续子数组,计算
后端
Debug笔记28 分钟前
你真的理解 Java 中的线程池吗?一次“查不出原因的接口变慢”的真实排查经历
后端