当前位置: 首页 > news >正文

【Spring】Spring是如何解决循环依赖问题的

Spring 通过 三级缓存提前暴露对象引用 的机制解决单例 Bean 的循环依赖问题。以下是详细原理和实现流程:


一、循环依赖的场景

假设存在以下相互依赖的 Bean:

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

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

此时 Spring 需要处理 A → B → A 的循环依赖。


二、Spring 的解决机制

1. 三级缓存

Spring 使用三级缓存存储不同状态的 Bean:

缓存名称作用
singletonObjects存储完全初始化后的单例 Bean(一级缓存)
earlySingletonObjects存储早期暴露的 Bean(未完成属性填充和初始化,二级缓存)
singletonFactories存储 Bean 的 ObjectFactory(用于生成早期引用,三级缓存)
2. Bean 创建流程(以 A → B → A 为例)

步骤 1:创建 Bean A

  1. 实例化 A(调用构造函数,但属性未填充)。
  2. 将 A 的 ObjectFactory 放入 三级缓存singletonFactories)。
  3. 开始填充 A 的属性,发现依赖 B。

步骤 2:创建 Bean B

  1. 实例化 B(调用构造函数,属性未填充)。
  2. 将 B 的 ObjectFactory 放入 三级缓存
  3. 开始填充 B 的属性,发现依赖 A。

步骤 3:解决依赖 A

  1. 三级缓存 中获取 A 的 ObjectFactory,生成 A 的早期引用。
  2. 将 A 的早期引用放入 二级缓存earlySingletonObjects),并从三级缓存中移除。
  3. 将 A 的早期引用注入到 B 中,完成 B 的初始化。
  4. 将 B 的完整实例放入 一级缓存singletonObjects)。

步骤 4:完成 Bean A 的初始化

  1. 将已注入 B 的 A 实例继续初始化。
  2. 将 A 的完整实例从 二级缓存 移到 一级缓存

三、核心源码解析

关键类DefaultSingletonBeanRegistry
缓存操作

// 从缓存获取 Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 三级缓存
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
    }
    return singletonObject;
}

四、限制条件

1. 仅支持单例(Singleton)作用域的 Bean

原型(Prototype)作用域的 Bean 无法解决循环依赖,Spring 直接抛出 BeanCurrentlyInCreationException

2. 不支持构造器注入的循环依赖

构造器注入的循环依赖无法解决(实例化阶段就需要依赖对象,但此时依赖的 Bean 未创建)。
错误示例

@Component
public class A {
    private B b;
    public A(B b) { this.b = b; } // 构造器注入导致循环依赖无法解决
}
3. 需要启用代理

• 若 Bean 被 AOP 代理(如通过 @Async@Transactional),需确保代理对象能正确暴露到缓存中。


五、如何避免循环依赖?

  1. 代码设计优化
    • 避免双向依赖,改为单向依赖。
    • 使用 @Lazy 延迟加载(将依赖标记为惰性初始化)。
    • 通过事件驱动(ApplicationEvent)解耦 Bean。

  2. 依赖注入方式
    • 优先使用 Setter 注入 而非构造器注入。

  3. 示例:使用 @Lazy

    @Component
    public class A {
        @Autowired
        @Lazy // 延迟注入 B 的代理对象
        private B b;
    }
    

六、总结

机制说明
三级缓存通过缓存不同状态的 Bean 解决循环依赖
提前暴露引用在 Bean 未完成初始化前,提前将引用暴露给其他 Bean 使用
单例限制仅支持单例 Bean,原型 Bean 和构造器注入的循环依赖无法解决

最佳实践:优先通过代码设计避免循环依赖,仅在必要场景下依赖 Spring 的自动解决机制。

相关文章:

  • [C语言]内存函数的使用和模拟实现
  • 分类操作-01.新增分类
  • canal集群部署
  • 求出e的值(信息学奥赛一本通-1092)
  • ctfshow做题笔记—栈溢出—pwn69~pwn72
  • HybridCLR Generate All 报错UnityLinker.exe
  • Ubuntu-配置apt国内源
  • SpringBoot 实现接口数据脱敏
  • 【自学笔记】MoonBit语言基础知识点总览-持续更新
  • GOF设计模式在 Spring 框架中的核心应用分析
  • golang算法快慢指针
  • 19个判定学术写作内容有AI生成痕迹的例子
  • (Lauterbach调试器学习笔记)一、首次连接TriCore开发板调试
  • AutoGen学习笔记系列(十三)Advanced - Logging
  • 第75期 Doxygen是干嘛的,Windows版本,如何安装,学习
  • 函数题 01-复杂度3 二分查找【PAT】
  • 市盈率研究
  • Spring Boot集成EasyExcel
  • Python使用入门(二)
  • 侯捷 C++ 课程学习笔记:C++ 新标准11/14
  • 国际博物馆日|航海博物馆:穿梭于海洋神话与造船工艺间
  • 当“小铁人”遇上青浦,看00后如何玩转长三角铁三
  • 舞者王佳俊谈“与AI共舞”:像多了一个舞伴,要考虑它的“感受”
  • 《五行令》《攻守占》,2个月后国博见
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 王东杰评《国家与学术》︱不“国”不“故”的“国学”