Spring如何使用三级缓存解决循环依赖
作者:夏诗曼CharmaineXia
前言
1. 什么是循环依赖
类A需要类B,我们就叫做类A依赖类B。简单说就是依赖,或者和别的类相互依赖
1.1 互相依赖
1.2 递归依赖
2. Sping中循环依赖有什么问题?
在Spring中,循环依赖指的是两个或多个Bean之间相互依赖形成的循环引用关系。具体来说,当Bean A依赖于Bean B,而Bean B又依赖于Bean A时,就形成了循环依赖。
只有单例的 Bean 才存在循环依赖的情况,原型(Prototype)情况下,Spring 会直接抛出异常。
循环依赖可能导致以下问题:
- 无法完成Bean的初始化:当存在循环依赖时,Spring容器无法确定先初始化哪个Bean,因为它们相互依赖,而且都需要对方完成初始化才能继续。这可能导致Bean的初始化过程无法完成,从而引发异常。
- 无限递归调用:当存在循环依赖时,Spring容器可能会陷入无限递归的调用中,导致系统堆栈溢出。这是因为每次获取Bean时,Spring容器需要检测循环依赖并创建实例,但由于循环依赖的存在,无法正常创建实例,从而导致无限递归调用。
为了解决循环依赖问题,Spring使用了三级缓存和"提前暴露"的策略
3. 什么是三级缓存
对于创建单例Bean,Spring创建了三个容器来存储不同时期的对象:
- ⼀级缓存 : Map<String,Object> singletonObjects,单例池,⽤于保存实例化、属性赋值(注⼊)、初始化完成的 bean 实例
- ⼆级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,⽤于保存实例化完成的 bean 实例
- 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象⼯⼚,⽤于保存 bean 创建⼯⼚,以便于后⾯扩展有机会创建代理对象
4. Spring 可以解决哪些情况的循环依赖?
Spring 不⽀持基于构造器注⼊的循环依赖,假如 AB 循环依赖,其中一方使用构造器注入,也是不支持的。
为什么呢?下面二级缓存会说明白。
二级缓存作用——普通循环依赖
只用一级缓存和二级缓存就能解决普通bean的循环依赖。
先回顾Bean对象创建的步骤:
二级缓存:又称 半成品池 存放的是实例化,但未属性赋值和初始化的Bean对象。一级缓存:又称 单例池 存放的是完成属性赋值和初始化的成品Bean,可以直接使用了。
那么二级缓存是如何解决普通Bean的循环依赖的?
实操环节
类A依赖类B,类B依赖类A。
1. 实例化类A对象
对象a被实例化出来,会被放到半成品池
中,当进行下一步属性赋值时,发现依赖了类B,所以开始创建对象b。
如果对象b是在a的构造函数中注入的,那就完了,a无法实例化,得先去实例化b,若是b也是构造函数中注入的a,那就无解了。
public class A { private B b; @Inject public A(B b) { this.b = b; } }
2. 实例化类B对象
对象b被实例化出来,也被放到半成品池
中。下一步是属性赋值,发现依赖了类A,会依次从⼀级到三级缓存查询类A对象,最终会在半成品池
中找到对象a,成功将它赋值到自己的属性中。
3. B对象完成创建
对象b在经过填充属性、初始化后会从半成品池
里挪到单例池
中,可以直接使用了。
4.继续创建A对象
这时候回过头来继续对象a的属性注入,把对象b赋值给自己的属性后再经过初始化,对象a也从半成品池
挪到单例池
,对象a创建完成,对象b也跟着创建完成。
三级缓存作用——aop循环依赖
二级缓存仍然存在问题,它无法解决AOP代理问题。
1. AOP代理问题
AOP(面向切面)简单的说,在不改源码的情况下在原始方法前后加一些代码。
它的底层是靠动态代理实现的,即生成一个代理类,重新原始方法,真正使用的时候其实用的是代理类对象,而非原始类对象。
既然用的是代理类对象,单例池中应该存放的就该是代理类对象。
二级缓存无法解决生成代理对象的问题,因为创建对象的过程很复杂,每个代理类都需要一个工厂来专门生成代理类对象。
三级缓存又叫工厂池
,就是用来存放生成代理类对象工厂的。
2. 何时生成代理对象
AOP是靠AOP处理器实现的,处理器有两个生成代理对象的方式。
前置处理:在Bean对象初始化后后置处理:再Bean对象实例化前
Spring为了解决使用AOP的对象循环依赖的问题,使用了这两种处理方式。
实操环节
类A依赖类B,类B依赖类A,类A使用了AOP。
1.实例化类A对象
首先把创建类A代理对象的工厂对象放到工厂池
中。
类A实例化对象时发现依赖了类B,使用前置处理器生成A的代理对象,放在半成品池子中。
2. 实例化类B对象
对象b找到对象a,成功属性赋值,再经过初始化成功创建,挪到单例池中待用。
如果类B也使用了AOP,那么对象b在初始化后,会通过后置处理器生成动态代理对象,放到单例池中。
3.继续创建A对象
对象b创建完,接着回头创建对象a,这个过程就很顺利了。
当对象a完成初始化以后,因为已经是代理对象,就不会在走后置处理。
对象a、b都创建完,会清空在二级、三级池中的相关数据,最终只在单例池中保留一份对象。
总结
尽管Spring提供了解决循环依赖的机制,但循环依赖本身是一个设计上的问题,可能导致代码的可读性和可维护性下降。因此,在编写代码时,应尽量避免出现循环依赖的情况。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。