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


先说结论

Spring中的循环依赖问题可以通过两个方法解决:

  1. 给对应的Bean添加@lazy注解
  2. 使用二级缓存即可。

而三级缓存的作用一方面是为了处理循环依赖问题,另一方面是为了解决AOP代理对象的重复创建问题(违背Spring的Bean单例原则)。

什么是循环依赖?

具体来讲,就是指两个或多个模块、类、组件之间相互依赖,形成一个依赖闭环。导致的无法确定加载或初始化。
示例代码:

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

//或者自己依赖自己
@Service
public class A {
    @Autowired
    private A a;
}
// 或者更多类之间的依赖
// 例如:A依赖B   B依赖C  C依赖A

Bean的生命周期核心步骤为4步(具体细节请看另一篇帖子:待补充):

  1. 实例化Bean
    a、Bean的实例化是通过反射机制来创建的,Spring根据@Component、@Bean 或者 XML中的<bean>元素来确定要创建的Bean。
    b、实例化之后是完成了A对象的构造,里面的成员变量都是默认值。
  2. 属性注入
    属性注入就是将属性值注入到Bean的字段中,可能是构造函数注入、setter方法注入等等。
    也就是在这一步可能出现循环依赖的问题。
  3. 初始化Bean
    调用初始化方法,堆Bean进行初始化。
  4. 销毁Bean
    回收Bean对象。

二级缓存解决循环依赖问题

如果说只考虑循环依赖问题,那么二级缓存完全足够解决,流程如下:(例子:Bean A 依赖 Bean B,而Bean B 又依赖 Bean A)

  1. 首先实例化 Bean A( A a = new A() )
  2. 将 A 放入二级缓存(属性为赋值,a是一个半成品)
  3. 给A进行属性注入(发现需要Bean B)---->进入 Bean B的生命周期
    3.1 实例化 Bean B(B b = new B())
    3.2 给B进行属性注入(发现需要Bean A)
    3.3 去二级缓存中找Bean A(半成品a),赋值b.a = a
    3.4 初始化 b —>(初始化完成后将b放入一级缓存,完整的b)
  4. 继续给A进行属性注入,一级缓存中存在b,a.b = b
  5. 初始化 a,(这是a也是完整的,b中注入的a是其引用,所以b也变成完整的了)

三级缓存

二级缓存中存在的代理对象重复创建问题

如果是二级缓存,可能会这样实现:直接在二级缓存中加一个判断逻辑,当从二级缓存中获取这个对象的时候,判断一下这个对象是否需要被代理,如果需要,就生成一个代理对象返回就可以了。如果这个判断逻辑只执行一次没有问题,但是执行两次就会返回两个不同的代理对象,这就违背了Spring的单例原则。

三级代理解决代理对象重复创建问题

如果时三级缓存,那么再三级缓存加判断逻辑,通过工厂创建一次代理对象之后,就会将这个代理对象加入二级缓存,后面再尝试获取代理对象的时候就不需要再次执行判断逻辑去生成一个新的对象,而是直接从二级缓存中获取上次生成的那个代理对象就行了。
三级缓存的设计并不是为了保证生命周期,而是为了避免代理对象的重复创建。

具体流程分析如下:
首先三级缓存分别是什么?作用是什么?

// 1. 一级缓存:存储已完成实例化、属性填充、初始化的完整单例 Bean(成品)
Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

// 2. 二级缓存:存储已实例化但未完成初始化的 Bean 的早期引用(可能是原始对象或代理对象)
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

// 3. 三级缓存:存储 Bean 工厂(ObjectFactory),用于生成或返回 Bean 的早期引用
Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();

(以 A依赖B,B依赖A为例)

  1. 首先创建 Bean a
    1.1 对 a 进行实例化(调用构造方法),生成原始对象(未进行属性注入)
    1.2 将 a 的工厂对象(ObjectFactory)存入三级缓存(singletonFactories)
  2. 对 a 进行属性注入,发现依赖 Bean b,查找 b:
    2.1 首先在一级缓存中查找是否存在完整的Bean b,存在则返回,不存在进行下一步
    2.2 看对应的Bean是否在创建中:
    ·····若正在创建中:则会去二级索引中查找Bean,找到则返回,否则去三级缓存通过BeanName查找对应的工厂,存在工厂则通过工厂创建Bean,否则返回null,执行步骤2.3
    ·····若不在创建中:直接返回null,执行步骤2.3
    2.3 由于没有找到Bean b,则进入 b 的生命周期,实例化 b,生成原始对象
    2.4 将 b 的工厂对象放入三级缓存
    2.5 对 b 进行属性注入,发现依赖 a
    2.5.1 在一级缓存中查找 Bean a,没找到进行下一步
    2.5.2 看 a 是否在创建中 ----> 正在创建中,则通过 a 的 BeanName 在三级缓存中查找他的工厂对象,调用其 getObject() 方法,得到之前创建的未完成初始化的 a,若 a 需要代理,则生成代理对象;否则返回原始对象
    2.5.3 将 a 的原始对象(或代理对象)存入二级缓存,并移除三级缓存中的 a 的工厂。
    2.5.4 将 a 的对象 注入到 b 中,继续完成 b 的其他属性注入
    2.6 b 属性注入完成,进行 b 的初始化
    2.7 初始化完成 b 之后移除二、三及缓存中的 b
  3. 继续进行 a 的属性注入,完成后 进行初始化,初始化完成后,移除二级缓存中的 a

整体流程图:
待补充

参考博客:spring如何解决循环依赖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值