浅入浅出的讲解Spring循环依赖问题
作者:一条coding 发布时间:2023-11-03 07:16:11
目录
前言
概念
什么是循环依赖?
报错信息
通俗版理解
两人对峙
必须有一人妥协
Spring版理解
实例化和初始化什么区别?
* 缓存
创建过程(简易版)
创建过程(源码版)
最后
前言
最近有粉丝问到了循环依赖问题,以后再有人问你,拿这篇“吊打”他。
概念
什么是循环依赖?
多个bean之间相互依赖,形成了一个闭环。比如:A依赖于B、B依赖于C、C依赖于A。
通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,基于set方法构造注入的属性互相引用的场景。
循环依赖的种类及能否解决如下:
名称 | 是否可解决循环依赖 |
---|---|
构造器循环依赖 | 否 |
Setter循环依赖 | 是 |
Prototype作用域的循环依赖 | 否 |
报错信息
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘myDao': Requested bean is currently in creation: Is there an unresolvable circular reference?
翻译一下
通过构造函数参数 0 表示的依赖关系未得到满足;嵌套的异常是 创建名称为'myDao'的bean时出错。请求的Bean目前正在创建中。是否存在一个无法解决的循环引用?
异常信息:bean当前创建异常org.springframework.beans.factory.BeanCurrentlyInCreationException。
通俗版理解
两人对峙
现在甲乙两个人,互相对峙,甲说乙先放,乙说甲先放。就是不开枪。
哎,就是玩!
相信这个场景大家在电视剧里都见过吧,最后一般是“反派死于话多”。
但是回到我们 spring里,我们是不希望有人死亡的,也就是必须两个bean都创建出来,怎么办?
必须有一人妥协
解决方案就是:必须有一个人先妥协。
甲说:我退一步,我先把弹夹卸了,你把枪放下。
乙一听就感动了,满含热泪的拿枪放下了。
甲一看乙没有打自己,也热泪盈眶,两人紧紧相拥。
从此过上了幸福美满的生活……
Spring版理解
回到我们spring里,先回顾一下bean的生命周期:
实例化
属性赋值
初始化
销毁
简单理解一下的上面的过程
实例化和初始化什么区别?
是不是只差了中间赋值的过程,那只实例化的bean可以使用吗?
当然不可以!
也就是说只实例化的bean是一个半成品,初始化之后才是成品,才可以使用。
现在A依赖B,B依赖A。
A对B说:我要完整的你
b也对a:我要完整的你
ok,两人打起来了,拿枪对峙。怎么解决?是不是得一个人妥协。
a说:算了吧,你给我个你的半成品,我将就一下。
b心里寻思,他用我的半成品创建一个完整的a,然后我就可以创建了。
心里这么想,嘴上就爽快答应着:行,没问题。
如此,a创建了完整的自己,b拿着a也完成了创建。
问题解决。
真的解决了吗?成品和半成品都存在哪里呢?
这就不得不提到大名鼎鼎的 * 缓存。
* 缓存
spring提供了 * 缓存来存放成品和半成品及工厂。位于DefaultSingletonBeanRegistry类中。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/**
*一级缓存:单例池
*存放已经初始化的bean——成品
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
/**
* * 缓存:单例工厂的高速缓存
*存放生成bean的工厂
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
/**
*二级缓存:早期单例对象的高速缓存
*存放已经实例化但未初始化(未填充属性)的的bean——半成品
*/
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
}
创建过程(简易版)
如果你是面试突击,建议把简易版被下来就可以应付面试了
等有时间再看源码版
假如A依赖B,B依赖A,那么这两个类之间形成了一个循环依赖
A先开始创建,通过其无参构造方法创建bean的实例,并将其实例放入到「二级缓存」提前暴露出来。A停止。
B开始创建,先去「一级缓存」找A的成品,找不到,再去「二级缓存」里找,还找不到,再去「 * 缓存」里找,找到了A的创建工厂,通过工厂,拿到A的半成品,并将A放到「二级缓存」。
拿到A后,B完成创建,将自己放入「一级缓存」。
此时A继续创建,同样从「一级缓存」开始找,拿到B后完成创建,将自己放入「一级缓存」。
创建过程(源码版)
源码版建议配合spring源码边debug边食用。
1、当我们在调用getBean()获取bean时,实际调用的是doGetBean() 方法。doGetBean() 想要获取 beanA ,于是调用 getSingleton() 方法从缓存中查找 beanA
2、在 getSingleton() 方法中,从「一级缓存」中查找,没有,返回 null
3、doGetBean() 方法中获取到 beanA 为 null ,于是走对应的处理逻辑,调用 getSingleton() 的重载方法(参数为 ObjectFactory 的)
4、在 getSingleton()方法中,先将 beanA_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean 方法
5、进入 AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出 beanA 的实例,然后判断,是否为单例,是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否是在第四步的集合中)判断为 true 则将 beanA 添加到「 * 缓存」中
6、对 beanA 进行属性填充,此时检测到 beanA 依赖于 beanB ,于是查找 beanB
7、调用 doGetBean() 方法,和上面 beanA 的过程一样,到缓存中查询 beanB ,没有则创建,然后给 beanB 填充属性
8、此时 beanB 依赖于 beanA ,调用 getSingleton() 获取 beanA ,依次从一级、二级、 * 缓存中找、此时从「 * 缓存」中获取到 beanA 的创建工厂,通过创建工厂获取到 singletonObject ,此时这个 singletonObject 指向的就是上面在 doCreateBean() 方法中实例化的 beanA
9、这样 beanB 就获取到了 beanA 的依赖,于是 beanB 顺利完成初始化,并将 beanA 从「 * 缓存」移动到「二级缓存」中
10、随后 beanA 继续他的属性填充工作,此时也获取到了 beanB ,beanA 也随之完成了创建,回到 getSingleton() 方法中继续向下执行,将 beanA 从「二级缓存」移动到「一级缓存」中
最后
来源:https://juejin.cn/post/7017631372923633694


猜你喜欢
- 本文实例讲述了Java 8 Stream 的终极技巧——Collectors 功能与操作方法。分享给大家供大家参考,具体如下:1. 前言昨天
- 一、原理区别:Java * 是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib
- 安卓虽然已经成为了移动设备第一操作系统,且影响力也延伸到了汽车和tv端,不过对于谷歌来说,需要依靠Java来做安卓开发一直是一个心病,因为O
- 前提今天在群里聊天的时候有群友问如何捕获错误日志,我说可以自己写,也可以用第三方的比如腾讯的bugly,友盟的错误统计等等,但是那些是别人的
- java 实现文件夹的拷贝实例代码 这里就直接上代码,
- 本文为大家分享了C#实现图书管理系统课程设计,供大家参考,具体内容如下一、设计目的通过模拟图书管理系统,实现以下功能学生账号的注册学生对馆藏
- 在使用手机时,蓝牙通信给我们带来很多方便。那么在Android手机中怎样进行蓝牙开发呢?本文以实例的方式讲解Android蓝牙开发的知识。&
- Result 类型是许多编程语言中处理错误的常用方式,包括 C# 的 dotNext 库。在本文中,我们将通过例子回顾 C# 中 using
- 前言本文提供三种不同的解决方式,也是三种不同的情况和思路我的问题是在springboot整合了xxl-job一段时间后出现的。如果你程序里集
- 前言在springboot项目中只需一句代码即可实现多个数据源之间的切换:// 切换sqlserver数据源:DataSourceConte
- sqlite是啥?1、一种轻型数据库2、关系型数据库3、占用资源很低,几百K内存,适合嵌入式设备4、支持windows、linux、unix
- JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了
- 前言如果你想玩转C# 里面多线程,工厂模式,生产者/消费者,队列等高级操作,就可以和我一起探索这个强大的线程安全提供阻塞和限制功能的C#神器
- 背景大家在使用Selenium + Chromedriver爬取网站信息的时候,以为这样就能做到不被网站的反爬虫机制发现。但是实际上很多参数
- 有很多地方要用到DatePickerDialog。但有时项目用到的主题样式是很丑的样式,显示出来的真丑。而我们真正想要的样式是这样的。这个就
- 昨天实现一个功能,根据文章的id或者别名查找文章。起初采用mybatis的Example进行查询,对参数artName进行判断,如果是纯数字
- Java8被称作Java史上变化最大的一个版本。其中包含很多重要的新特性,最核心的就是增加了Lambda表达式和StreamAPI。这两者也
- 跟同事讨论到- 用C# Stopwatch 取得效能数值,Stopwatch.ElapsedMilliseconds 只到毫秒(ms),如果
- classProgram{ staticvoid Main() {&
- 前言安卓开发中一个很基础的操作就是打开一个 Activity ,另一个很必要的操作就是,打开一个 Activity ,在打开的 Activi