首页 热点资讯 义务教育 高等教育 出国留学 考研考公
您的当前位置:首页正文

spring5源码深度解析-----IOC之循环依赖处理

2023-11-11 来源:华佗小知识
spring5源码深度解析-----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 singletonObjects = new ConcurrentHashMap<>(256);private final Map> singletonFactories = new HashMap<>(16);private final Map 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 完成初始化。到这⾥整个链路就已经完成了初始化过程了。

因篇幅问题不能全部显示,请点此查看更多更全内容