当前位置: 首页 > 后端技术 > Java

面试题:如何解决Spring的循环依赖问题

时间:2023-04-01 16:55:35 Java

Spring的循环依赖问题什么是循环依赖,什么是循环依赖?它可以分为两个部分:循环和依赖。循环是指计算机领域的循环,执行过程形成一个闭环;依赖是完成这个动作的前提,和我们平时说的依赖大体一致。放在Spring中,一个或多个Bean实例之间存在直接或间接的依赖关系,形成循环调用。循环依赖可以分为直接循环依赖和间接循环依赖。直接循环依赖的简单依赖场景:BeanA依赖BeanB,然后BeanB依次依赖BeanA(BeanA->BeanB->BeanA),间接循环依赖的依赖场景:BeanA依赖在BeanB上,BeanB依赖BeanC,BeanC依赖BeanA,中间多了一层,但还是形成了一个循环(BeanA->BeanB->BeanC->BeanA).第一种循环依赖是自依赖,依赖自身形成循环依赖。一般不会出现这种循环依赖,因为我们很容易发现。第二种是直接依赖,发生在两个对象之间。例如,BeanA依赖于BeanB,BeanB又依赖于BeanA。如果细心一点,用肉眼不难发现。第三种是间接依赖。这种类型的依赖发生在三个或更多对象相互依赖的场景中。间接依赖最简单的场景是:BeanA依赖BeanB,BeanB依赖BeanC,BeanC依赖BeanA,可以想象当中间依赖对象很多的时候,很难找到这种循环依赖,一般会借助一些工具来检查。Spring对几种循环依赖场景的支持在介绍Spring对几种循环依赖场景的处理之前,我们先了解一下Spring中循环依赖的场景。大部分常见的场景总结如下图:说的好,源码下没有秘密,下面我们通过源码来探究一下这些场景Spring是否支持,以及支持与否的原因。话不多说,进入正题。场景①——单例bean的setter注入也是最常用的方法之一。假设有两个服务名为OrderService(与订单相关的业务逻辑)和TradeService(与贸易相关的业务逻辑)。代码如下:/***@authormghio*@since2021-07-17*/@ServicepublicclassOrderService{@AutowiredprivateTradeServicetradeService;publicvoidtestCreateOrder(){//省略业务逻辑...}}/***@authormghio*@since2021-07-17*/@ServicepublicclassTradeService{@AutowiredprivateOrderServiceorderService;publicvoidtestCreateTrade(){//省略业务逻辑...}}这种循环依赖场景,程序可以正常运行,从代码来看确实存在循环依赖,也就是说Spring支持这种循环依赖场景。之所以我们没有注意到这里的循环依赖,是因为Spring默默地解决了它。假设不做任何处理,如果按照正常的创建逻辑,流程是这样的:容器先创建OrderService,发现依赖TradeService,然后创建OrderService,发现依赖TradeService...,一个出现死循环,最后出现Stackoverflow错误,程序停止。为了支持这种常见的循环依赖场景,Spring将对象的创建分为以下几个步骤:实例化一个新的对象(在堆中),但是此时对象属性还没有赋值给对象赋值调用一些实现类BeanPostProcessor方法,到这个阶段,Bean已经创建完成,属性的赋值也已经完成。此时会调用容器中所有实现了BeanPostProcessor接口的类(如AOP)进行初始化(如果实现了InitializingBean,则调用该类的方法完成类初始化),创建的实例为回。为此,Spring引入了三级Cache来处理这个问题(三级缓存定义在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中),一级缓存singletonObjects用来存放完全初始化的bean,从缓存中取出的bean可以直接使用。二级缓存earlySingletonObjects用于存放提前暴露的单例对象的缓存,存放原始的Bean对象(属性还没有赋值),用于解决循环依赖。三级缓存singletonFactories用于存放单例对象工厂的缓存。用于解决循环依赖的Bean工厂对象。上面例子使用三级缓存的处理流程如下:如果你看过三级缓存的定义源码,你可能也会有这样的疑问:为什么三级缓存要定义为Map>,对象不能直接缓存吗?这里不能直接保存对象实例,因为那样就不能增强了。详见类org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法源码如下:场景②——注入多个Beansetter作为例子,唯一不同的是现在声明为多个实例。示例代码如下:/***@authormghio*@since2021-07-17*/@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicclassOrderService{@AutowiredprivateTradeServicetradeService;publicvoidtestCreateOrder(){//省略业务逻辑...}}/***@authormghio*@since2021-07-17*/@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicclassTradeService{@AutowiredprivateOrderService订单服务;publicvoidtestCreateTrade(){//省略业务逻辑...}}如果在Spring中运行上面的代码,可以成功启动,因为它在类org.springframework.beans中。factory.support.DefaultListableBeanFactory的preInstantiateSingletons()方法在预实例化处理过程中过滤掉多实例bean。该方法的代码如下:但是如果此时还有其他单例类型的bean依赖这些多实例类型的bean时,就会报如下图所示的循环依赖错误。第三种场景——代理对象的setter注入也是经常遇到的。有时为了实现异步调用,会在XXXXService类的方法上加上@Async注解,让方法对外变成异步调用(前提是在启用类@EnableAsync上加上启用注解),示例代码如下:/***@authormghio*@since2021-07-17*/@EnableAsync@SpringBootApplicationpublicclassBlogMghioCodeApplication{publicstaticvoidmain(String[]args){SpringApplication.run(BlogMghioCodeApplication.类,参数);}}/***@authormghio*@since2021-07-17*/@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicclassOrderService{@AutowiredprivateTradeServicetradeService;@AsyncpublicvoidtestCreateOrder(){//省略业务逻辑...}}/***@authormghio*@since2021-07-17*/@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicclassTradeService{@Autowired私有订单服务订单服务;publicvoidtestCreateTrade(){//省略业务逻辑...}}在@Async注解标注的场景中,添加启用异步注解(@EnableAsync)后,会通过AOP自动生成代理对象。运行上面的代码会抛出BeanCurrentlyInCreationException异常。运行的大致流程如下图所示:源码在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory类的doCreateBean方法中,会判断二级缓存中的对象earlySingletonObjects是否相等到原始对象。方法判断部分源码如下:二级缓存中存储的对象是AOP生成的代理对象,不等于原始对象,因此抛出循环依赖错误。仔细看源码会发现,如果二级缓存为空,则直接返回(因为没有要比较的对象,根本无法验证),循环依赖的错误不会被报道。默认情况下,Spring会按照文件的全路径递归查找,按照路径+文件名排序,排序靠前的先加载,所以我们只需要调整两个类名,让@标记的类异步注解排在后面。场景④——构造函数注入构造函数注入的场景非常少。目前为止,我在接触过的公司项目和开源项目中都没有遇到过构造函数注入。虽然用的不多,但是需要知道为什么Spring在这种场景下不支持循环依赖呢?构造函数注入的示例代码如下:/***@authormghio*@since2021-07-17*/@ServicepublicclassOrderService{privateTradeServicetradeService;publicOrderService(TradeServicetradeService){this.tradeService=tradeService;}publicvoidtestCreateOrder(){//省略业务逻辑...}}/***@authormghio*@since2021-07-17*/@ServicepublicclassTradeService{privateOrderServiceorderService;publicTradeService(OrderServiceorderService){this.orderService=orderService;}publicvoidtestCreateTrade(){//省略业务逻辑...}}构造函数注入不能加入三级缓存,Spring框架中的三级缓存在这种场景下是没有用的,所以只能抛出异常被抛出。整体流程如下(虚线表示不能执行,为了直观画下一步):第五种场景——DependsOn循环依赖这种DependsOn循环依赖场景很少,一般情况下用的不多.了解导致循环依赖的问题即可。@DependsOn注解主要用于指定实例化的顺序。示例代码如下:/***@authormghio*@since2021-07-17*/@Service@DependsOn("tradeService")publicclassOrderService{@AutowiredprivateTradeService贸易服务;publicvoidtestCreateOrder(){//省略业务逻辑...}}/***@authormghio*@since2021-07-17*/@Service@DependsOn("orderService")publicclassTradeService{@AutowiredprivateOrderService订单服务;publicvoidtestCreateTrade(){//省略业务逻辑...}}通过上面我们知道,如果这里的类没有打上@DependsOn注解,是可以正常运行的,因为Spring支持单例Setter注入,但是经过添加示例代码的@DependsOn注解,会报循环依赖错误。原因是在org.springframework.beans.factory.support.AbstractBeanFactory类的doGetBean()方法中检查了dependsOn的实例。是否存在循环依赖,如果存在循环依赖,则抛出循环依赖异常。部分代码的判断方法如下:总结本文主要介绍什么是循环依赖以及Spring是如何处理各种循环依赖场景的。知道源码中的位置后,有兴趣的朋友可以去看看完整的源码。最后,Spring对各种循环依赖场景的支持如下图所示(P.S.Spring版本:5.1.9.RELEASE):