Spring的循环依赖问题与三级缓存

220 阅读4分钟

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操作

  • 把代理对象加入池中

那么循环依赖创建对象又是怎么样的呢?

image-20210606164625716

这样不断的类似递归式的调用就一直进行下去了。

3、怎么解决循环依赖

这里我们考虑添加缓存。

三级缓存的概念

一级缓存:SingletonObject——把刚完成生命周期的对象放入

二级缓存:EarlySingletonObject——把完成创建还没完成生命周期的对象放入

三级缓存:SingletonFactory——对象工厂,用来创建对象用的

通俗一点说,循环依赖之所以会不断的循环下去,就是因为它在不断的创建新对象。

其实我们根本就不需要它们一直创建,我们只需要拿到老师:学生,学生:老师就好了,根本用不着一层一层的继续下去。所以后面是null也没有关系。所以我们干脆就让A刚创建完空壳之后,可以先放一个这个对象的空值进入缓存,我们只要保证有这个对象就行了。

image-20210606170823569

这里因为如果存入一级缓存肯定是不行的,因为一级缓存要求的是对象创建完成后放入,很明显二级缓存更合适,因为二级缓存是没创建完就可以放入一个引用。而我们这个时候不需要它的值有多准确,只是需要能有个对象来中断对象的循环创建即可。

4、事情并没那么简单

这样好像是解决了我们问题,一切都皆大欢喜了,准备收工了,这个时候又出现了问题......

当我们创建SpringBean的时候,一旦用到了动态代理,上面的这些又不起作用了。

因为AOP采用了动态代理,如图,创建了A空壳,A要赋值B,没有B就会去创建一个B,如果我们采用的是代理对象,代理类和原始类根本就不是一个东西,那么赋值的时候找原来的对象根本找不到,只能不断的去创建新的代理对象。

image-20210606163140886

这就很难受了,我们该如何解决动态代理改变为代理对象的问题呢?

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去生成动态代理对象用。这样我们生成的代理对象就可以放入二级缓存,然后进行其他操作将最终的对象放入一级缓存即可。