开局一张图,今天带你们搞清楚Spring循环依赖。

首先看一个启动异常

应该由不少同学遇到过这种情况,很荣幸,遇到循环依赖了。SpringBoot2.6.x默认禁用循环依赖,所以需要添加如下配置解决。

可能你的问题到此就解决了,那么spring是怎么解决循环依赖的呢?我来解释一下。

循环依赖常见情况:

1. 构造方法循环依赖(spring无法解决)

假设有两个类,A和B,它们相互依赖。A类的构造方法需要一个B类的实例,而B类的构造方法需要一个A类的实例。这是一个典型的循环依赖问题。

下面是示例代码:

在这个例子中,当Spring尝试创建A和B时,它们之间的循环依赖将导致创建失败。这种构造方法的循环依赖解决起来有一定复杂度,默认spring是无法直接解决的。

2. Spring解决循环依赖的限制条件

(1)Spring只能解决单例Bean的循环依赖问题。如果两个原型Bean相互引用,则Spring无法解决它们。

(2)非代理对象,如果两个Bean都需要使用代理对象,则Spring也无法解决它们

(3)满足以上两个条件的主bean通过属性或者setter方法注入所依赖的bean,而不是通过构造函数注入。

这样的循环依赖Spring是可以解决的,解决方式就像刚开始的配置那样简单。

虽然解决问题很简单,那么其中的原理大家可能也希望了解一下,毕竟这也算是面试八股必背题。那么今天就给大家讲清楚,spring怎么解决循环依赖。

Spring是怎么解决循环依赖的?

一句话描述,为了解决这个问题,Spring使用了一个名为"临时Bean引用"的技术。它的工作原理是,当一个Bean被创建时,Spring将其放在一个早期暴露的Bean工厂中,然后创建另一个Bean。当创建另一个Bean时,Spring会将第一个Bean的实例注入到第二个Bean的setter方法中。然后,当第一个Bean被完全创建时,Spring将把它的实例注入到第二个Bean的setter方法中。也就是我们可能听说过的spring三级缓存,下面详细介绍一下三级缓存解决方案。

三级缓存解决方案

spring内部有三级缓存:

成品:一级缓存 singletonObjects ,用于保存实例化、注入、初始化完成的bean实例 。

半成品:二级缓存 earlySingletonObjects ,用于保存实例化完成的bean实例。

原材料工厂: 三级缓存 singletonFactories ,用于保存bean的创建工厂,以便于后面扩展有机会

创建代理对象。

三级缓存是singletonFactories ,但是是不完整的Bean的工厂Factory,是当一个Bean在new之后(没有属性填充、初始化),就put进去。所以,是原材料工厂二级缓存是对三级缓存的过渡性处理,只要通过 getObject() 方法从三级缓存的BeanFactory中取出Bean一次,原材料就变成变成品,就put到二级缓存 , 所以,二级缓存里边的bean,都是半成品一级缓存里面是完整的Bean,是当一个Bean完全创建后(完成属性填充、彻底的完成初始化)才put进去, 所以,是成品

一张图描述整个过程

那么说到这里,有可能会有人觉得二级缓存不重要如果没有二级缓存行不行???

假设不用第二级缓存,TestService1注入到TestService3的流程如图:

TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是 ObjectFactory 对象。说白了,**两次从三级缓存中获取都是 ObjectFactory 对象,而通过它创建的实例对象每次可能都不一样的。**为了解决这个问题,spring引入的第二级缓存。前一个图其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。

下面看一下Spring源码

在AbstractBeanFactory 的 doGetBean() 方法中,我们根据BeanName去获取Singleton Bean的时候,会先从缓存获取。

getSingleton()的查找bean的次序比较清晰:

1、从一级缓存singletonObjects (成品仓库)中获取单例Bean。

2、一级缓存获取不到,则从二级缓存earlySingletonObjects (半成品仓库)中获取单例Bean。

3、二级缓存获取不到,则从三级缓存singletonFactories(原材料工厂 仓库)中获取单例BeanFactory原材料工厂。

4、如果从三级缓存中拿到了BeanFactory,则通过getObject()生产一个最原始的bean,可以理解为

最原始的bean实例(原材料),没有进行属性填充,也没有完成初始化,由于此处是提取bean,所以bean原材料已经被使用了一次,顺手把Bean(半成品)存入二级缓存中,并把该Bean的(原材料工厂)从三级缓存中删除。

三级缓存里边的原材料工厂(singletonFactory)在哪里设置呢

这就是put三级缓存 singletonFactories 的地方,满足以下3个条件时,把bean加入三级缓存中:

1、单例

2、允许循环依赖

3、当前单例Bean正在创建

singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的关键。***注意:***这段代码发生在 createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还没有完善(没有进行属性填充和初始化)

下面看下doCreateBean过程:

设置到三级缓存之后,对于其他依赖它的对象而言已经足够了(已经有内存地址了,可以根据对象引用定位到堆中对象),能够被认出来了。addSingletonFactory方法的第二参数,通过匿名对象的方式,创建了一个 ObjectFactory 工厂,这个工厂只有一个方法,源码如下

一级缓存在哪里设置呢?

到这里我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,

那一级缓存在哪里设置的呢?

在类 DefaultSingletonBeanRegistry 中,可以发现这个 addSingleton(String beanName, Object

singletonObject) 方法,代码如下:

还有个问题,第三级缓存中为什么要添加 ObjectFactory 对象,直接保存实例对象不行吗?真的不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。针对这种场景spring是怎么做的呢?答案就在 AbstractAutowireCapableBeanFactory 类 doCreateBean 方法的这段代码中:

它定义了一个匿名内部类,通过 getEarlyBeanReference 方法获取代理对象,其实底层是通过AbstractAutoProxyCreator 类的 getEarlyBeanReference 生成代理对象。

到此大家应该明白整个循环依赖解决的过程了吧。多看源码多思考,加油!

举报/反馈

雷哥不爱写代码

88获赞 57粉丝
不会写代码的程序员会教你写代码
关注
0
0
收藏
分享