Spring如何解决循环依赖

目录

1 什么是相互依赖?

2 怎么检测循环依赖

3 Spring中如何解决循环依赖?

3.1 Spring bean的生命周期:

3.2 解决循环依赖

3.2.1 相关概念介绍

3.2.2 debug观察三级对象的存入过程


1 什么是相互依赖?

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A。

通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景。也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题。

 第三种情况说明:

 Spring中循环依赖场景主要有以下三种:
(1)field属性的循环依赖
(2)构造器的循环依赖
(3)DependsOn循环依赖

按对象的状态可以将对象分类两类:

  • 成品:完成实例化和初始化
  • 半成品:完成实例化但未完成初始化

2 怎么检测循环依赖

检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

3 Spring中如何解决循环依赖?

Spring解决循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或着属性是可以延后设置的(但是构造器必须是在获取引用之前)。即:持有了某一个对象的引用之后可以在后续步骤的时候给对象赋值 

 spring解决循环依赖的思路就是取消了红色框的箭头指向,其理论依据(本质)是实例化和初始化可以分开执行。

map框中说明了整个流程中添加到map中的对象的状态和顺序。其中map的spring实现方案是三级缓存。

3.1 Spring bean的生命周期:

详情看这篇文章:Spring bean的生命周期_SeaDhdhdhdhdh的博客-CSDN博客

3.2 解决循环依赖

3.2.1 相关概念介绍

Spring的单例对象的初始化主要分为三步:

  1. 实例化:其实也就是调用对象的构造方法实例化对象
  2. 注入:填充属性,这一步主要是对bean的依赖属性进行填充
  3. 初始化:属性注入后,执行自定义初始化

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。 

那么我们要解决循环引用也应该从bean初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

	/** Cache of singleton objects: bean name --> bean instance */
    //一级缓存:存放已经经历了完整生命周期的bean对象
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name --> ObjectFactory */
    //三级缓存:存放可以生成Bean的工厂
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name --> bean instance */
    //二级缓存:存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整),也称为半成品
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  • 第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
  • 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
  • 第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂。

其中的ObiectFactory为函数式接口,可以将lambda表达式作为参数放到方法的实参中,在方法执行的时候,并不会实际调用当前lambda表达式,只有在调用getObject方法的时候才会调用lambda表达式的内容。

3.2.2 debug观察三级对象的存入过程

进行debug操作,来观察AB对象到底是以什么样的流程放到缓存中去?

源码中主要包括6个方法:

 配置文件中的A和B对象:

 直接进入finishBeanFactoryInitialization的最后一个方法:

 从这里开始spring开始了创建对象的过程:

在这里插入图片描述

 接下来,进入getBean()方法观察它实现了什么?

调用doGetBean()方法,继续进入:

在这里插入图片描述

由此可见,三个缓存中都没有获取到对象,所以下一步需要去创建对象,下面很多方法,暂时不用知道,直接看下面的方法:

在这里插入图片描述

他调用了我们传入的 return createBean()方法

我们进入查看,在这里面,可以找到一个非常重要的类Object beanInstance = doCreateBean(beanName, mbdToUse, args); 调用doCreateBean方法获取实例化参数,里面实现了一些 Aware方法以及popilate来注入属性 然后通过反射来初始化bean 具体流程我们就不了解了,我们主要是介绍三级缓存的流程,通过这个方法,我们实现了bean对象的实例化
 在这里插入图片描述

在bean的实例化完成以后,往三级缓存添加a对象的信息,会来到这个方法:

进入lambda表达式方法:

 由上面图片可以发现,此时只是讲lambda表达式放入三级缓存中,并没有将对象a放入,所以三级缓存中存的数据结构为:

  • key:a
  • value:singletonFactory(lambda表达式)

自此完成了a对象的创建工作,接下来完成一个重要的工作:属性填充 

在这里插入图片描述

接下来调用populateBean方法未bean中填充属性,我们进入populateBean中观察方法,其他的方法暂时不用了解,我们直接看下面的方法 

在这里插入图片描述

该方法为我们执行属性的赋值操作。

在这里插入图片描述

但是注意,此时我们并没有创建B的bean对象 所以 它赋值的不是b对象,而是一个RuntimeBeanReference 对象 =运行时bean引用。即上图中第二行代码获取的originalValue是一个RuntimeBeanReference类。

然后我们进入Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
观察它是如何处理的

在这里插入图片描述

进入resolveReference()方法:此时会尝试从工厂中获取b对象,套娃开始了!!!

此时进入了doGetBean方法中,我们发现缓存中没有b对象。那么它就会通过下述方法来创建bean对象(整个过和创建a对象的过程一样(仅仅是创建过程,不包括属性赋值过程),不再赘述

b对象创建完成后三个缓存中的内容,a对象,b对象如下图所示:

此时,一二级缓存都为空,三级缓存中存放a,b对象的lanbda表达式,a,b对象都已经实例化,但是其属性值都为null。

然后运行至填充属性populateBean()方法:

 进入方法:

此时获取的a属性的值依然是RuntimeBeanReference类(和a对象填充b属性的时候一样)。 

来到处理属性值方法:

进入以后 :

 再进入resolveReference方法:

再进入getBean方法:

 进入doGetBean:

此时getSingleton并不能获取到a对象。因为在之前的所有步骤中 并没有像三个缓存中存放a对象。

进入getSingleton方法:

此时从三级缓存中获取lambda表达式,然后调用getObject方法。进入:

 再进入:

 选中的代码可以理解为对原始类的增强。此时执行完下图中的getObject方法。获取到了a对象。

 此时a对象为半成品状态,并将a对象放入二级缓存。并将三级缓存中的a的k,v键值对删除。此时缓存状态为:红色为删除状态。

 将A对象返回,此时获取到了A对象,下图的注释不用太在意,不用关心getObjectForBeanInstance,只知道此刻我们获取到了 A对象,需要返回给B对象的A属性赋值,此时b对象是一个成品对象。并将其放入一级缓存中

此时缓存中的内容为:

 此时我们可以看到B对象的A属性已经赋值完成,但是a属性里的b属性还没有赋值,此时B的实例化和初始化都已经完成了,接下来该初始化A了。 此时继续向上返回b对象,给a对象中的b属性赋值。

 此时已经赋值完成 我们发现已经完成了他们的赋值 整体看上去就像个一个循环一样。

继续返回我们发现它将A也放入了1级缓存中
在这里插入图片描述
此时我们的A对象已经创建完成 返回到最初的在这里插入图片描述

然后循环通过getBean方法去创建B对象(最开始的遍历beanName去创建)
由于B对象在以及缓存中已经存在 所以直接返回我们的循环依赖对象的创建至此已经完成 。

最终容器状态:

总结:

  • 一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。
  • 二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
  • 三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。

在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值