Spring如何巧妙解决循环依赖问题

什么是循环依赖?

循环依赖是指两个或多个Bean之间相互依赖,形成闭环的情况。例如:AService依赖BService,而BService又依赖AService。这种场景下,传统的创建顺序无法满足依赖注入的要求。

Spring的三级缓存机制

Spring通过三级缓存机制优雅地解决了循环依赖问题:

1. 第一级缓存:singletonObjects

  • 存储完全初始化完成的单例Bean

  • 这里的Bean是经过完整生命周期处理的(包括AOP代理)

2. 第二级缓存:earlySingletonObjects

  • 存储提前暴露的Bean引用(早期引用)

  • 这些Bean尚未完成完整初始化,但可以用于解决循环依赖

3. 第三级缓存:singletonFactories

  • 存储Bean的ObjectFactory(工厂对象)

  • 通过工厂可以获取Bean的早期引用,必要时创建代理对象

解决循环依赖的详细流程

场景:AService ←→ BService 相互依赖

java

// AService 依赖 BService
@Service
public class AService {@Autowiredprivate BService bService;
}// BService 依赖 AService
@Service
public class BService {@Autowiredprivate AService aService;
}

解决步骤:

  1. 开始创建AService

    • creatingSet.add('AService') - 标记AService正在创建

    • 实例化AService普通对象

  2. AService需要注入BService

    • 从单例池获取BService,不存在则开始创建BService

  3. 创建BService

    • creatingSet.add('BService') - 标记BService正在创建

    • 实例化BService普通对象

    • BService需要注入AService

  4. BService获取AService

    • 从单例池获取AService,不存在

    • 检查creatingSet发现AService正在创建(循环依赖 detected!)

    • 从三级缓存获取AService的ObjectFactory

    • 执行工厂方法:getEarlyBeanReference() → 必要时创建AService代理对象

    • 将结果存入二级缓存earlySingletonObjects,并从三级缓存移除工厂

  5. BService继续初始化

    • 注入AService的早期引用(可能是代理对象)

    • 完成其他属性填充

    • 执行AOP(如果需要)

    • 将完整BService存入单例池

  6. AService继续初始化

    • 注入已创建的BService

    • 完成其他属性填充

    • 执行AOP(但发现已提前AOP,直接使用早期引用)

    • 将完整AService存入单例池

关键源码分析

1. 添加单例工厂

java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 添加到三级缓存this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

doCreateBean()方法中,Spring会提前暴露Bean的工厂:

java

// 为了解决循环依赖提前缓存单例创建工厂
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2. 获取单例Bean的逻辑

java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 从一级缓存查找Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 2. 从二级缓存查找singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// 3. 从三级缓存查找并创建早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 移动到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

3. 提前AOP处理

java

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {// 如果需要AOP,则创建代理对象Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;}return bean;
}

为什么需要三级缓存?

  1. 一级缓存:存储完整的单例Bean,保证单例性质

  2. 二级缓存:存储早期引用,避免多次执行AOP产生多个代理对象

  3. 三级缓存:存储工厂对象,延迟决策是否需要进行AOP

如果只有二级缓存,每次获取早期引用都需要执行AOP,可能导致创建多个不同的代理对象,违反单例原则。

使用注意事项

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/99231.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/99231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CUDA 中Thrust exclusive_scan使用详解

1. 基本概念Thrust 是 NVIDIA CUDA 提供的类似 C STL 的并行算法库。Scan (前缀和)&#xff1a;给定数组 [a0, a1, a2, ...]&#xff0c;产生前缀和序列。Exclusive Scan (排他前缀和)&#xff1a; 输出位置 i 存放的是输入数组中 0 到 i-1 的累积结果。换句话说&#xff0c;结…

Linux -- 信号【上】

目录 一、信号的引入 1、信号概念 2、signal函数 普通标准信号详解表 3、前台/后台进程 3.1 概念 3.2 查看后台进程 3.3 后台进程拉回前台 3.4 终止后台进程 3.5 暂停前台进程 3.6 回复运行后台进程 4、发信号的本质 二、信号的产生 1、终端按键 2、系统调用 2…

Altium Designer(AD)自定义PCB外观颜色

目录 1视图设置界面介绍 2PCB阻焊层颜色设置 2.1进入视图设置界面 2.2阻焊层颜色设置 2.3顶层和底层阻焊层颜色设置 2.4顶层阻焊层试图效果 2.5底层阻焊层试图效果 3设置PCB丝印颜色设置 3.1找到丝印设置选项 3.2设置顶层和底层丝印颜色 3.3顶层丝印 3.4底层丝印 4…

5天改造,节能50%!冷能改造如何实现“不停产节能”?

你有没有发现一个现象&#xff1f;很多工厂老板一提到节能改造&#xff0c;第一反应就是摇头。不是不想省电费&#xff0c;而是怕停产。停产一天损失几十万&#xff0c;改造周期动辄几个月&#xff0c;这账怎么算都不划算。但如果我告诉你&#xff0c;有一种改造方式&#xff0…

【Flink】窗口

目录窗口窗口的概念窗口的分类滚动窗口&#xff08;Tumbling Windows&#xff09;滑动窗口&#xff08;Sliding Windows&#xff09;会话窗口&#xff08;Session Windows&#xff09;全局窗口&#xff08;Global Windows&#xff09;窗口API概览窗口函数增量聚合函数ReduceFun…

攻击路径(4):API安全风险导致敏感数据泄漏

本文是《攻防演练 | JS泄露到主机失陷[1]》的学习笔记&#xff0c;欢迎大家阅读原文。攻击路径通过未授权访问攻击获取敏感数据通过SQL注入攻击获取服务器权限通过凭据访问攻击获取数据库权限和敏感数据和应用权限安全风险与加固措施通过未授权访问攻击获取敏感数据、通过SQL注…

机器学习面试题:请介绍一下你理解的集成学习算法

集成学习&#xff08;Ensemble Learning&#xff09;的核心思想是“集思广益”&#xff0c;它通过构建并结合多个基学习器&#xff08;Base Learner&#xff09;来完成学习任务&#xff0c;从而获得比单一学习器更显著优越的泛化性能。俗话说&#xff0c;“三个臭皮匠&#xff…

Invalid bound statement (not found): com.XXX.XXx.service.xxx无法执行service

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.xxx.service.CitytownService.selectCitytown 出现无法加载sevice层的时候&#xff0c;如下图所示1&#xff0c;处理方法是&#xff0c;先看下注解MapperScan内的包地址&#xff0c…

泛型(Generics)what why when【前端TS】

我总是提醒自己一定要严谨严谨严谨 目录TypeScript 泛型 (Generics)1. 什么是泛型&#xff1f;2. 为什么需要泛型&#xff1f;3. 泛型常见用法3.1 函数泛型3.2 接口泛型3.3 类泛型3.4 泛型约束3.5 泛型默认值3.6 多个泛型参数4. 泛型应用场景TypeScript 泛型 (Generics) 1. 什…

分布式协议与算法实战-协议和算法篇

05丨Paxos算法&#xff08;一&#xff09;&#xff1a;如何在多个节点间确定某变量的值? 提到分布式算法&#xff0c;就不得不提 Paxos 算法&#xff0c;在过去几十年里&#xff0c;它基本上是分布式共识的代名词&#xff0c;因为当前最常用的一批共识算法都是基于它改进的。比…

9.13 9.15 JavaWeb(事务管理、AOP P172-P182)

事务管理事务概念事务是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败操作开启事务&#xff08;一组操作开始前&#xff0c;开启事务&#xff09;&#xff1a;start transaction / begin提交事务&#xff08;这…

检索融合方法- Distribution-Based Score Fusion (DBSF)

在信息检索&#xff08;IR&#xff09;、推荐系统和多模态检索中&#xff0c;我们常常需要融合来自多个检索器或模型的结果。不同检索器可能对同一文档打出的分数差异很大&#xff0c;如果直接简单加权&#xff0c;很容易出现某个检索器“主导融合结果”的情况。 Distribution…

Oracle体系结构-归档日志文件(Archive Log Files)

核心概念&#xff1a;什么是归档日志文件&#xff1f; 定义&#xff1a; 归档日志文件&#xff08;Archive Log Files&#xff09;是在线重做日志文件&#xff08;Online Redo Log Files&#xff09;在被覆盖之前的一个完整副本。它们由 Oracle 的后台进程 ARCn&#xff08;归档…

GoogLeNet实战:用PyTorch实现经典Inception模块

配套笔记&讲解视频&#xff0c;点击文末名片获取研究背景&#xff08;Background&#xff09; 1.1 领域现状&#xff08;大环境与挑战&#xff09; 想象一下&#xff0c;你和朋友们在看一大堆照片——猫、狗、汽车、蛋糕&#xff0c;大家要把每张照片贴上标签。几年前&…

【开题答辩全过程】以 “旧书驿站”微信小程序的设计与开发为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

【办公类-112-01】20250912家园每周沟通指导(Deepseek扩写完善+Python模拟点击鼠标自动发送给家长微信)

背景需求 孩子刚上小班,家长比较关心孩子情况(情绪、社交、吃饭等) 所以我每周五晚上和家长沟通一下孩子的情况。 操作流程 第一周(9月5日)是“适应周”,我添加了所有孩子的一位家长的微信号 23份全部是手打,足足写了4个小时。第一周案例多,所以写了很多,措辞酝酿后…

Spark专题-第一部分:Spark 核心概述(1)-Spark 是什么?

众所周知&#xff0c;教学文档总该以理论部分作为开篇&#xff0c;于是我们这篇Spark专题同样会以一堆理论和专有名词开始&#xff0c;笔者会尽可能的让专业词汇通俗易懂 第一部分&#xff1a;Spark 核心概述 Spark 是什么&#xff1f; 1. 大数据时代的"超级赛车"…

从零到一上手 Protocol Buffers用 C# 打造可演进的通讯录

一、为什么是 Protobuf&#xff08;而不是 XML/自定义字符串/.NET 二进制序列化&#xff09; 在需要把结构化对象持久化或跨进程/跨语言传输时&#xff0c;常见方案各有痛点&#xff1a; BinaryFormatter 等 .NET 二进制序列化&#xff1a;对类型签名与版本极其脆弱、体积偏大&…

计算机网络(三)网络层

三、网络层网络层是五层模型中的第三层&#xff0c;位于数据链路层和传输层之间。它的核心任务是实现数据包在不同网络之间&#xff08;跨网络&#xff09;的逻辑传输。网络层的数据传输单位是数据报&#xff08;Datagram&#xff09;或数据包&#xff08;Packet&#xff09;。…

互联网大厂Java面试实录:从基础到微服务全栈技术答疑

互联网大厂Java面试实录&#xff1a;从基础到微服务全栈技术答疑 本文以电商场景为背景&#xff0c;展现一场互联网大厂Java开发职位的面试过程。严肃的面试官与搞笑的水货程序员谢飞机展开三轮技术问答&#xff0c;涵盖Java SE、Spring Boot、数据库、微服务、安全以及CI/CD等…