
我们之前文章讲Bean的创建过程中,提到了如果当前bean依赖的其它bean,就会调用getBean()方法把依赖的bean先创建出来
可是你有没有想过,如果当前Bean依赖于另一个Bean,而另一个Bean又就形成了依赖于当前Bean,或者当前Bean依赖于自己,这样依赖关系形成了一个闭环。这就是我们这篇文章将要说的Bean的循环依赖问题!
首先我们要明确Spring不是能够解决所有情况下的循环依赖问题的,只能解决:
IOC容器默认的单例Singleton的场景,并且使用setter方法注入,是支持循环依赖的,不会报错。看下面A和B产生循环依赖的例子。
但是原型Prototype的场景是不支持循环依赖的,会报错。
再来说说全是构造器注入的方式,下面的代码,A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是**无法被解决的。**同样也会报错!
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
那么Spring到底是如何解决Bean的循环依赖的问题?实际上是利用了三级缓存来解决的!
只有单例的bean会通过三级缓存提前暴露来解决循环依赖问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中
我们之前说过在创建Bean的时候,首先调用getBean()方法
getBean()方法会调用getSingleton()方法先尝试从缓存中获取这个Bean
三级缓存的核心就是getSingleton()方法!
整个缓存分为三级(本质是三个Map集合):
(1)getSingleton()方法首先会在一级缓存中查找,如果没有就返回null
(2)在一级缓存中没有查找到这个Bean,说明这个Bean还没有被完整的创建出来,此时会调用getSingleton()参数为ObjectFactory重载方法
(3)重载个这个方法会先将beanName放入到singletonsCurrentlyInCreation这个集合中,标志着这个单例Bean正在创建
(4)标记完这个Bean正在创建以后,就开始调用createBean()方法来创建这个Bean。这个createBean()方法我们已经很熟悉了,在之前的创建Bean的过程那篇文章中已经详细讲过了。再实例化这个Bean之前,BeanPostProcessor后置处理器会尝试返回这个Bean的代理对象,也就是说,开启了AOP代理之后,当前Bean注入并不是依赖的Bean,而是注入的这个Bean的代理对象。如果返回的代理对象不为null,就直接返回这个代理对象来替代想要创建的这个Bean,如果返回的代理对象为null,那么就会继续执行剩下的流程,对这个将要创建的Bean进行实例化、属性填充、初始化。
(5)如果没有直接返回代理对象,就会调用doCreateBean()方法,
调用createBeanInstance()方法先利用反射机制或者构造器实例化这个Bean
只有当这个实例化的Bean,是正在创建中的并且是单例Bean,而且允许提前暴露,才会把这个Bean添加到三级缓存中。
(6)实例化完成并且添加到了三级缓存之后,就开始对这个Bean进行属性填充。如果在属性填充的过程中发现当前Bean依赖于其它Bean,就会查找这个依赖的Bean,和之前说的流程也是一样的,先从缓存中查找,如果没有找到,就会创建这个依赖的的Bean,并填充属性。
(7)如果在给依赖的Bean填充属性的发现它也依赖于上一个Bean,造成了循环依赖的情况。**此时两个互相依赖的Bean都是实例化完了,但是还没有进行属性填充,也没有进行初始化。**当前Bean会调用getSingleton()方法,依次从一级、二级、三级缓存中去找依赖的上一个Bean。
我们说上一个Bean之前已经被实例化并且添加到了三级缓存中,于是当前Bean就会从三级缓存中获取到上一个BeanFactory的创建工厂,通过beanFactory就可以得到上一个实例化的Bean。由于通过工厂已经实例化出来一个Bean,所以把上一个Bean放到二级缓存中,同时从三级缓存中删除,这样之后其它bean如果也依赖于这个Bean的话,就可以直接从二级缓存中获取到了。
(8)当前Bean得到所依赖的上一个Bean实例之后,就可以继续进行属性填充,并且进行初始化了
(9)这样当前Bean被创建出来之后放到了一级缓存中,此时上一个Bean还是处于创建中的状态,那么回过头来继续创建这个处于创建中状态的Bean,由于它所依赖的Bean已经创建出来并且放到了一级缓存中,所以它可以直接从一级缓存中直接获取到这个依赖的Bean,然后继续完成属性填充和初始化。最后也会把自己放到一级缓存中。
这样多个Bean之间的循环依赖的问题就解决了,实际上依靠的是Bean的“中间态”这个概念,指的就是已经实例化但是没有进行初始化的Bean。相当于一个Bean的半成品。但是如果是使用构造器注入的bean,不能解决循环依赖问题。因为实例化过程就是通过构造器创建的,Bean如果连实例化都没有完成,也就不能不可能放到二级缓存中提前暴露出来。
到这你是不是觉得三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个Bean引用不行吗?
实际上这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理。
也就是说,在没有AOP代理的时候,三级缓存实际上直接返回的是实例化阶段的Bean对象,并存放到二级缓存中,依赖注入的也是这个实例化Bean对象。但是在有AOP代理时,三级缓存中的Bean工厂才会创建一个代理对象并返回,并将代理对象存放二级缓存中。并且在Bean对象初始化完成之后,通过禁用三级缓存,从二级缓存中获取代理对象来替换Bean本身,并存放到一级缓存中。
所以简单总结一下源码的实现:
Spring使用了三级缓存来解决多个Bean循环依赖的问题,这三个缓存实际上是三个Map集合,一级缓存用来保存已经创建好的单例Bean,二级缓存用来提前暴露Bean,三级缓存用来提前暴露Bean工厂。
假设A、B产生了循环依赖,那么在实例化A的时候就会将它放入到三级缓存中,接着在填充属性时,发现依赖了B,那么就会执行同样的流程,将B实例化后也放入到三级缓存中。当B进行属性填充的时候,又发现B也依赖于A,这时候就会从三级缓冲中查找到提前暴露出来的A工厂,通过A工厂得到实例化的A,然后把A放入到二级缓存中,同时从三级缓存中移除掉。B得到A之后就会继续进行属性填充以及初始化工作,当B被完整的创建出来之后就会放到一级缓存中,并出清掉二级缓存,此时B的创建流程结束。接下来就会继续执行处于正在创建中状态的A,由于A依赖的B已经创建好放在一级缓存中了,此时A在属性填充时可以直接从一级缓存中获取到B,完成属性填充以及初始化的工作。这样A、B就都创建了出来。