深入分析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 无法解决。
设计意义 循环依赖应作为临时解决方案,长期需通过代码优化消除。
相关推荐
keep one's resolveY9 分钟前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克343 分钟前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠1 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌2 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
女生也可以敲代码2 小时前
AI时代下的50道前端开发面试题:从基础到大模型应用
前端·面试
Agent产品评测局2 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源2 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
IT_陈寒2 小时前
SpringBoot自动配置的坑差点让我加班到天亮
前端·人工智能·后端
呱牛do it2 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19433 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解