作者:青石路

cnblogs.com/youzhibing/p/14514823.html

写在前面

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 的过程中

示例代码地址:

https://gitee.com/youzhibing/spring-circle/tree/master/spring-circle-dependence-type

上面的 3 个问题,概括下就是

  • Spring 是如何甄别单例情况下的构造方法循环依赖的

  • Spring 是如何甄别原型循环依赖的

  • 为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致

我们慢慢往下看,跟源码的过程可能比较快,大家看仔细了

还是那句话

看完之后仍有疑问,可以评论区留言,也可以自行去查阅相关资料进行解疑

源码起点

Spring 读取和解析 xml 的过程,我们就不去跟了,我们重点跟一下我们关注的内容

我们从 DefaultListableBeanFactory 类的 preInstantiateSingletons 方法作为起点

按如下顺序可以快速的找到起点,后面两种情况都从此处开始进行源码跟踪

构造方法循环依赖的甄别

闲话少说,我们直接开始跟源码

获取 cat 实例

cat 的 RootBeanDefinition 中有几个属性值得我们注意下

接着往下走

我们来到了 createBeanInstance 方法,此时 Set<String> singletonsCurrentlyInCreation 只存放了 cat

singletonsCurrentlyInCreation 看字面意思就知道,存放的是当前正在创建中的单例对象名

我们接着往下跟

由于 constructorArgumentValues 中有元素,所以需要通过有参构造函数来创建 cat 对象

因为构造函数的参数是 Dog 类型的 dog ,所以通过反射调用 Cat 的有参构造函数来创建 cat 之前,需要先从 Spring 容器中获取到 dog 对象

获取 Cat 构造函数依赖的 dog 实例

所以流程又来到了我们熟悉的 getBean ,只是现在获取的是 dog ;获取流程与获取 cat 时一样,所以跟的速度会快一些,大家注意看我停顿的地方

此时 singletonsCurrentlyInCreation 存放了 cat 和 dog ,表示他们都在创建中

又来到了 createBeanInstance ,过程与之前 cat 的过程一样,我们接着往下看

又来到了熟悉的 getBean ,需要从 Spring 容器获取 Dog 构造函数依赖的 cat 对象

获取 Dog 构造函数依赖的 cat 对象

接下来重点来了,大家看清楚了

因为 singletonsCurrentlyInCreation 已经存在 cat 了, !this.singletonsCurrentlyInCreation.add(beanName) 结果就是 true

说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException

我们在控制台看到的异常信息就从这来的

原型循环依赖的甄别

原型类型的实例有个特点:每次获取都会重新创建一个实例,那在 Spring 启动过程中,还有创建的必要吗?

Spring 启动不创建 prototype 类型的实例

我们来跟下源码就明白了

关键代码

不符合上述 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 启动过程中完成的,而原型实例是在获取的时候创建的

所以两者的循环依赖的报错时机不一致。另外,关注Java知音公众号,回复“后端面试”,送你一份面试题宝典!

推荐好文

>>【练手项目】基于SpringBoot的ERP系统,自带进销存+财务+生产功能>>分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
>>能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!

不懂就问,Spring 是如何判定原型循环依赖和构造方法循环依赖的?相关推荐

  1. elementui table 不显示表头_不懂就问 || 单晶XRD第二期课后答疑出炉啦!!

    单晶XRD第二期课程答疑终于来啦!单晶XRD第二期上课的内容都会了没不会也没关系往下看老师带着答案走来了提问!回答!1 怎么看独立可观测点和衍射点分别是多少?答:可以在后缀是.abs文件中查看.2 没 ...

  2. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)

    ⭐欢迎订阅<大厂面试突击>专栏,面试10多家大厂总结出的重点知识,仅前50名免费⭐ 在日落大道浪漫出逃 除了风没有人知道 前言 哈喽大家好,我是一条 最近3天面了大约有10家公司,平均一天 ...

  3. 菩提树下都是宝,大家学习要趁早,不懂多问为什么,这种态度才算好

    好好学习 天天向上  菩提树下都是宝,大家学习要趁早,不懂多问为什么,这种态度才算好.           每当你想睡觉的时候,每当你想说自己学了很久想放松一下的时候,  每当你不愿意坚持下去的时候, ...

  4. EasyPoi横向遍历的用法(不懂就问)

    EasyPoi横向遍历的用法(不懂就问) 一.前因 示例展示 说明 一.前因 这段时间开始使用easypoi进行开发,最近用到了横向遍历的模板语法,然而无论怎么试,都输出不了,我就纳闷了.然后就是去官 ...

  5. 不懂就问,刚用vs2019运行一个c++为什么会这样啊

    不懂就问,刚用vs2019运行一个c++为什么会这样啊 小白想试试这个编译器但是一上来我就蒙了 我一点运行就变成了下面那样了 我不想什么附加到进程啊 我也不知道怎么回事,我只想好好地运行 谢谢大家帮我 ...

  6. php是一段代码嘛,php一段代码不懂下来问上?

    当前位置:我的异常网» JavaScript » php一段代码不懂下来问上? php一段代码不懂下来问上? www.myexceptions.net  网友分享于:2013-09-23  浏览:8次 ...

  7. Java面试前看看一篇就不怕问Spring框架了

    1 Spring核心组件 一句话概括:Spring是一个轻量级.非入侵式的控制反转(IoC)和面向切面(AOP)的框架. Spring 版本 JDK版本 1.x 1.3:引入了动态代理机制,AOP 底 ...

  8. Spring依赖注入和循环依赖问题分析

    Spring源码揭秘之依赖注入和循环依赖问题分析 前言 依赖注入的入口方法 依赖注入流程分析 AbstractBeanFactory#getBean AbstractBeanFactory#doGet ...

  9. Spring基础专题——第三章(反转控制与依赖注入)

    前言:去年到现在一直没有很好的时间完成这个spring基础+源码的博客目标,去年一年比较懒吧,所以今年我希望我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从 ...

最新文章

  1. ValueError: urls must start with a leading slash
  2. linux设置nexus开机自启动_在linux中使用nexus搭建maven私服
  3. 一起睡了四年的大学舍友
  4. 在.net开发中使用Log4Net组件
  5. php 怎么防注入,php 防止注入的几种办法
  6. Caffe RPN :error C2220: warning treated as error - no 'object' file generated
  7. city code table广东_专访 | 杨龙——第20届广东十佳服装设计师
  8. jzoj3058-火炬手【高精度,暴力】
  9. cocos2d 走动椭圆
  10. 解决编译redis报错zmalloc.h:50:10: fatal error: jemalloc/jemalloc.h: No such file or directory
  11. 大数据分析需注意哪些问题
  12. 反射机制——获取Class中的方法
  13. oracle 最大一行,一行最大column数和row piece-概念
  14. 2018年高教社杯全国大学生数学建模竞赛A题解题思路
  15. 闫啸的发明和发现20220901
  16. 华硕顽石四代自主扩展内存条图解
  17. Flink reduce详解
  18. Android百度地图导航出现无法起调问题的解决方法
  19. python读书笔记2000_python高级编程读书笔记(一)
  20. 片上总线Wishbone 学习(十)总线周期之单写读操作

热门文章

  1. 你手中的iPhone 7已过时!被苹果列为清仓产品,或为iPhone SE2让路
  2. 曝微信低调上线“银行储蓄”服务,这是越来越像支付宝的节奏?
  3. 暴风集团:冯鑫因涉嫌对非国家工作人员行贿被公安机关拘留
  4. 华为回应“锁屏广告”事件:非官方所为
  5. 5G套餐月资费感受下:最低325元 仅提供8GB数据流量
  6. 官宣!苹果3月25日发布会邀请函派出:服务为主硬件为辅
  7. 面试官十大常问面试问题总结
  8. 爬虫教程 python3_【Python3爬虫】Appium入门教程
  9. opencv HOG SVM
  10. webrtc 静音检测(二)