一天,侄子和我哥聊天,我坐在旁边听着
侄子:爸爸,你爱我妈妈吗?
哥:这话说的,不爱能有你吗?
侄子:确定有我不是因为荷尔蒙吗?
哥:因为什么荷尔蒙,因为爱情!
侄子:那我妈花点钱,你咋老说呢?
哥:这你就不懂了,挣钱本不易,花钱要仔细
侄子:快得了吧,挣钱这么少,我妈都没跑,给你照顾家,钱还不让花
哥:我发现你这孩子怎么不知道好赖呢,我攒钱不是为了给你去媳妇啊
侄子:那你赶紧给我妈花吧,我妈要是跑了,你还得花钱娶一个,到最后,钱我捞不着,亲妈还混没了
我:通透!!!
Spring 中常见的循环依赖有 3 种:单例 setter 循环依赖、单例构造方法循环依赖、原型循环依赖
关于单例 setter 循环依赖,Spring 是如何甄别和处理的,可查看:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗
何谓单例构造方法循环依赖了,我们看具体代码就明白了
两个要素:① scope 是默认值,也就是 singleton;② 多个实例之间通过构造方法形成了循环依赖
这种情况下,Spring 是怎么处理的了,我们先来看看执行结果
Spring 启动过程中报错了: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?
问题就来了:Spring 是如何甄别单例情况下的构造方法循环依赖的,然后进行报错的
大家先把这个问题暂留在心里,我们再来看看什么是原型循环依赖
同样,我们直接看代码就明白何谓原型循环依赖了
同样是 2 个要素:① scope 不是默认值,而是 prototype,也就是原型,每次获取该实例的时候都会新建;② setter 循环依赖
这种情况下 Spring 又会有什么样的执行结果了
Spring 启动正常,但从 Spring 容器获取 loop 实例的时候,报了同样的错误
问题来了:① Spring 是如何甄别原型循环依赖的,然后进行报错提示的
② 为什么两种情况的报错时机会不一致,一个在 Spring 启动过程中,一个却在使用 Spring 的过程中
示例代码地址:spring-circle-dependence-type
上面的 3 个问题,概括下就是
1、Spring 是如何甄别单例情况下的构造方法循环依赖的
2、Spring 是如何甄别原型循环依赖的
3、为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致
我们慢慢往下看,跟源码的过程可能比较快,大家看仔细了
还是那句话
看完之后仍有疑问,可以评论区留言,也可以自行去查阅相关资料进行解疑
Spring 读取和解析 xml 的过程,我们就不去跟了,我们重点跟一下我们关注的内容
我们从 DefaultListableBeanFactory 类的 preInstantiateSingletons 方法作为起点
按如下顺序可以快速的找到起点,后面两种情况都从此处开始进行源码跟踪
闲话少说,我们直接开始跟源码
cat 的 RootBeanDefinition 中有几个属性值得我们注意下
接着往下走
我们来到了 createBeanInstance 方法,此时 Set<String> singletonsCurrentlyInCreation 只存放了 cat
singletonsCurrentlyInCreation 看字面意思就知道,存放的是当前正在创建中的单例对象名
我们接着往下跟
由于 constructorArgumentValues 中有元素,所以需要通过有参构造函数来创建 cat 对象
因为构造函数的参数是 Dog 类型的 dog ,所以通过反射调用 Cat 的有参构造函数来创建 cat 之前,需要先从 Spring 容器中获取到 dog 对象
所以流程又来到了我们熟悉的 getBean ,只是现在获取的是 dog ;获取流程与获取 cat 时一样,所以跟的速度会快一些,大家注意看我停顿的地方
此时 singletonsCurrentlyInCreation 存放了 cat 和 dog ,表示他们都在创建中
又来到了 createBeanInstance ,过程与之前 cat 的过程一样,我们接着往下看
又来到了熟悉的 getBean ,需要从 Spring 容器获取 Dog 构造函数依赖的 cat 对象
接下来重点来了,大家看清楚了
因为 singletonsCurrentlyInCreation 已经存在 cat 了, !this.singletonsCurrentlyInCreation.add(beanName) 结果就是 true
说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException
我们在控制台看到的异常信息就从这来的
原型类型的实例有个特点:每次获取都会重新创建一个实例,那在 Spring 启动过程中,还有创建的必要吗?
我们来跟下源码就明白了
关键代码
不符合上述 3 个条件的实例,在 Spring 启动过程中都不会被创建
下面接着讲正题,来看看 Spring 是如何甄别原型循环依赖的
获取 loop 实例
在 loop 实例创建之前,调用了 beforePrototypeCreation 方法,将 loop 名放到了 ThreadLocal<Object> prototypesCurrentlyInCreation
表示当前线程正在创建 loop ,我们接着往下看
原型类型的对象创建过程分两步:① 实例化(反射调构造方法),② 初始化(属性填充),和单例类型对象的创建过程是一样的
依赖的处理是在初始化过程中进行的, loop 对象依赖 circle 属性,所以对 loop 对象的 circle 属性进行填充的时候,需要去 Spring 容器获取 circle 实例
又来到了我们熟悉的 getBean ,获取 loop 依赖的 circle 实例,我们继续往下跟
在 circle 对象创建之前,同样调用了 beforePrototypeCreation 方法,那么此时 prototypesCurrentlyInCreation 中就同时存在 loop 和 circle
表示当前线程正在创建 loop 实例和 circle 实例;继续往下走
兜兜转转又来到了 getBean ,获取 circle 对象依赖的 loop 属性,接下来是重点,大家看仔细了
因为 prototypesCurrentlyInCreation 中存在 loop 了,说明当前线程正在创建 loop 实例
而现在又要创建新的 loop ,说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException
经过上面的梳理,相信大家对之前的三个问题都没有疑问了,我们来总结下
1、Spring 是如何甄别单例情况下的构造方法循环依赖的
Spring 通过 Set<String> singletonsCurrentlyInCreation 记录当前正在创建中的实例名称
创建实例对象之前,会判断 singletonsCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException
2、Spring 是如何甄别原型循环依赖的
Spring 通过 ThreadLocal<Object> prototypesCurrentlyInCreation 记录当前线程正在创建中的原型实例名称
创建原型实例之前,会判断 prototypesCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException
3、为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致
单例构造方法实例的创建是在 Spring 启动过程中完成的,而原型实例是在获取的时候创建的
所以两者的循环依赖的报错时机不一致
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有