问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

被问麻了,Spring 如何处理循环依赖?

发布网友 发布时间:2024-09-25 16:36

我来回答

1个回答

热心网友 时间:2024-10-04 08:58

前言

Spring如何处理循环依赖?这是最近较为频繁被问到的一个面试题,在前面Bean实例化流程中,对属性注入一文多多少少对循环依赖有过介绍,这篇文章详细讲一下Spring中的循环依赖的处理方案。

什么是循环依赖

依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者多个Bean相互依赖,如:

构造器循环依赖

代码示例:

public class BeanA {private BeanB beanB;public BeanA(BeanB beanB){this.beanB = beanB;}}public class BeanB {private BeanA beanA;public BeanB(BeanA beanA){this.beanA = beanA;}}

配置文件

<bean id="beanA" class="cn.itsource._01_di.BeanA" ><constructor-arg type="cn.itsource._01_di.BeanB" ref="beanB"/> </bean> <bean id="beanB" class="cn.itsource._01_di.BeanB"> <constructor-arg type="cn.itsource._01_di.BeanA" ref="beanA" /> </bean>Setter循环依赖

代码示例

public class BeanA {private BeanB beanB;public void setBeanB(BeanB beanB){this.beanB = beanB;}}@Datapublic class BeanB {private BeanA beanA;public void setBeanA(BeanA beanA){this.beanA = beanA;}}

配置文件

<bean id="beanA" class="cn.itsource._01_di.BeanA" ><property name="beanB" ref="beanB" /></bean><bean id="beanB" class="cn.itsource._01_di.BeanB"><property name="beanA" ref="beanA" /></bean>

循环依赖包括: 构造器注入循环依赖 set , 注入循环依赖 和 prototype模式Bean的循环依赖。Spring只解决了单例Bean的 setter 注入循环依赖,对于构造器循环依赖,和 prototype模式的循环依赖是无法解决的,在创建Bean的时候就会抛出异常 :“BeanCurrentlyInCreationException” ,

循环依赖控制开关在 AbstractRefreshableApplicationContext 容器工厂类中有定义:

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext { @Nullable private Boolean allowBeanDefinitionOverriding; //是否允许循环依赖 @Nullable private Boolean allowCircularReferences; //设置循环依赖 public void setAllowCircularReferences(boolean allowCircularReferences) {this.allowCircularReferences = allowCircularReferences; }

默认情况下是允许Bean之间的循环依赖的,在依赖注入时Spring会尝试处理循环依赖。如果将该属性配置为“false”则关闭循环依赖,当在Bean依赖注入的时遇到循环依赖时抛出异常。可以通过如下方式关闭,但是一般都不这么做

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");//禁用循环依赖applicationContext.setAllowCircularReferences(false);//刷新容器applicationContext.refresh();...构造器循环依赖处理

构造器是不允许循环依赖的,动动你的小脑瓜想一想,比如:A 依赖 B ,B依赖C,C依赖A,在实例化A的时候,构造器需要注入B,然后Spirng会实例化B,此时的A属于“正在创建”的状态。当实例化B的时候,发现构造器需要注入C,然后去实例化C,然而实例化C的时候又需要注入A的实例,这样就造成了一个死循环,永远无法先实例化出某一个Bean,所以Spring遇到这里构造器循环依赖会直接抛出异常。

那么Spring到底是如何做的呢?

首先Spring会走Bean的实例化流程尝试创建 A 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找A 是否正在创建,如果没找到,则将 A 放入 “正在创建Bean池”中,然后准备实例化构造器参数 B。

Spring会走Bean的实例化流程尝试创建 B 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找B 是否正在创建,如果没找到,则将 B 放入 “正在创建Bean池”中,然后准备实例化构造器参数 A。

Spring会走Bean的实例化流程尝试创建 A 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找A 是否正在创建。

此时:Spring发现 A 正处于“正在创建Bean池”,表示出现构造器循环依赖,抛出异常:“BeanCurrentlyInCreationException”

DefaultSingletonBeanRegistry#getSingleton

下面我们以 BeanA 构造参数依赖BeanB, BeanB 构造参数依赖BeanA 为例来分析。

当Spring的IOC容器启动,尝试对单利的BeanA进行初始化,根据之前的分析我们知道,单利Bean的创建入口是 AbstractBeanFactory#doGetBean 在该方法中会先从单利Bean缓存中获取,如果没有代码会走到:DefaultSingletonBeanRegistry#getSingleton(jString beanName, ObjectFactory<?> singletonFactory) 方法中 ,在该方法中会先对把创建的Bean加入 一个名字为 singletonsCurrentlyInCreation 的 ConcurrentHashMap中,意思是该Bean正在创建中,然后调用 ObjectFactory.getObject() 实例化Bean , 假设 BeanA 进入了该方法进行实例化:

//正在创建中的Beanprivate final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {...省略...//把该Bean的名字加入 singletonsCurrentlyInCreation 正在创建池 中beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>();}try { //调用ObjectFactory创建Bean的实例 singletonObject = singletonFactory.getObject(); newSingleton = true;}...省略...//如果singletonsCurrentlyInCreation中没该Bean,就把该Bean存储到singletonsCurrentlyInCreation中,//如果 singletonsCurrentlyInCreation 中有 该Bean,就报错循环依赖异常BeanCurrentlyInCreationException//也就意味着同一个beanName进入该方法2次就会抛异常protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName);} }

beforeSingletonCreation 方法非常关键 ,它会把beanName加入 singletonsCurrentlyInCreation,一个代表“正在创建中的Bean”的ConcurrentHashMap中。

如果singletonsCurrentlyInCreation中没该beanName,就把该Bean存储到singletonsCurrentlyInCreation中, 如果 singletonsCurrentlyInCreation 中有 该Bean,就报错循环依赖异常BeanCurrentlyInCreationException

【注意】也就意味着同一个beanName进入该方法2次就会抛异常 , 现在BeanA已经加入了singletonsCurrentlyInCreation

AbstractAutowireCapableBeanFactory#autowireConstructor

我们前面分析过 ObjectFactory.getObject实例化Bean的详细流程,这里我只是大概在复盘一下就行了。因为我们的BeanA的构造器注入了一个BeanB,所以 代码最终会走到AbstractAutowireCapableBeanFactory#autowireConstructor ,通过构造器来实例化BeanA(在属性注入那一章有讲到 ) 。

在autowireConstructor 方法中会通过 ConstructorResolver#resolveConstructorArguments来解析构造参数,调用 BeanDefinitionValueResolver 去把 ref="beanB" 这种字符串的引用变成一个实实在在的Bean,即BeanB,所以在 BeanDefinitionValueResolver 属性值解析器中又会去实例化BeanB,同样会走到 DefaultSingletonBeanRegistry#getSingleton 中把BeanB加入 singletonsCurrentlyInCreation “正在创建Bean池”中,然后调用ObjectFactory.getObject实例化BeanB。

低于BeanB而已同样需要通过构造器创建,BeanB构造器参数依赖了BeanA,也就意味着又会调用 BeanDefinitionValueResolver 去把 ref=“beanA” 这种字符串引用变成容器中的BeanA的Bean实例,然后代码又会走到 DefaultSingletonBeanRegistry#getSingleton。然后再一次的尝试把BeanA加入singletonsCurrentlyInCreation “正在创建Bean池”。

此时问题就来了,在最开始创建BeanA的时候它已经加入过一次“正在创建Bean” 池,这会儿实例化BeanB的时候,由于构造器参数依赖了BeanA,导致BeanA又想进入“正在创建Bean” 池 ,此时 Spring抛出循环依赖异常:

Error creating bean with name ‘beanA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

到这,Spring处理构造器循环依赖的源码分析完毕。

setter循环依赖处理

setter循环依赖是可以允许的。Spring是通过提前暴露未实例化完成的Bean的 ObjectFactory来实现循环依赖的,这样做的目的是其他的Bean可以通过 ObjectFactory 引用到该Bean。

实现流程如下:

Spring创建BeanA,通过无参构造实例化,把BeanA添加到“正在创建Bean池”中,并暴露当前实例的ObjectFactory,即把ObjectFactory添加到singletonFactories(三级缓存)中,该ObjectFactory用来获取创建中的BeanA,然后,然后通过setter注入BeanB

Spring创建BeanB,通过无参构造实例化,把BeanB添加到“正在创建Bean池”中,并暴露一个ObjectFactory,然后,然后通过setter注入BeanA

在BeanB通过setter注入BeanA时,由于BeanA 提前暴露了ObjectFactory ,通过它返回一个提前暴露一个创建中的BeanA。

然后完成BeanB的依赖注入

这里补张图:

获取Bean的时候走三级缓存

protected Object getSingleton(String beanName, boolean allowEarlyReference) { //一级缓存,存储实例化好的BeanObject singletonObject = this.singletonObjects.get(beanName);//如果单利缓存池中没有,但是beanName正在创建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //获取二级缓存,这个里面存储的是正在创建的Bean,半成品singletonObject = this.earlySingletonObjects.get(beanName); //如果也为空,但是允许循环依赖if (singletonObject == null && allowEarlyReference) { //从三级缓存获取Bean的创建工厂, ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) {//创建Bean的实例singletonObject = singletonFactory.getObject();//把Bean存储到二级缓存this.earlySingletonObjects.put(beanName, singletonObject); //移除三级缓存中的创建工厂this.singletonFactories.remove(beanName); }} }}return (singletonObject != NULL_OBJECT ? singletonObject : null); }AbstractAutowireCapableBeanFactory#doCreateBean

我们以BeanA 通过settter依赖BeanB,BeanB通过setter 依赖BeanA为例来分析一下源码,在之前的Bean实例化流程分析过程中我们了解到,Bean的实例化会走AbstractBeanFactory#doGetBean,然后查找单利缓存中是否有该Bean ,如果没有就调用 DefaultSingletonBeanRegistry#getSingleton,方法会把BeanA加入 singletonsCurrentlyInCreation “创建中的Bean池”,然后调用ObjectFactory.getObject创建Bean.

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 源码:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {final String beanName = transformedBeanName(name);Object bean;// Eagerly check singleton cache for manually registered singletons.//缓存中获取Bean,解决了循环依赖问题Object sharedInstance = getSingleton(beanName);...缓存中没有走下面...if (mbd.isSingleton()) { //走 DefaultSingletonBeanRegistry#getSingleton ,方法会把bean加入“正在创建bean池” //然后调用ObjectFactory实例化Bean sharedInstance = getSingleton(beanName, () -> {try { return createBean(beanName, mbd, args);}catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex;} }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

第一次进来,缓存中是没有BeanA的,所有会走 getSingleton 方法,然后代码最终会走到AbstractAutowireCapableBeanFactory#doCreateBean 方法中 。

AbstractAutowireCapableBeanFactory#doCreateBean源码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {//实例化Bean instanceWrapper = createBeanInstance(beanName, mbd, args);}...省略...//如果是单利 ,如果是允许循环依赖,如果 beanName 出于创建中,已经被添加到“创建中的bean池”boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) { if (logger.isDebugEnabled()) {logger.debug("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references"); } //把ObjectFactory 添加到 singletonFactories 中。 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));} try {//走依赖注入流程 populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd);}//缓存单利Bean的创建工厂,用于解决循环依赖protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) { //singletonObjects单利缓存中
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
手机垃圾清理使用哪个软件最好? 适合新手用的手机清理软件 苹果手机怎么把号码黑名单里? VUE视频相机使用教程 教你简单几步拍出大片 如何使用佳能相机录像 相机录像具体操作教程分享 羊头捣蒜做法图解 服刑期间领取养老金是否构成犯罪 狗狗胰腺炎最怕的三种蔬菜是什么 怎么喂食 银行卡给专业的贷款公司做流水有风险吗? 电话号可直接贷款吗,他知道我的实名电话,还用paos机刷了我的银行卡一... 补维生素c,维生素B族,补铁的维生素可以一起吃吗 Spring循环依赖解决方案 Spring获取Bean的9种方式 可以单独补铁吗?例如安利的 2020年10月21日D2224次动车徐锦芳的车厢号和坐位号? 刷完的鞋子发黄怎么办 2345影视大全是免费的吗? 王者荣耀贵族五荣耀王者196个五级符文值多少钱? 4月1号是什么节日为什么叫愚人节) 愚人节几月几号2024 八仙庵开门了吗 2020八仙庵开放时间 几点开门 ...中,如家 汉庭 七天 锦江之星 莫泰 速8 格林豪泰员工工资哪个比较高... 快捷酒店排行榜前十有哪些品牌 端午节想到深圳玩一天,具体去哪,给点油代表性的答案谢谢你们。_百度知 ... 端午去海边朋友圈说说 11年4月28到10月22日,已知4月28日是星期四,则从4月28到10月22日经历了... 端午能去海边吗 端午节可以去海边玩吗 端午节能去海边玩吗 端午可以去海边吗 SpringBean的配置详解 spring实例化bean的三种方式(springbean实例化和初始化) bean对象的三种注入方式(bean对象的三种注入方式有哪些) spring编程中如何解决循环依赖? 下列脊椎动物中真正属于鱼类的是( )①甲鱼②黄鳝 ⑨鲸鱼 ④娃娃鱼⑤泥 ... 下列属于鱼类的动物是 A.娃娃鱼 B.鲍鱼 C.黄鳝 D. 下列动物真正属于鱼的是( ) A.甲鱼 B.黄鳝 C.鲸鱼 D.娃娃 下列动物真正属于鱼类的是( ) 下列属于鱼类的动物是 [ ] A.娃娃鱼 B.鲍鱼 ... 变频器tatbtc什么意思 热血江湖九泉哪层有55级左右能刷的怪 热血江湖中的刀客在每一个等级中应该打什么怪物练级最快呀? 白色的鞋子晒了发黄了怎么办白色的鞋子晒了发黄了有什么办法 热血江湖九泉多少层是卡墙的队伍啊 ①东②南③西04北⑤中,对青[)赤一()黄一()白一(丿黑一()怎么对亲 湖南2024高考分数线公布时间 如何给孩子取一个好听的名字? 怎样取一个好听又有诗意的名字? 祁门红茶外贸出口相关资讯 如何提高祁红品质?