目录标题
- 1、什么是循环依赖
- 2、解决循环依赖的原理
- 3、Spring通过三级缓存解决循环依赖
- 4、为什么要使用三级缓存而不是二级缓存?
- 5、三级缓存中存放的是lambda表达式而不是一个半成品对象
1、什么是循环依赖
众所周知,Spring的容器中管理整个体系的bean对象。每个bean对象是java中的一个实例化的完整对象。一个完整对象就包含属性和方法,一个对象中的属性可以是另外一个对象的引用。所以在这时就会出现A对象的属性是B,B的属性如果是A的话,则两个对象相关依赖,都无法完成实例化的属性赋值。
2、解决循环依赖的原理
在传统的获取对象的方式是通过new一个对象,并将各种属性值放入对象中,等一切初始化工作完成之后,将这个完整的对象返回给使用者。如果在设置属性时,值对象为空时,会抛出空指针问题,则创建失败。
Spring则是将整个bean的创建进行分步进行,并将创建的半成品bean对象进行缓存,等需要的属性对象完成创建之后,在回头完善这个半成品bean。如果在创建属性对象时,出现循环依赖的现象时,将半成品对象提供给属性对象,则属性对象能够完整创建,就解决了循环依赖的问题。
3、Spring通过三级缓存解决循环依赖
例如:A对象的属性是B对象,B对象的属性是A对象。
三级缓存分别是
- 一级缓存singletonObjects
- 初始大小为256
- 存放完整的bean对象
- 二级缓存earlySingletonObjects
- 初始大小为16
- 存放半成品的bean对象
- 三级缓存singletonFactories
- 初始大小为16
- 存放singletonFactorie
- singletonFactorie是一个函数接口,通过调用getObject方法会触发调用getEarlyBeanReference方法来获取一个半成品的bean对象。
从缓存的初始大小可以看出一级缓存才是存放bean的空间,二级三级缓存时为了解决一些特殊情况设置的。
Spring的bean创建过程可以分实例化对象、设置属性、后置初始化。
- 首先A对象在实例化之后会创建A的原始对象,此时会将一个lambda表达式放入三级缓存中(() -> getEarlyBeanReference(beanName, mbd,bean)),然后进行下一步设置属性,此时B对象还没有创建,开始创建B对象,此时A对象的设置属性和初始化步骤还没有完成。
- 在创建B的过程中,实例化对象之后也会讲原始对象通过三级缓存暴露出去。在B对象设置属性时,需要通过缓存中查找A对象,从一级缓存中开始查找,在三级缓存中找到A对象暴露出的singletonFactorie对象。
- B对象成功设置对象之后,继续后续操作就可以创建一个完整的B对象,并将B对象放入一级缓存。在B对象创建完成之后,继续A对象的设置属性操作,设置完属性之后,A对象完成创建之后也放入一级缓存中。
- 到此循环依赖解决
4、为什么要使用三级缓存而不是二级缓存?
因为A对象可能会存在代理对象的情况,如果A对象的代码中存在AOP的逻辑,则通过getEarlyBeanReference获取到的是A的代理对象。并且多次调用getEarlyBeanReference中的getObject会获取多个A的代理对象,所以如果只是二级缓存,会获取多个A的半成品代理对象违背了单例原则。
如果A没有代理的情况,只需要二级缓存就可以解决循环依赖。
5、三级缓存中存放的是lambda表达式而不是一个半成品对象
首先在现实情况下A对象并不是一定会出现循环以来的。A会在创建完成之后,将三级缓存中的缓存删除。
如果A对象需要实现代理,但是在bean的生命周期内,代理的操作是在A设置完属性之后,通过后续的beanPost操作实现的,如果在创建A的过程中就创建一个A对象的半成品代理对象是不符合Bean生命周期的设计。
所以缓存中存放的是lambda表达式,只有在发生循环依赖的情况下,并且被代理的时候,才会通过getObject来获取半成品代理对象。(兼容多种情况)