什么是循环依赖
类A有个字段需要注入类B,类B有个字段需要注入类C,类C有个字段需要注入类A,它们之间的依赖关系形成了一个循环。
Spring初始化完一个对象之后会把实例放入单例池(singletonObjects)中,也就是一级缓存。
我们看看一级缓存下循环依赖造成的结果。
1、new A(),发现需要B对象
2、去singletonObjects找B对象,没有找到
3、new B(),发现需要C对象
4、去singletonObjects找C对象,没有找到
5、new C(),发现需要A对象
6、去singletonObjects找A对象,没有找到
然后回到第一步,发现这个是死循环,最后会因为堆栈溢出而结束运行,所以一级缓存无法解决循环依赖问题。
循环依赖怎么解决
再新增一级缓存,把对象提前暴露出来(对象实例化但未初始化),新增一级的缓存叫earlySingletonObjects。
1、new A(),放入earlySingletonObjects, 发现需要B对象
2、去singletonObjects找B对象,没有找到
3、去earlySingletonObjects找B对象,没有找到
4、new B(),放入earlySingletonObjects,发现需要C对象
5、去singletonObjects找C对象,没有找到
6、去earlySingletonObjects找C对象,没有找到
7、new C(),放入earlySingletonObjects,发现需要A对象
8、去singletonObjects找A对象,没有找到
9、去earlySingletonObjects找A对象,找到了
10、给C对象的字段注入A对象,C对象初始化完成,把C对象放入singletonObjects
11、返回B对象的创建中,在singletonObjects找到C对象
12、给B对象的字段注入C对象,B对象初始化完成,把B对象放入singletonObjects
13、返回A对象的创建中,在singletonObjects找到B对象
14、给A对象的字段注入B对象,A对象初始化完成,把A对象放入singletonObjects
15、END
二级缓存就可以解决循坏依赖的问题了。
Spring为什么使用三级缓存解决循环依赖
但Spring为什么还用三级缓存去解决呢?
因为二级缓存虽然解决了循环依赖,但是还有AOP的问题没有解决。
如果一个Bean需要被代理,那么它在初始化完成之后原始Bean会被代理Bean替换掉放入IOC容器,其他Bean注入的也是代理对象,如果使用上述二级缓存的流程,那么其他对象注入的都是原始对象,AOP就失效了。
如果使用上述的二级缓存,还有一个方法可以解决AOP失效,就是提前AOP,这样earlySingletonObjects放的就是代理对象了。但是有个问题,你在实例化A的时候,你根本不知道A产生了循环依赖,你只有到了实例化C,为C注入属性的时候才能知道A产生了循环依赖。如果我们可以提前知道A没有循环依赖,我们就可以不用提前AOP(没有循环依赖也就是不用提前暴露bean,其他bean也就不会提前拿到并注入),A走正常流程就可以了。现在问题是我们不能提前知道循环依赖,所以只能无差别的对Bean提前进行AOP。
这是一个解决方案,但不是一个好方案,因为AOP的流程被提前了,相当于Bean的生命周期改变了,Bean的生命周期在整个Spring框架中是核心、底层的东西。这个方案相当于架构为问题让步、妥协,我们知道架构这东西一旦定下来就很少再改了,除非迫不得已,否则底层的东西不会向问题让步,最不济也是选一个比较low一点、折中一点的解决方案。
这个问题还不至于底层让步,现在关键在于如果我们能知道一个bean出现循环依赖了,我们就提前AOP,没有出现循环依赖的,正常走流程,这样不至于所以bean都提前AOP,没有对原先的架构造成破坏,也做了一点妥协,部分bean需要提前AOP,是一个比较折中的方案。
所以加了第三级缓存singletonFactories,作用就是解决AOP问题,在earlySingletonObjects之前加一层缓冲,因为我也不知道现在这个bean是不是循环依赖了,所以不能过早暴露出来。
对应上述例子,
实例化A,放入singletonFactories(不知道A是循环依赖对象),...
...
...
...
实例化C,需要注入A
singletonObjects找A没有
earlySingletonObjects找A没有
singletonFactories找A,找到了
调用singletonFactories的get方法,此时在这里就可以知道A是否循环依赖了,循环依赖了就提前AOP返回代理对象,没有循环依赖就返回原始对象。
remove singletonFactories的A
add earlySingletonObjects A
...
...
...
这样三级缓存的条件下,解决了循环依赖(部分场景)+AOP的问题。