要谈Spring循环依赖,首先要知道Spring何时进行依赖注入。在以前的文章中我们有提到,IoC容器初始化时,要经历BeanDefinition的Resource定位,BeanDefinition的载入解析以及BeanDefinition在IoC容器中的注册,经过上述过程后,IoC容器的初始化就完成了,里面的BeanDefinition也有了,然后才发生依赖注入。而循环依赖就是在依赖注入过程中发生的A依赖于B而B有依赖于A的现象。
接下来再说Spring中发生循环依赖会怎样。
spring中的循环依赖有三种情况:
- 构造器循环依赖:spring处理不了,抛出异常BeanCurrentlylnCreationException
- 单例模式下的setter循环依赖:通过三级缓存处理。
- 非单例循环依赖:无法处理。
构造器循环依赖:
将正在创建的bean记录在缓存中,如果一个bean在创建过程中发现自己已经被记录了,则抛出BeanCurrentlylnCreationException异常。
setter循环依赖:
Spring为了解决单例的循环依赖问题,使用了三级缓存。
1 | /** Cache of singleton objects: bean name –> bean instance */ |
这三级缓存的作用分别是:
singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
singletonObjects:完成初始化的单例对象的cache(一级缓存)
我们在创建bean的时候,会首先从cache中获取这个bean,这个缓存就是sigletonObjects。主要的调用方法是:
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:
1 | public interface ObjectFactory<T> { |
这个接口在AbstractBeanFactory里实现,并在核心方法doCreateBean()引用下面的方法:
1 | protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
这段代码发生在createBeanInstance之后,populateBean()之前,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
检测循环依赖的过程如下:
A 创建过程中需要 B,于是 A 将自己放到三级缓里面 ,去实例化 B
B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了!
- 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
- B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面
如此一来便解决了循环依赖的问题
所以总结以下,解决setter循环依赖的方法就是在循环依赖之前给他设置了阶梯,或者说分了层,使得那种没有被完全初始化好的对象可以作为被依赖去完成依赖对象的初始化(也就是提前曝光)。
spring单例对象初始化经过以下过程:
- createBeanInstance: 调用构造方法对对象进行实例化。
- populateBean: 属性填充
- initializeBean: 调用xml中的init方法
所以循环依赖发生在第一二步。