关于spring:Spring-的循环依赖问题

5次阅读

共计 5518 个字符,预计需要花费 14 分钟才能阅读完成。

什么是循环依赖

什么是 循环依赖 呢?能够把它拆分成 循环 依赖 两个局部来看,循环 是指计算机领域中的循环,执行流程造成闭合回路;依赖 就是实现这个动作的前提筹备条件,和咱们平时说的依赖大体上含意统一。放到 Spring 中来看就一个或多个 Bean 实例之间存在间接或间接的依赖关系,形成循环调用,循环依赖能够分为 间接循环依赖 间接循环依赖,间接循环依赖的简略依赖场景:Bean A 依赖于 Bean B,而后 Bean B 又反过来依赖于 Bean ABean A -> Bean B -> Bean A),间接循环依赖的一个依赖场景:Bean A 依赖于 Bean BBean B 依赖于 Bean CBean C 依赖于 Bean A,两头多了一层,然而最终还是造成循环(Bean A -> Bean B -> Bean C -> Bean A)。

循环依赖的类型

第一种是 自依赖,本人依赖本人从而造成循环依赖,个别状况下不会产生这种循环依赖,因为它很容易被咱们发现。

第二种是 间接依赖,产生在两个对象之间,比方:Bean A 依赖于 Bean B,而后 Bean B 又反过来依赖于 Bean A,如果比拟仔细的话肉眼也不难发现。

第三种是 间接依赖,这种依赖类型产生在 3 个或者以上的对象依赖的场景,间接依赖最简略的场景:Bean A 依赖于 Bean BBean B 依赖于 Bean CBean C 依赖于 Bean A,能够设想当两头依赖的对象很多时,是很难发现这种循环依赖的,个别都是借助一些工具排查。

Spring 对几种循环依赖场景反对状况

在介绍 Spring 对几种循环依赖场景的解决形式之前,先来看看在 Spring 中循环依赖会有哪些场景,大部分常见的场景总结如下图所示:

有句话说得好,源码之下无机密,上面就通过源码探索这些场景 Spring 是否反对,以及反对的起因或者不反对的起因,话不多说,上面进入正题。

第 ① 种场景——单例 Bean 的 setter 注入

这种应用形式也是最罕用的形式之一,假如有两个 Service 别离为 OrderService(订单相干业务逻辑)和 TradeService(交易相干业务逻辑),代码如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {// omit business logic ...}

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() {// omit business logic ...}

}

这种循环依赖场景,程序是能够失常运行的,从代码上看的确是有循环依赖了,也就是说 Spring 是反对这种循环依赖场景的,这里咱们觉察不到循环依赖的起因是 Spring 曾经默默地解决了。

假如没有做任何解决,依照失常的创立逻辑来执行的话,流程是这样的:容器先创立 OrderService,发现依赖于 TradeService,再创立 OrderService,又发现依赖于 TradeService …,产生有限死循环,最初产生栈溢出谬误,程序进行。为了反对这种常见的循环依赖场景,Spring 将创建对象分为如下几个步骤:

  1. 实例化一个新对象(在堆中),但此时尚未给对象属性赋值
  2. 给对象赋值
  3. 调用 BeanPostProcessor 的一些实现类的办法,在这个阶段,Bean 曾经创立并赋值属性实现。这时候容器中所有实现 BeanPostProcessor 接口的类都会被调用(e.g. AOP
  4. 初始化(如果实现了 InitializingBean,就会调用这个类的办法来实现类的初始化)
  5. 返回创立进去的实例

为此,Spring 引入了三级缓存来解决这个问题(三级缓存定义在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 中),第一级缓存 singletonObjects 用于寄存齐全初始化好的 Bean,从该缓存中取出的 Bean 能够间接应用,第二级缓存 earlySingletonObjects 用于寄存提前裸露的单例对象的缓存,寄存原始的 Bean 对象(属性尚未赋值),用于解决循环依赖,第三级缓存 singletonFactories 用于寄存单例对象工厂的缓存,寄存 Bean 工厂对象,用于解决循环依赖。上述实例应用三级缓存的解决流程如下所示:

如果你看过三级缓存的定义源码的话,可能也有这样的疑难:为什么第三级的缓存的要定义成 Map<String, ObjectFactory<?>>,不能间接缓存对象吗?这里不能间接保留对象实例,因为这样就无奈对其做加强解决了。详情可见类 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 办法局部源码如下:


第 ② 种场景——多例 Bean 的 setter 注入

这种形式平时应用得绝对较少,还是应用前文的两个 Service 作为示例,惟一不同的中央是当初都申明为 多例 了,示例代码如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {// omit business logic ...}

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() {// omit business logic ...}

}

如果你在 Spring 中运行以上代码,是能够失常启动胜利的,起因是在类 org.springframework.beans.factory.support.DefaultListableBeanFactorypreInstantiateSingletons() 办法预实例化解决时,过滤掉了多例类型的 Bean,办法局部代码如下:

然而如果此时有其它单例类型的 Bean 依赖到这些多例类型的 Bean 的时候,就会报如下所示的循环依赖谬误了。


第 ③ 种场景——代理对象的 setter 注入

这种场景也会常常碰到,有时候为了实现异步调用会在 XXXXService 类的办法上增加 @Async 注解,让办法对外部变成异步调用(前提要是要在启用类上增加启用注解哦 @EnableAsync),示例代码如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@EnableAsync
@SpringBootApplication
public class BlogMghioCodeApplication {public static void main(String[] args) {SpringApplication.run(BlogMghioCodeApplication.class, args);
  }

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {

  @Autowired
  private TradeService tradeService;

  @Async
  public void testCreateOrder() {// omit business logic ...}

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() {// omit business logic ...}

}

在标有 @Async 注解的场景下,在增加启用异步注解(@EnableAsync)后,代理对象会通过 AOP 主动生成。以上代码运行会抛出 BeanCurrentlyInCreationException 异样。运行的大抵流程如下图所示:

源码在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 类的办法 doCreateBean 中,会判断第二级缓存 earlySingletonObjects 中的对象是否等于原始对象,办法判断局部的源码如下:

二级缓存寄存的对象是 AOP 生成进去的代理对象,和原始对象不相等,所以抛出了循环依赖谬误。如果细看源码的话,会发现如果二级缓存是空的话会间接返回(因为比拟的对象都没有,根本无法校验了),就不会报循环依赖的谬误了,默认状况下,Spring 是依照文件全门路递归搜寻,按 门路 + 文件名 排序,排序靠前先加载,所以咱们只有调整这两个类名称,让办法标有 @Async 注解的类排序在前面即可。


第 ④ 种场景——结构器注入

结构器注入的场景很少,到目前为止我所接触过的公司我的项目和开源我的项目中还没遇到应用结构器注入的,尽管用得不多,然而须要晓得 Spring 为什么不反对这种场景的循环依赖,结构器注入的示例代码如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class OrderService {

  private TradeService tradeService;

  public OrderService(TradeService tradeService) {this.tradeService = tradeService;}

  public void testCreateOrder() {// omit business logic ...}

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class TradeService {

  private OrderService orderService;

  public TradeService(OrderService orderService) {this.orderService = orderService;}

  public void testCreateTrade() {// omit business logic ...}

}

结构器注入无奈退出到第三级缓存当中,Spring 框架中的三级缓存在此场景下无用武之地,所以只能抛出异样,整体流程如下(虚线示意无奈执行,为了直观也把下一步画进去了):


第 ⑤ 种场景——DependsOn 循环依赖

这种 DependsOn 循环依赖场景很少,个别状况下不怎么应用,理解一下会导致循环依赖的问题即可,@DependsOn 注解次要是用来指定实例化程序的,示例代码如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@DependsOn("tradeService")
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {// omit business logic ...}

}
/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@DependsOn("orderService")
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() {// omit business logic ...}

}

通过上文,咱们晓得,如果这里的类没有标注 @DependsOn 注解的话是能够失常运行的,因为 Spring 反对单例 setter 注入,然而加了示例代码的 @DependsOn 注解后会报循环依赖谬误,起因是在类 org.springframework.beans.factory.support.AbstractBeanFactory 的办法 doGetBean() 中查看了 dependsOn 的实例是否有循环依赖,如果有循环依赖则抛出循环依赖异样,办法判断局部代码如下:

总结

本文次要介绍了什么是循环依赖以及 Spring 对各种循环依赖场景的解决,文中只列出了局部波及到的源码,都标了所在源码中的地位,感兴趣的敌人能够去看看残缺源码,最初 Spring 对各种循环依赖场景的反对状况如下图所示(P.S. Spring 版本:5.1.9.RELEASE):

正文完
 0