【spring源码系列】之【Bean的循环依赖】
希望之光永远向着目标清晰的人敞开。
1. 循环依赖概述
循环依赖通俗讲就是循环引用,指两个或两个以上对象的bean相互引用对方,A依赖于B,B依赖于A,最终形成一个闭环。
Spring循环依赖的场景有两种:
- 构造器的循环依赖
- field 属性的循环依赖
对于构造器的循环依赖,Spring 是无法解决,只能抛出 BeanCurrentlyInCreationException 异常;对于field 属性的循环依赖,Spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。下面重点分析属性依赖的情况。
2. 循环依赖执行流程
step1
: A类实例化执行,第一次三个缓存中都没有,会走doGetBean
一路走到createBeanInstance
,完成无参构造函数实例化,并在addSingletonFactory
中设置三级缓存;
step2
:A类populateBean
进行依赖注入,随后触发了B类属性的getBean
操作;
step3
: B类与A类类似,第一次三个缓存中也都没有,无参构造函数实例化后,设置三级缓存,将自己加入三级缓存;
step4
:B类populateBean
进行依赖注入,这里触发了A类属性的getBean
操作;
step5
: A类之前正在创建,此时已经是第二次进入,由于一级二级缓存中都没有,会从三级缓存中获取,并且允许 bean提前暴露则从三级缓存中拿到对象工厂,从工厂中拿到对象成功后,升级到二级缓存,并删除三级缓存;若以后有别的类引用的话就从二级缓存中进行取;
step6
:B类拿到了A的提前暴露实例注入到A类属性中了,此时完成B类的实例化;
step7
:A类之前依赖B类,B的实例化完成,进而促使A的实例化也完成,并且此时A的类B属性已经有值,A类继续走后续的afterSingletonCreateion与addSingleton方法,删除正在创建缓存中的实例,并将实例从二级缓存移入以及缓存,同时删除二三级缓存;
以上是A类实例化的全过程,下面会针对源码逐一分析。
3. 源码分析
首先创建A的实例,需要从A的getBean
方法开始,到doGetBean
,第一次优先从缓存中取,进入getSingleton
方法:
这个方法主要是从三个缓存中获取,分别是:singletonObjects
、earlySingletonObjects
、singletonFactories
,三者定义如下:
意义如下:
- singletonObjects:单例对象的cache
- singletonFactories : 单例对象工厂的cache
- earlySingletonObjects :提前暴露的单例对象的Cache
解决循环依赖的关键是三个缓存,其中一级缓存singletonObjects
存放完全实例化的对象,对象以及其依赖的属性都有值;二级缓存earlySingletonObjects
存放半实例化的对象,相当于在内存开辟了空间,已完成创建,但是还未进行属性赋值,可以提前暴露使用;三级缓存singletonFactories
为对象工厂,用来创建提前暴露的bean并放入二级缓存中。
首次初始化A时,三个缓存中都没有对象,会进入如下getSingleton(String beanName, ObjectFactory<?> singletonFactory)
方法,该方法第二个参数是个函数式接口,当内部调用getObject
方法时,会调用createBean
方法:
先进入getSingleton
方法:
该方法完成流程图中的四个步骤:
step1
:bean单例创建前,将beanName放入singletonsCurrentlyInCreation
缓存;
step2
: singletonFactory.getObject()
调用外面函数式接口中的createBean
方法创建bean;
step3
:afterSingletonCreation
方法将beanName从singletonsCurrentlyInCreation缓存删除,表示已创建完;
step4
: 实例化后加入一级缓存,二三级缓存删除。
以上1、3、4涉及代码如下:
其中step2
流程较长,当A第一次实例化时,走到创建无参构造函数实例化createBeanInstance
,随后会走addSingletonFactory
方法:
从这段代码我们可以看出singletonFactories
这个三级缓存才是解决循环依赖的关键,该代码在createBeanInstance
方法之后,也就是说这个bean其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了,所以Spring在这个时候选择将该对象提前曝光出来让大家认识认识。当三级缓存有值后,后面如果再次用到该bean的时候,会从三级缓存中,并通过提前暴露,升级到二级缓存中,到这里我们发现三级缓存singletonFactories
和二级缓存earlySingletonObjects
中的值都有出处了,那一级缓存在哪里设置的呢?就是在A创建完,并把A依赖的属性B也创建完后,B有依赖于A,再次进入A后,A直接从二级缓存中获取,从而促使B对象创建完,随即A也就创建完成,A完成createBean后走上面的step4
中的addSingletion
方法,完成一级缓存的设置。
4.总结
Spring在创建bean的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中),这样一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 getObject() 获取了,故在缓存中使用三级缓存获取到实例,并将实例升级到二级缓存,供后续实例如需二次使用时,可直接从二级缓存中取,待实例完全创建后,升级到一级缓存,并清理二级三级缓存,总而言之提前暴露三级缓存,以及一二三级缓存的综合使用是解决循环依赖的关键,各级缓存各司其职,又能够相互呼应,spring的设计实在精妙,给我们自己设计项目提供了一种优秀的思考方式。
__EOF__

本文链接:https://www.cnblogs.com/father-of-little-pig/p/15035056.html
关于博主:不要为了技术而技术,总结分享技术,感恩点滴生活!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)