大白话Spring循环依赖详解


Spring 三级缓存解析

首先需要注意,三级缓存即是三层缓存,只不过Spring框架在设计时对每一层缓存都进行了单一职责原则的划分。换句话说,三级缓存不仅仅是用于简单存储数据的,而是有明确的准入标准和设计概念:

一个类创建为一个对象的过程通常分为三个步骤:实例化属性填充初始化

例如,对于类 A,其中的字段 b 定义为 String b = "dacongming"。当执行 A a = new A(); 时:

  • 实例化:会先为这个对象分配内存,此时a这个引用的地址已经确定,但字段b的值为null
  • 属性填充:此时会为A类中的字段赋值,比如b的值会变为"dacongming"
  • 初始化:对a对象进行一些初始化处理,比如如果你实现了初始化方法,那么此时就会执行这个方法,这样一个对象就创建完毕了。

Spring 如何通过三级缓存解决循环依赖

Spring 解决循环依赖的三级缓存机制可能大家已经有所耳闻了:

  1. singletonObjects:一级缓存,存储已经初始化完成的单例Bean。
  2. earlySingletonObjects:二级缓存,存储尚未完成初始化的Bean。
  3. singletonFactories:三级缓存,存储提前暴露的单例工厂。二级缓存中存储的Bean就是从这个工厂中获取到的对象。

当从缓存中获取对象时,Spring 按照以下顺序进行查询:

  • 首先从一级缓存查询;
  • 如果未查到,再从二级缓存查询;
  • 如果二级缓存中也未查到,再从三级缓存查询;
  • 如果三级缓存中仍未找到,则创建该对象。
Spring 循环依赖的流程解析

以 A 和 B 两个类相互依赖为例,Spring 的解决流程如下图所示:

循环依赖图示

我们可以看到,A 对象是完整使用了三级缓存的。在 A 对象实例化完成后,它会主动将对应的BeanFactory放入三级缓存中。那么,为什么要将BeanFactory放入三级缓存,而不是直接将 A 的原始对象放入二级缓存呢?这是因为 A 可能需要生成代理对象。

在第2步中,BeanFactory其实是一个回调函数:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))

当你从BeanFactory中获取 A 的对象时,相当于触发了这个回调函数
上面的匿名函数()->getEarlyBeanReference(beanName, mbd, bean)就是回调函数,它的第三个入参bean是 A 的原始对象。这个函数的逻辑是,如果 A 需要生成代理对象,那么会对 A 的原始对象进行包装,并返回 A 的代理对象。

正常情况下,代理对象的创建是在原始对象初始化完毕之后进行的。但这里 A 的原始对象还未创建完成,就提前创建了 A 的代理对象。这是因为 A 被 B 引用了,而 A 后续需要以代理的方式存在于 Spring 上下文中,所以要提前创建 A 的代理对象并将其引用提供给 B,这样 B 在使用 a 属性时,使用的才是 A 的代理对象。

因此,A 的代理对象相当于提前暴露了,它内部包含了对 A 原始对象的引用。当 A 的原始对象初始化完毕时,A 的代理对象也就完成了初始化。

如果 A 不需要代理对象,那么将直接返回 A 的原始对象。无论返回的是代理对象还是原始对象,都会从三级缓存中移除,并放入二级缓存。因此,如果没有代理需求,完全可以跳过三级缓存,直接将原始对象放入二级缓存中,这样二级缓存中存储的都是未完全初始化的对象,而一级缓存则存储的是已经彻底初始化完成、可以直接使用的对象。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值