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

【Java后端】Spring 如何解决循环依赖:原理 + 源码解读

Spring 如何解决循环依赖:原理 + 源码解读

  1. 开篇引导:什么是循环依赖
  2. Spring 解决循环依赖的思路(三层缓存)
  3. 源码走读(关键方法:getSingletondoCreateBeanaddSingletonFactory
  4. 限制与不能解决的场景
  5. 实践建议

Spring 如何解决循环依赖(原理 + 源码解读)

目录

  • 一、循环依赖是什么

  • 二、Spring 的解决思路

  • 三、源码走读(三层缓存机制)

    • 1. getSingleton
    • 2. doCreateBean
    • 3. addSingletonFactory
  • 四、不能解决的场景

  • 五、常见解决办法

  • 六、总结


一、循环依赖是什么

循环依赖指的是 两个或多个 Bean 互相依赖,导致初始化时出现死循环
比如:

@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate A a;
}
  • 构造器注入:直接死锁,无法解决。
  • Setter/Field 注入:Spring 可以通过“提前暴露半成品对象”来打破循环。

二、Spring 的解决思路

Spring 采用 三层缓存机制 + 提前暴露引用 来解决单例 Bean 的循环依赖。

三层缓存:

  1. singletonObjects:一级缓存,存放完全初始化好的单例 Bean。
  2. earlySingletonObjects:二级缓存,存放早期的半成品 Bean。
  3. singletonFactories:三级缓存,存放一个 ObjectFactory,用于生成早期引用(可能是代理对象)。

流程简化:

  1. Bean 创建时,先放入三级缓存(singletonFactories)。
  2. 如果别的 Bean 依赖它,就能通过三级缓存拿到早期引用。
  3. 依赖注入完成后,Bean 初始化成功,放入一级缓存,并清理二、三级缓存。

三、源码走读(三层缓存机制)

1. getSingleton

DefaultSingletonBeanRegistry 中:

public Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 先从一级缓存中取Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 2. 一级没有,且 Bean 正在创建中,则尝试二级缓存singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 3. 二级没有,就从三级缓存拿 ObjectFactory 生成早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 提前曝光的对象放到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}return singletonObject;
}

👉 这里是 循环依赖能被打破的关键点

  • A 需要 B,发现 B 还在创建中,去二级/三级缓存里取。
  • 如果三级缓存有工厂,就生成一个“早期引用”,保证注入能继续。

2. doCreateBean

AbstractAutowireCapableBeanFactory 中,Bean 的创建核心流程:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {// 1. 实例化 Bean(构造方法)BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);Object bean = instanceWrapper.getWrappedInstance();// 2. 是否需要提前暴露?boolean earlySingletonExposure = (mbd.isSingleton() && allowCircularReferences&& isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {// 加入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 3. 填充属性(setter/field 注入)populateBean(beanName, mbd, instanceWrapper);// 4. 初始化(各种回调、AOP 代理)bean = initializeBean(beanName, bean, mbd);// 5. 放入一级缓存,清理二、三级缓存registerSingleton(beanName, bean);return bean;
}

👉 关键点:在属性注入之前,就把早期引用工厂放入三级缓存
这样如果别的 Bean 需要它,可以从三级缓存里拿到“半成品”。


3. addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);}
}

👉 确保只有在 Bean 没有完全初始化前,才会暴露到三级缓存


四、不能解决的场景

  1. 构造器注入的循环依赖

    • 因为实例化必须先完成构造器,Spring 没法提前暴露半成品。
  2. 原型作用域(prototype)的循环依赖

    • 每次创建都是新对象,不能共享早期引用。
  3. 部分 AOP/异步场景

    • 代理对象和早期引用可能不一致,导致注入问题。
  4. Spring Boot 2.6+ 默认禁止循环依赖

    • 需显式开启:

      spring.main.allow-circular-references=true
      

五、常见解决办法

  1. 重构依赖关系(最佳实践)

    • 拆出第三个 Service,消除循环。
  2. 使用 @Lazy 延迟加载

    public A(@Lazy B b) { this.b = b; }
    
  3. 使用 ObjectProviderProvider

    @Autowired
    private ObjectProvider<B> bProvider;
    
  4. ApplicationContext#getBean 延迟获取(不推荐,耦合度高)。


六、总结

  • Spring 通过 三级缓存(singletonObjects / earlySingletonObjects / singletonFactories)getEarlyBeanReference,解决了大部分 单例 + Setter/Field 注入 的循环依赖。
  • 构造器循环依赖、prototype Bean 无法解决。
  • Spring Boot 2.6+ 默认关闭循环依赖,需要显式开启。
  • 从设计角度,循环依赖往往是架构问题,最佳做法是重构消除循环


文章转载自:

http://t2xw0O7I.ctpfq.cn
http://ya5x3uuX.ctpfq.cn
http://9IEvGbkv.ctpfq.cn
http://nmapEA18.ctpfq.cn
http://9Z9LLxdX.ctpfq.cn
http://l3y5WEt8.ctpfq.cn
http://jYlC7rPz.ctpfq.cn
http://gVuAspM5.ctpfq.cn
http://SaJYj1R3.ctpfq.cn
http://4rJee1M4.ctpfq.cn
http://hOlO8xXo.ctpfq.cn
http://rwKcoBHa.ctpfq.cn
http://SbvAMEJH.ctpfq.cn
http://AeOA8Nqf.ctpfq.cn
http://t0abIppn.ctpfq.cn
http://M42AHGgF.ctpfq.cn
http://Ut3pCkKU.ctpfq.cn
http://QOhqmIkm.ctpfq.cn
http://gUz1jPtl.ctpfq.cn
http://VQv01zMM.ctpfq.cn
http://DKYtrDDB.ctpfq.cn
http://Vbfe1rVr.ctpfq.cn
http://89diLQhS.ctpfq.cn
http://XnVdwFUV.ctpfq.cn
http://PfF7jVgE.ctpfq.cn
http://Xl5PrNo3.ctpfq.cn
http://m3pWCRgB.ctpfq.cn
http://3kqtz2uC.ctpfq.cn
http://Qxecvb7G.ctpfq.cn
http://HTkEwNou.ctpfq.cn
http://www.dtcms.com/a/386467.html

相关文章:

  • 进程之间的通信(共享内存 + 其他IPC原理)
  • AI 提示词学习笔记
  • PHP通过命令行调用Ghostscript把pdf转换成图片集
  • AWS 弹性伸缩(Auto Scaling)详解:服务器如何自动顶住流量洪峰?
  • 企业级AI应用落地实战(一):落地历程分享
  • 主数据管理:标准化缺失的潜在三大风险
  • LLC--开关损耗及软开关
  • 计算机视觉 - 对比学习(下)不用负样本 BYOL + SimSiam 融合Transformer MoCo-v3 + DINO
  • 内存与网络的字节序:大端 vs 小端
  • Linux网络:网络基础
  • [视图功能3] 排序与分组在业务数据分析中的应用
  • 架构师成长之路-集群
  • 《WINDOWS 环境下32位汇编语言程序设计》学习17章 PE文件(1)
  • cursor中配置qwen3-coder模型使用
  • 智慧健康驿站:AI与IoT赋能下的健康社区建设新引擎
  • 贪心算法应用:MEC任务卸载问题详解
  • Linux基础知识-安装jdk8与jmeter
  • 基于Django+Vue的新闻文本分类系统(协同过滤推荐算法)
  • 机器人控制器开发(通讯——建图和导航模式切换)
  • 容器化部署项目05
  • AI如何深度驱动数据资产入表业务开展-一线经验
  • Chromium 138 编译指南 macOS 篇:Xcode 与开发工具安装配置(二)
  • 网络.1 UDP
  • 在 Mac 环境安装全局默认版本 Python
  • 小迪安全v2023学习笔记(八十三讲)—— 组件安全JacksonFastJsonXStreamCVE复现
  • 鲁能大师显卡跑分计算规则,与算力对应关系?
  • 边缘智能的“隐形引擎”——TinyML 模型在 ARM Cortex-M 系列上的极致量化与加速实战
  • kernel32.dll如何修复?科普kernel32.dll缺失的故障问题的多种解决方法
  • git推送远程仓库
  • 「日拱一码」091 机器学习——集成学习