1、什么是循环依赖
刚开始学Spring的时候,对循环依赖和缓存感觉很陌生,总是觉得晦涩难懂,但其实这个问题并不复杂。
比如教师类,教师(Teacher)都会有学生(Student),那么学生可以作为一个老师的属性;同理学生亦可以有老师这样一个属性。所以学生类也可以有老师这个属性。
public class Teacher{
public Student student;
}
public class Student{
public Teacher teacher;
}
这样就很麻烦了,当我们创建一个Teacher 对象的时候,要给Student属性赋值,这个时候我们的Student对象也要赋值一个Teacher的属性......
这样两个类,你中有我我中有你。
最终实现永动机。
2、SpringBean的生命周期
我们采用Spring出现了循环依赖的问题。而且Spring有自动创建bean的功能,不弄明白Spring到底是怎么创建bean的,我们就没办法去解决循环依赖的问题。
这里说的比较简单,仅仅说了对目前有用的一些步骤:
一个正常的SpringBean的生命周期:
-
按照要求生成容器
-
创建对象A(createBeanInstance)
-
对A属性进行赋值,它会先到池中找 是否有这个对象,有的话就赋值,没有的话就直接创建一个(populateBean)
-
AOP操作
-
把代理对象加入池中
那么循环依赖创建对象又是怎么样的呢?
这样不断的类似递归式的调用就一直进行下去了。
3、怎么解决循环依赖
这里我们考虑添加缓存。
三级缓存的概念
一级缓存:SingletonObject——把刚完成生命周期的对象放入
二级缓存:EarlySingletonObject——把完成创建还没完成生命周期的对象放入
三级缓存:SingletonFactory——对象工厂,用来创建对象用的
通俗一点说,循环依赖之所以会不断的循环下去,就是因为它在不断的创建新对象。
其实我们根本就不需要它们一直创建,我们只需要拿到老师:学生,学生:老师就好了,根本用不着一层一层的继续下去。所以后面是null也没有关系。所以我们干脆就让A刚创建完空壳之后,可以先放一个这个对象的空值进入缓存,我们只要保证有这个对象就行了。
这里因为如果存入一级缓存肯定是不行的,因为一级缓存要求的是对象创建完成后放入,很明显二级缓存更合适,因为二级缓存是没创建完就可以放入一个引用。而我们这个时候不需要它的值有多准确,只是需要能有个对象来中断对象的循环创建即可。
4、事情并没那么简单
这样好像是解决了我们问题,一切都皆大欢喜了,准备收工了,这个时候又出现了问题......
当我们创建SpringBean的时候,一旦用到了动态代理,上面的这些又不起作用了。
因为AOP采用了动态代理,如图,创建了A空壳,A要赋值B,没有B就会去创建一个B,如果我们采用的是代理对象,代理类和原始类根本就不是一个东西,那么赋值的时候找原来的对象根本找不到,只能不断的去创建新的代理对象。
这就很难受了,我们该如何解决动态代理改变为代理对象的问题呢?
5、三级缓存解决循环依赖问题
Spring是这样解决问题的。
- 先添加了一个属性creatingAdd("创建的对象A");
- 实例化得到A原始对象---->放入三级缓存
- 给A属性的B赋值---->从单例池中找B,找不到---->创建B的实例
- 实例化B原始对象
- 给A属性赋值---->单例池当中找---->找不到,去查creatingAdd("A")---->发现A出现了循环依赖---->去二级缓存中找---->去三级缓存中找---->拿到A原始对象---->进行AOP---->生成A的代理对象---->放入二级缓存
- 给其他属性赋值
- 其他工作
- 放入单例池
- 给其他属性赋值
- 其他工作
- 从二级缓存中拿到A对象
- 放入单例池
- 移除A的creatingAdd("创建的对象A")属性
这里我们先判断是否出现了循环依赖的现象,就是通过添加一个creatingAdd来看看A的属性B,它是否又加上了属性A,如果是的话,那就出现了循环依赖。
我们最终的对象还是会被放入单例池当中,也就是一级缓存。我们创建A的原始对象这次放入三级缓存,Spring发现了循环依赖现象,会去二级缓存和三级缓存找,主要是拿到这个原始对象,target去生成动态代理对象用。这样我们生成的代理对象就可以放入二级缓存,然后进行其他操作将最终的对象放入一级缓存即可。