浅入浅出的讲解Spring循环依赖问题

 更新时间:2021年10月13日 00:04  点击:1992

前言

最近有粉丝问到了循环依赖问题,以后再有人问你,拿这篇“吊打”他。

概念

什么是循环依赖?

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

通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,基于set方法构造注入的属性互相引用的场景。

循环依赖的种类及能否解决如下:


名称 是否可解决循环依赖
构造器循环依赖
Setter循环依赖
Prototype作用域的循环依赖

报错信息

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘myDao': Requested bean is currently in creation: Is there an unresolvable circular reference?

翻译一下

通过构造函数参数 0 表示的依赖关系未得到满足;嵌套的异常是 创建名称为'myDao'的bean时出错。请求的Bean目前正在创建中。是否存在一个无法解决的循环引用?

异常信息:bean当前创建异常org.springframework.beans.factory.BeanCurrentlyInCreationException。

通俗版理解

两人对峙

现在甲乙两个人,互相对峙,甲说乙先放,乙说甲先放。就是不开枪。

哎,就是玩!

相信这个场景大家在电视剧里都见过吧,最后一般是“反派死于话多”。

但是回到我们 spring里,我们是不希望有人死亡的,也就是必须两个bean都创建出来,怎么办?

必须有一人妥协

解决方案就是:必须有一个人先妥协。

甲说:我退一步,我先把弹夹卸了,你把枪放下。

乙一听就感动了,满含热泪的拿枪放下了。

甲一看乙没有打自己,也热泪盈眶,两人紧紧相拥。

从此过上了幸福美满的生活……

Spring版理解

回到我们spring里,先回顾一下bean的生命周期:

  • 实例化
  • 属性赋值
  • 初始化
  • 销毁

简单理解一下的上面的过程

实例化和初始化什么区别?

是不是只差了中间赋值的过程,那只实例化的bean可以使用吗?

当然不可以!

也就是说只实例化的bean是一个半成品,初始化之后才是成品,才可以使用。

现在A依赖B,B依赖A。

A对B说:我要完整的你

b也对a:我要完整的你

ok,两人打起来了,拿枪对峙。怎么解决?是不是得一个人妥协。

a说:算了吧,你给我个你的半成品,我将就一下。

b心里寻思,他用我的半成品创建一个完整的a,然后我就可以创建了。

心里这么想,嘴上就爽快答应着:行,没问题。

如此,a创建了完整的自己,b拿着a也完成了创建。

问题解决。

真的解决了吗?成品和半成品都存在哪里呢?

这就不得不提到大名鼎鼎的三级缓存。

三级缓存

spring提供了三级缓存来存放成品和半成品及工厂。位于DefaultSingletonBeanRegistry类中。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {   
	/**
	*一级缓存:单例池
	*存放已经初始化的bean——成品
	*/
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /**
	*三级缓存:单例工厂的高速缓存
	*存放生成bean的工厂
	*/
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    /**
	*二级缓存:早期单例对象的高速缓存
	*存放已经实例化但未初始化(未填充属性)的的bean——半成品
	*/
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
}

创建过程(简易版)

如果你是面试突击,建议把简易版被下来就可以应付面试了
等有时间再看源码版

假如A依赖B,B依赖A,那么这两个类之间形成了一个循环依赖

  • A先开始创建,通过其无参构造方法创建bean的实例,并将其实例放入到「二级缓存」提前暴露出来。A停止。
  • B开始创建,先去「一级缓存」找A的成品,找不到,再去「二级缓存」里找,还找不到,再去「三级缓存」里找,找到了A的创建工厂,通过工厂,拿到A的半成品,并将A放到「二级缓存」。
  • 拿到A后,B完成创建,将自己放入「一级缓存」。
  • 此时A继续创建,同样从「一级缓存」开始找,拿到B后完成创建,将自己放入「一级缓存」。

创建过程(源码版)

源码版建议配合spring源码边debug边食用。

1、当我们在调用getBean()获取bean时,实际调用的是doGetBean() 方法。doGetBean() 想要获取 beanA ,于是调用 getSingleton() 方法从缓存中查找 beanA

2、在 getSingleton() 方法中,从「一级缓存」中查找,没有,返回 null

3、doGetBean() 方法中获取到 beanA 为 null ,于是走对应的处理逻辑,调用 getSingleton() 的重载方法(参数为 ObjectFactory 的)

4、在 getSingleton()方法中,先将 beanA_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean 方法

5、进入 AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出 beanA 的实例,然后判断,是否为单例,是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否是在第四步的集合中)判断为 true 则将 beanA 添加到「三级缓存」中

6、对 beanA 进行属性填充,此时检测到 beanA 依赖于 beanB ,于是查找 beanB

7、调用 doGetBean() 方法,和上面 beanA 的过程一样,到缓存中查询 beanB ,没有则创建,然后给 beanB 填充属性

8、此时 beanB 依赖于 beanA ,调用 getSingleton() 获取 beanA ,依次从一级、二级、三级缓存中找、此时从「三级缓存」中获取到 beanA 的创建工厂,通过创建工厂获取到 singletonObject ,此时这个 singletonObject 指向的就是上面在 doCreateBean() 方法中实例化的 beanA

9、这样 beanB 就获取到了 beanA 的依赖,于是 beanB 顺利完成初始化,并将 beanA 从「三级缓存」移动到「二级缓存」中

10、随后 beanA 继续他的属性填充工作,此时也获取到了 beanB ,beanA 也随之完成了创建,回到 getSingleton() 方法中继续向下执行,将 beanA 从「二级缓存」移动到「一级缓存」中

最后

到此这篇关于Spring循环依赖问题的文章就介绍到这了,更多相关Spring循环依赖内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • Spring AOP 对象内部方法间的嵌套调用方式

    这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
  • Spring Cloud 中@FeignClient注解中的contextId属性详解

    这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25
  • Springboot如何实现Web系统License授权认证

    这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • 详解SpringCloudGateway内存泄漏问题

    这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
  • Spring为什么不推荐使用@Autowired注解详析

    @Autowired 注解的主要功能就是完成自动注入,使用也非常简单,但这篇文章主要给大家介绍了关于Spring为什么不推荐使用@Autowired注解的相关资料,需要的朋友可以参考下...2021-11-03
  • Springboot如何使用mybatis实现拦截SQL分页

    这篇文章主要介绍了Springboot使用mybatis实现拦截SQL分页,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-19
  • SpringMVC文件上传原理及实现过程解析

    这篇文章主要介绍了SpringMVC文件上传原理及实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-15
  • Spring Data JPA 关键字Exists的用法说明

    这篇文章主要介绍了Spring Data JPA 关键字Exists的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-10
  • tomcat启动完成执行 某个方法 定时任务(Spring)操作

    这篇文章主要介绍了tomcat启动完成执行 某个方法 定时任务(Spring)操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-25
  • 使用Maven 搭建 Spring MVC 本地部署Tomcat的详细教程

    这篇文章主要介绍了使用Maven 搭建 Spring MVC 本地部署Tomcat,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-08-16
  • Java Spring Cloud 负载均衡详解

    这篇文章主要介绍了Spring Cloud负载均衡及远程调用实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-09-18
  • SpringMvc自动装箱及GET请求参数原理解析

    这篇文章主要介绍了SpringMvc自动装箱及GET请求参数原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-19
  • SpringMvc获取请求头请求体消息过程解析

    这篇文章主要介绍了SpringMvc获取请求头请求体消息过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-17
  • Springboot使用thymeleaf动态模板实现刷新

    这篇文章主要介绍了Springboot使用thymeleaf动态模板实现刷新,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-31
  • Idea打包springboot项目没有.original文件解决方案

    这篇文章主要介绍了Idea打包springboot项目没有.original文件解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-26
  • spring boot 使用utf8mb4的操作

    这篇文章主要介绍了spring boot 使用utf8mb4的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-20
  • Springmvc ResponseBody响应json数据实现过程

    这篇文章主要介绍了Springmvc ResponseBody响应json数据实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-26
  • SpringData Repository接口用法解析

    这篇文章主要介绍了SpringData Repository接口用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-27
  • Spring boot 无法注入service问题

    这篇文章主要介绍了Spring boot 无法注入service问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-09