Spring 是如何解决循环依赖的?

简介: 在我的一篇文章中介绍如何避免 Spring 自调用事务失效,其中网友给建议,说可以在类中注入自身,然后调用,而注入自身的过程也是循环依赖的处理过程。下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?

前言


相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的:

网络异常,图片无法展示
|

还会提示这么一句:

Requested bean is currently in creation: Is there an unresolvable circular reference?

老铁!这就是发生循环依赖了!

当然这里是一个异常情况。

在我的一篇文章中介绍如何避免 Spring 自调用事务失效,其中网友给建议,说可以在类中注入自身,然后调用,而注入自身的过程也是循环依赖的处理过程。

下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?


什么是循环依赖

网络异常,图片无法展示
|

Dependency Resolution Process

Spring IoC 容器会在运行时检测到构造函数注入循环引用,并抛出 BeanCurrentlyInCreationException。

所以要避免构造函数注入,可以使用 setter 注入替代。

根据官方文档说明,Spring 会自动解决基于 setter 注入的循环依赖。

当然在咱们工作中现在都使用 @Autowired 注解来注入属性。

PS: @Autowired 是通过反射进行赋值。

这里从我们最经常使用的场景切入,看 Spring 是如何解决循环依赖的?


代码

@Service
public class CircularServiceA {
    @Autowired
    private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
    @Autowired
    private CircularServiceC circularServiceC;
}
@Service
public class CircularServiceC {
    @Autowired
    private CircularServiceA circularServiceA;
}

这里有 A、B、C 三个类,可以看到发生了循环依赖:

网络异常,图片无法展示
|

但是即使发生了循环依赖,我们依然可以启动 OK,使用并没有任何影响。


Spring 是如何解决循环依赖的

Spring 单例 Bean 的创建 中介绍介绍了使用三级缓存。

singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

singletonFactories: 三级缓存,存储 singletonFactory。

网络异常,图片无法展示
|

当然,这里看着比较长,可以简化一下:

网络异常,图片无法展示
|


通过 Debug 来说明生成过程

从 preInstantiateSingletons 方法开始:

添加断点 beanName.equals("circularServiceA")

启动Debug:

网络异常,图片无法展示
|

会从缓存中获取单例 Bean

网络异常,图片无法展示
|

这里很显然获取不到,继续执行,创建单例实例

网络异常,图片无法展示
|

发现是单例再次获取

网络异常,图片无法展示
|

这里还会从一级缓存获取一次 circularServiceA , 没有获取到,将 circularServiceA 添加到在创建的池子里面 (singletonsCurrentlyInCreation 是一个 set 集合)。

然后会调用工厂方法 createBean(beanName, mbd, args) 创建对象。

网络异常,图片无法展示
|

在 createBean 中去实例化 Bean 。

网络异常,图片无法展示
|

判断是否是循环引用,是的话需要添加到三级缓存中。

网络异常,图片无法展示
|

circularServiceA 不在一级缓存中,则将 circularServiceA 的 singletonFactory 添加到 三级缓存 (singletonFactories) 中,同时从二级缓存中移除。


到这一步为止,circularServiceA 已经在三级缓存中了。

开始对 Bean 的属性进行赋值。

网络异常,图片无法展示
|

在 populateBean 方法中执行到

PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);

就会对属性进行赋值

网络异常,图片无法展示
|

在 injet 方法中,回去解决相关依赖。

网络异常,图片无法展示
|

继续 Debug ,发现解决依赖,最后发现其实又调用回 beanFactory.getBean(beanName);

不过这次创建的是 circularServiceB

网络异常,图片无法展示
|

下面是调用链:

网络异常,图片无法展示
|

circularServiceB 的过程和 circularServiceA 的一样,也是创建了三级缓存,然后去创建 circularServiceC

网络异常,图片无法展示
|

这时候三级缓存里面有它们三个的 singletonFactory 。

网络异常,图片无法展示
|

circularServiceC 也调用到 doGetBean 方法去获取 circularServiceA

不过这次 调用到 Object sharedInstance = getSingleton(beanName); 的时候, circularServiceA 已经存在了。

网络异常,图片无法展示
|


这次调用虽然没有从一级缓存 (singletonObjects) 中获取到 circularServiceA,但是 circularServiceA创建中,所以进入判断

在这里执行完之后, circularServiceA 从三级缓存升级到二级缓存

网络异常,图片无法展示
|


使用反射对 circularServiceC 中的 circularServiceA 进行赋值, 此时 circularServiceA 是在 二级缓存中。

那就比较好奇了,这时候 circularServiceC 里面的 circularServiceA 已经通过反射赋值,这个赋值给的是什么值?

查看代码:

网络异常,图片无法展示
|


这块是从三级缓存(singletonFactories)中获取的 singletonObject,然后调用

singletonObject = singletonFactory.getObject();

获取的一个对象

网络异常,图片无法展示
|


这里获取到的是 circularServiceA 的引用,注意 circularServiceA 这时候还没创建完成,只是引用。所以这里赋值的是 circularServiceA 的引用。

到这里 circularServiceC 就创建完了。

然后会将 C 添加到一级缓存和已注册列表中,同时从二级三级缓存中删除 C。

网络异常,图片无法展示
|

网络异常,图片无法展示
|


继续执行 B 和 A 的属性赋值以及后续的初始化流程。

至此,循环依赖解决完毕。


总结


Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:

  • singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。
  • earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。
  • singletonFactories: 三级缓存,存储 singletonFactory。

本文也通过 Debug 来验证了使用三级缓存解决依赖的过程。

网络异常,图片无法展示
|

不过还有一些问题没有说明:

  1. 循环依赖和代理之间的关系是什么?比如 @Transactional 和 @Async 注解会对循环依赖产生什么影响?
  2. 为什么要用三级缓存?二级缓存不可以么?
目录
相关文章
|
6天前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
4天前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
4天前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
29天前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
29天前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
29天前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
|
29天前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决
|
22天前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
1月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
87 0
|
19天前
|
XML Java 数据库连接
Spring Boot集成MyBatis
主要系统的讲解了 Spring Boot 集成 MyBatis 的过程,分为基于 xml 形式和基于注解的形式来讲解,通过实际配置手把手讲解了 Spring Boot 中 MyBatis 的使用方式,并针对注解方式,讲解了常见的问题已经解决方式,有很强的实战意义。在实际项目中,建议根据实际情况来确定使用哪种方式,一般 xml 和注解都在用。