深入分析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 无法解决。
设计意义 循环依赖应作为临时解决方案,长期需通过代码优化消除。
相关推荐
猴哥源码8 分钟前
基于Java+SpringBoot的动物领养平台
java·spring boot
老任与码12 分钟前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
小兵张健13 分钟前
武汉拿下 23k offer 经历
java·面试·ai编程
FreeBuf_23 分钟前
Apache组件遭大规模攻击:Tomcat与Camel高危RCE漏洞引发数千次利用尝试
java·tomcat·apache
无妄-202434 分钟前
软件架构升级中的“隐形地雷”:版本选型与依赖链风险
java·服务器·网络·经验分享
qqxhb38 分钟前
零基础数据结构与算法——第四章:基础算法-排序(上)
java·数据结构·算法·冒泡·插入·选择
华子w90892585944 分钟前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
爱莉希雅&&&1 小时前
技术面试题,HR面试题
开发语言·学习·面试
天天扭码1 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
猴哥源码1 小时前
基于Java+SpringBoot的在线小说阅读平台
java·spring boot