这个问题或者换个问法:Spring是如何解决循环依赖的?答案即是Spring的三级缓存
什么是循环依赖?
简单说,就是A对象依赖B对象,B对象⼜依赖A对象,类似的代码如下:
其他还有很多种⽅式,如A依赖B,B依赖C,C依赖A,或是A依赖A⾃⼰,只要产⽣了依赖关系的闭环,即造成了循环依赖。
那么,循环依赖会引发什么问题呢?理解这个问题先得理解Bean的⽣命周期
Bean的⽣命周期
1. 启动容器:加载Bean
2. 实例化Bean对象
3. 依赖注⼊:装配Bean的属性
4. 初始化Bean:执⾏aware接⼝⽅法、预初始化⽅法、初始化⽅法、后初始化⽅法
5. 关闭容器:销毁Bean
在以上第四个步骤执⾏完毕,才算⼀个初始化完成的Bean,也即Spring容器中完整的Bean对象。
循环依赖的问题
Spring容器保存Bean的⽅式,是采取缓存的⽅式:使⽤ Map<String, Object> 的结构,key为
Bean的名称,value为Bean对象。需要使⽤时直接从缓存获取
假如A、B互相依赖(循环依赖):
1. 容器中没有A对象,实例化A对象
2. 装配A中的B对象,发现B在容器中没有,需要先实例化B
3. 实例化B对象
4. 装配B中的A对象,发现A在容器中没有,需要先实例化A
5. 重复第⼀个步骤
这就套娃了, 你猜是先 StackOverflow 还是 OutOfMemory ?
Spring怕你不好猜,就先抛出了 BeanCurrentlyInCreationException
- Bean会依赖某些注⼊的Bean来完成初始化⼯作
- 由于Spring⽀持构造⽅法注⼊,属性/Setter注⼊的⽅式,所以不能简单的先把所有对象全部实例化,放到缓存中,再全部执⾏初始化。原因很简单,此时所有对象的引⽤都可以获取到,但属性都是null,执⾏初始化甚⾄构造⽅法都可能出现空指针异常。
那么我们说Spring能解决循环依赖,也不是所有的情况都可以解决,只有以下情况才⽀持
Spring⽀持的循环依赖
在Spring容器中注册循环依赖的Bean,必须是单例模式,且依赖注⼊的⽅式为属性注⼊。
原型模式及构造⽅法注⼊的⽅式,不⽀持循环依赖。以下为说明:
- 原型模式(prototype)的Bean:原因很好理解,创建新的A时,发现要注⼊原型字段B,⼜创建新的B发现要注⼊原型字段A...还是典型的套娃⾏为...
- 基于构造器的循环依赖,就更不⽤说了,官⽅⽂档都摊牌了,你想让构造器注⼊⽀持循环依赖,是不存在的,不如把代码改了
那么默认单例的属性注⼊场景,Spring是如何⽀持循环依赖的?
Spring解决循环依赖
Spring是使⽤三级缓存的机制来解决循环依赖问题,以下为三级缓存的定义:
三级缓存的源码⻅ DefaultSingletonBeanRegistry :
以下是部分说明:
- 三级缓存singletonFactories中保存的是ObjectFactory对象(Bean⼯⼚),其中包含了BeanName,Bean对象,RootBeanDefinition,该⼯⼚可以⽣成Bean对象。
- 由于Bean可能被代理,此时注⼊到其他Bean属性中的也应该是代理Bean。
单例模式的A、B循环依赖执⾏流程如下:
为什么要使⽤三级缓存?
依照以上三级缓存的流程,其实使⽤⼆级缓存也能满⾜循环依赖的注⼊:
- 普通的IoC容器使⽤⼀级缓存即可,但⽆法解决循环依赖问题。
- 解决循环依赖问题:使⽤⼆级缓存即可。⼀级缓存保存完整Bean,⼆级缓存保存提前曝光的不完整的Bean。
- 需要AOP代理Bean时,有两种实现思路:(1)再加⼀级缓存(2)只使⽤⼆级缓存,其中⼆级缓存保存Bean的代理对象,代理对象中引⽤不完整的原始对象即可。
- Spring使⽤三级缓存保存ObjectFactory即Bean⼯⼚,在代码的层次设计及扩展性上都会更好。ObjectFactory内部可以根据 SmartInstantiationAwareBeanPostProcessor 这样的后置处理器获取提前曝光的对象