spring源码深度解析— IOC 之 循环依赖处理
什么是循环依赖
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如a依赖于b,b依赖于c,c又依赖于a。如下图所示:
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。
对于构造器的循环依赖,spring 是无法解决的,只能抛出 beancurrentlyincreationexception 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖。
spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean spring 无法解决,直接抛出 beancurrentlyincreationexception 异常。
如何检测循环依赖
检测循环依赖相对比较容易,bean在创建的时候可以给该bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
解决循环依赖
我们先从加载 bean 最初始的方法 dogetbean()
开始。
在 dogetbean()
中,首先会根据 beanname 从单例 bean 缓存中获取,如果不为空则直接返回。
protected object getsingleton(string beanname, boolean allowearlyreference) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null && issingletoncurrentlyincreation(beanname)) { synchronized (this.singletonobjects) { singletonobject = this.earlysingletonobjects.get(beanname); if (singletonobject == null && allowearlyreference) { objectfactory<?> singletonfactory = this.singletonfactories.get(beanname); if (singletonfactory != null) { singletonobject = singletonfactory.getobject(); this.earlysingletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname); } } } } return singletonobject; }
这个方法主要是从三个缓存中获取,分别是:singletonobjects、earlysingletonobjects、singletonfactories,三者定义如下:
private final map<string, object> singletonobjects = new concurrenthashmap<>(256); private final map<string, objectfactory<?>> singletonfactories = new hashmap<>(16); private final map<string, object> earlysingletonobjects = new hashmap<>(16);
这三级缓存分别指:
(1)singletonfactories : 单例对象工厂的cache
(2)earlysingletonobjects :提前暴光的单例对象的cache
(3)singletonobjects:单例对象的cache
他们就是 spring 解决 singleton bean 的关键因素所在,我称他们为三级缓存,第一级为 singletonobjects,第二级为 earlysingletonobjects,第三级为 singletonfactories。这里我们可以通过 getsingleton()
看到他们是如何配合的,这分析该方法之前,提下其中的 issingletoncurrentlyincreation()
和 allowearlyreference
。
-
issingletoncurrentlyincreation()
:判断当前 singleton bean 是否处于创建中。bean 处于创建中也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 spring 解决 bean 循环依赖的理念相辅相成,因为 spring 解决 singleton bean 的核心就在于提前曝光 bean。 - allowearlyreference:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是是否允许从 singletonfactories 缓存中通过
getobject()
拿到对象,为什么会有这样一个字段呢?原因就在于 singletonfactories 才是 spring 解决 singleton bean 的诀窍所在,这个我们后续分析。
getsingleton()
整个过程如下:首先从一级缓存 singletonobjects 获取,如果没有且当前指定的 beanname 正在创建,就再从二级缓存中 earlysingletonobjects 获取,如果还是没有获取到且运行 singletonfactories 通过 getobject()
获取,则从三级缓存 singletonfactories 获取,如果获取到则,通过其 getobject()
获取对象,并将其加入到二级缓存 earlysingletonobjects 中 从三级缓存 singletonfactories 删除,如下:
singletonobject = singletonfactory.getobject(); this.earlysingletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname);
这样就从三级缓存升级到二级缓存了。
上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?一直往下跟会发现在 docreatebean()
( abstractautowirecapablebeanfactory ) 中,有这么一段代码:
boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences && issingletoncurrentlyincreation(beanname)); if (earlysingletonexposure) { if (logger.isdebugenabled()) { logger.debug("eagerly caching bean '" + beanname + "' to allow for resolving potential circular references"); } addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean)); }
也就是我们上一篇文章中讲的最后一部分,提前将创建好但还未进行属性赋值的的bean放入缓存中。
如果 earlysingletonexposure == true
的话,则调用 addsingletonfactory()
将他们添加到缓存中,但是一个 bean 要具备如下条件才会添加至缓存中:
- 单例
- 运行提前暴露 bean
- 当前 bean 正在创建中
addsingletonfactory()
代码如下:
protected void addsingletonfactory(string beanname, objectfactory<?> singletonfactory) { assert.notnull(singletonfactory, "singleton factory must not be null"); synchronized (this.singletonobjects) { if (!this.singletonobjects.containskey(beanname)) { this.singletonfactories.put(beanname, singletonfactory); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } } }
从这段代码我们可以看出 singletonfactories 这个三级缓存才是解决 spring bean 循环依赖的诀窍所在。同时这段代码发生在 createbeaninstance()
方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了,所以 spring 在这个时候选择将该对象提前曝光出来让大家认识认识。
介绍到这里我们发现三级缓存 singletonfactories 和 二级缓存 earlysingletonobjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 defaultsingletonbeanregistry 中可以发现这个 addsingleton()
方法,源码如下:
protected void addsingleton(string beanname, object singletonobject) { synchronized (this.singletonobjects) { this.singletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } }
添加至一级缓存,同时从二级、三级缓存中删除。这个方法在我们创建 bean 的链路中有哪个地方引用呢?其实在前面博客 lz 已经提到过了,在 dogetbean()
处理不同 scope 时,如果是 singleton,则调用 getsingleton()
,如下:
if (mbd.issingleton()) { sharedinstance = getsingleton(beanname, () -> { try { return createbean(beanname, mbd, args); } catch (beansexception ex) { // explicitly remove instance from singleton cache: it might have been put there // eagerly by the creation process, to allow for circular reference resolution. // also remove any beans that received a temporary reference to the bean. destroysingleton(beanname); throw ex; } }); bean = getobjectforbeaninstance(sharedinstance, name, beanname, mbd); }
public object getsingleton(string beanname, objectfactory<?> singletonfactory) { assert.notnull(beanname, "bean name must not be null"); synchronized (this.singletonobjects) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { //.... try { singletonobject = singletonfactory.getobject(); newsingleton = true; } //..... if (newsingleton) { addsingleton(beanname, singletonobject); } } return singletonobject; } }
至此,spring 关于 singleton bean 循环依赖已经分析完毕了。所以我们基本上可以确定 spring 解决循环依赖的方案了:spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 objectfactory 提前曝光(即加入到 singletonfactories 缓存中),这样一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 objectfactory 的 getobject()
获取了,也就是 getsingleton()
中的代码片段了。
到这里,关于 spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下就上面那个循环依赖 spring 解决的过程:首先 a 完成初始化第一步并将自己提前曝光出来(通过 objectfactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 b,此时就会去尝试 get(b),这个时候发现 b 还没有被创建出来,然后 b 就走创建流程,在 b 初始化的时候,同样发现自己依赖 c,c 也没有被创建出来,这个时候 c 又开始初始化进程,但是在初始化的过程中发现自己依赖 a,于是尝试 get(a),这个时候由于 a 已经添加至缓存中(一般都是添加至三级缓存 singletonfactories ),通过 objectfactory 提前曝光,所以可以通过 objectfactory.getobject()
拿到 a 对象,c 拿到 a 对象后顺利完成初始化,然后将自己添加到一级缓存中,回到 b ,b 也可以拿到 c 对象,完成初始化,a 可以顺利拿到 b 完成初始化。到这里整个链路就已经完成了初始化过程了。
上一篇: Python爬取租房数据实例,据说可以入门爬虫的小案例!
下一篇: 编程题常见输入格式处理方法
推荐阅读
-
spring源码深度解析— IOC 之 默认标签解析(上)
-
spring源码深度解析— IOC 之 循环依赖处理
-
spring源码深度解析— IOC 之 开启 bean 的加载
-
Spring源码之循环依赖
-
spring源码深度解析— IOC 之 默认标签解析(下)
-
Springboot源码分析之Spring循环依赖揭秘
-
spring源码深度解析— IOC 之 bean 的初始化
-
【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
-
【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
-
Spring5.0源码学习系列之浅谈循环依赖问题