深入分析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 无法解决。
设计意义 循环依赖应作为临时解决方案,长期需通过代码优化消除。
相关推荐
Joy T6 分钟前
Spring AI 2.0 Agent 进阶:Memory、State 与 Context Engineering 常见技术全景
java·人工智能·后端·spring·agent入门·agent state
估值探索者7 分钟前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
天若有情67319 分钟前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++
打工仔折腾 AI1 小时前
FastAPI 从本机到生产服务器:Nginx+Gunicorn+Uvicorn 完整部署实录
人工智能·后端·python·nginx·fastapi·gunicorn
IT_陈寒1 小时前
Java空指针这次真把我坑惨了
前端·人工智能·后端
moMo1 小时前
LangChain 到 LangGraph: RAG 知识库改造
后端
用户574385305112 小时前
元数据驱动的通用 CRUD 后端框架 (3- createBusiness 工厂:三层装配 + 一行出 REST)
后端
西峰u2 小时前
Java单例模式|从基础到多线程安全
java·java单例模式
ClinicTech2 小时前
实验室洗瓶机的清洗系统架构与自动化控制解析
java·python