一. 什么是循环依赖

循环依赖,就是两个或则两个以上的bean互相依赖对方,最终形成闭环。比如“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;类似以下代码:

public class A {private B b;
}public class B {private A a;
}

常规情况下,会出现以下情况:

  1. 通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。
  2. A对象需要注入B对象,发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后,会放入对象缓存里)。
  3. 通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。
  4. B对象需要注入A对象,发现对象池里还没有A对象。
  5. 创建A对象,循环以上步骤。

Spring 循环依赖的场景有两种:

  1. 构造器的循环依赖。
  2. field 属性的循环依赖。

对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖

Spring 只解决 scope 为 singleton 的循环依赖。对于scope 为 prototype 的 bean ,Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。

二. 解决循环依赖

Spring创建Bean的过程中会用到三级缓存:

// DefaultSingletonBeanRegistry.java/*** 一级缓存,存放可用的成品Bean。* 对应关系为 bean name --> bean instance*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/*** 二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖。* 对应关系也是 bean name --> bean instance。*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);/*** 三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。* 对应关系是 bean name --> ObjectFactory*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

要了解解决循环依赖的原理,得从Bean的创建过程说起:

// AbstractBeanFactory.java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){//<1> 从缓存中或者实例工厂中获取 Bean 对象Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {...}else {....if (mbd.isSingleton()) {// <3> 将创建好的Bean实例放入一级缓存sharedInstance = getSingleton(beanName, () -> {try {// <2> bean 实例化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.// 显式从单例缓存中删除 Bean 实例// 因为单例模式下为了解决循环依赖,可能他已经存在了,所以销毁它。destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}...  }
}
  • <1> 首先尝试从三级缓存中获取实例

  • <2> 如果三级缓存都没有获取到bean实例,则说明需要创建该实例,则进行bean的实例化

  • <3> 将创建好的实例,放入一级缓存中

2.1 从缓存中获取Bean

从三级缓存中读取实例的代码如下:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.javaprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock// 从单例缓冲中加载 bean(一级缓存)Object singletonObject = this.singletonObjects.get(beanName);// 缓存中的 bean 为空,且当前 bean 正在创建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 二级缓存singletonObject = this.earlySingletonObjects.get(beanName);// 如果earlySingletonObjects 中没有,且允许提前创建if (singletonObject == null && allowEarlyReference) {// 加锁(双重校验锁模式)synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton lock// 拿到锁后再次获取单例实例singletonObject = this.singletonObjects.get(beanName);// 如果单例实例仍然为空if (singletonObject == null) {// 并且 早期单例对象的缓存 中也为空singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 从 singletonFactories 中获取对应的 ObjectFactory(三级缓存)ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//<1.1>// 获得 beansingletonObject = singletonFactory.getObject();// 添加 bean 到 早期单例对象的缓存 中this.earlySingletonObjects.put(beanName, singletonObject);// 从 singletonFactories 中移除对应的 ObjectFactorythis.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

<1.1>处,如果从三级缓存中获取到实例后,就会放入二级缓存,并删除三级缓存。

2.2 实例化Bean

<2>处,如果三级缓存都没有获取到bean实例,则说明需要创建该实例,则进行bean的实例化,代码如下:

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.java
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {....try {// <2.1> 创建 Bean 对象Object beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;}....
}

调用doCreateBean方法创建实例:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {...// <2.1.1> 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}...// <2.1.2> 判断是否有后置处理// 如果有后置处理,则允许后置处理修改 BeanDefinitionsynchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {// 后置处理修改 BeanDefinitionapplyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// <2.1.3> 解决单例模式的循环依赖boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 提前将创建的 bean 实例加入到 singletonFactories(三级缓存) 中 // 这里是为了后期避免循环依赖addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 开始初始化 bean 实例对象Object exposedObject = bean;try {// <2.1.4> 对 bean 进行填充,将各个属性值注入,其中,可能存在依赖于其他 bean 的属性// 则会递归初始依赖 beanpopulateBean(beanName, mbd, instanceWrapper);// <2.1.5> 调用初始化方法exposedObject = initializeBean(beanName, exposedObject, mbd);}...return exposedObject;
}

该方法整体流程比较复杂,详细逻辑可以参考 《创建Bean的流程》,但是整体流程如下:

  • <2.1.1> 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化
  • <2.1.2> 后置处理
  • <2.1.3> 判断是否提前曝光,如果提前曝光,则将当前Bean的ObjectFactory放入三级缓存中。
  • <2.1.4> 对 bean 进行填充,其中,如果依赖于其他 bean 的属性,则会递归调用getBean创建依赖的实例。
  • <2.1.5> 调用初始化方法

解决循环依赖非常关键的一步就是<2.1.3>,将当前刚创建好的Bean实例(未初始化),封装成ObjectFactory,放入三级缓存中:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 加入三级缓存this.singletonFactories.put(beanName, singletonFactory);// 删除二级缓存this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

2.3 将创建好的Bean实例放入一级缓存

<3>处,将创建好的实例,放入一级缓存中,代码如下:

 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");// 全局锁synchronized (this.singletonObjects) {...try {// <3.1> 初始化 bean// 这个过程其实是调用 createBean() 方法(策略模式,具体的创建逻辑由传入的ObjectFactory决定)singletonObject = singletonFactory.getObject();newSingleton = true;}...// <3.2> 加入一级缓存中if (newSingleton) {addSingleton(beanName, singletonObject);}return singletonObject;}}
  • <3.1>,调用<2>处的代码创建实例,详见 《2.2 实例化Bean》
  • <3.2>,由于<2>处的代码创建的实例已经是初始化完成的,所以在此处将Bean实例加入一级缓存中。

2.4 整体流程

2.5 循环依赖流程

我们以最简单的循环依赖为例,A、B两个类都互相依赖对方:

public class A {private B b;
}public class B {private A a;
}

假如Spring容器启动时,先加载A,那么A、B循环依赖初始化流程如下图所示:

本文参考至:

Spring为什么不使用二级缓存?Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?… - 知乎 (zhihu.com)

Spring循环依赖三级缓存是否可以去掉第三级缓存? - SegmentFault 思否

详解Spring循环依赖相关推荐

  1. Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)

    总览 问题 详解 String.intern()的作用 link LeetCode的Two Sum题 link 什么是可重入锁? link 谈谈LockSupport link 谈谈AQS link ...

  2. Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3

    Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3 总览 问题 详解 String.intern()的作用 link LeetCode的Two Sum题 ...

  3. Spring循环依赖和三级缓存详解

    Spring循环依赖和三级缓存详解 Spring在启动过程中,使用到了三个map,称为三级缓存 我们可以这样理解,假设,我们只有一个缓存容器,并且缓存是直接开放给用户可以调用的,如果将未完成赋值的Be ...

  4. Spring循环依赖详解

    Spring循环依赖详解 什么是循环依赖 spring是如何解决循环依赖 循环源码分析 getSingletion方法 getSingleton spring开启代理对象的地方 循环依赖的限制条件 什 ...

  5. 详解spring的IOC控制反转和DI依赖注入

    转载 详解spring的IOC控制反转和DI依赖注入 2018-06-05 15:45:34 jiuqijack 阅读数 2945 文章标签: spring IOC控制反转 DI依赖注入 更多 分类专 ...

  6. Spring循环依赖问题,Spring是如何解决循环依赖的?

    文章目录 一.什么是循环依赖 1.代码实例 2.重要信息 二.源码分析 1.初始化Student 对Student中的ClassRoom进行Autowire操作 2.Student的自动注入Class ...

  7. 详解spring事务失效和回滚失败的场景

    详解spring事务失效和回滚失败的场景 详解spring事务失效和回滚失败的场景 前言 一 .事务不生效 1.访问权限问题 2. 方法用final修饰 3.方法的内部调用 3.1 新加一个Servi ...

  8. Spring框架学习教程,详解Spring注入bean的几种方式

    首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入. 依赖注入是指:让调用类对某一接口的实现类的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖. Sprin ...

  9. 这个Spring循环依赖的坑,90%以上的人都不知道

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Mythsman 原文:https://blog.myths ...

最新文章

  1. 负载均衡算法-最少连接数均衡
  2. 一篇文章带你了解https是如何做到客户端与服务端之间安全通信
  3. Mysql 修改 复杂的汇总_MySQL数据分析:复杂查询
  4. 2019ICPC(银川) - Largest Common Submatrix(单调栈)
  5. 点击按钮打开选择文件对话框
  6. 偷梁换柱 | 无备份情况下的数据恢复实践
  7. STM32工作笔记002---STM32初探-概述
  8. HTML中的table和div
  9. 【java.lang.UnsupportedClassVersionError】问题的解决方法
  10. linux 删除压缩包_【干货】记住!这些 Linux 命令千万不要运行!
  11. Linux内存管理之mmap
  12. matlab亮度对比度调节公式,图像编辑之对比度调整(亮度对比度的算法公式)
  13. c语言学习体会300字,大一个人总结自我评价300字_大学生学年自我鉴定评价
  14. 债券价格和到期收益率的关系_债券价格与到期收益率之间的关系.PPT
  15. 会议室预订小程序,共享会议室小程序,微信小程序会议室预约系统毕设作品
  16. python学习第一课——环境配置+比着葫芦画个瓢
  17. clickhouse--Window Functions 窗口函数概念讲解及实际使用示例
  18. 数据源——中青数据百融
  19. 高新技术企业申报流程
  20. 互联网、因特网以及万维网之间的区别和联系

热门文章

  1. 当代大学生想进入IT行业,要做好哪些准备呢?
  2. MVC和MVVC的一些区别
  3. AI落地的“摆渡者”,RPA如何成为企业降本增效的得力助手?
  4. 我在MIT人工智能研究实验室工作一年学到的 5 件事
  5. python安装prosail库及Fortran编译器下载与配置
  6. 英语计算机测评,一种英语翻译结合计算机测评录入平台的制作方法
  7. MySQL数据库产品体系结构的安全问题
  8. 【算法千题案例】每日一练LeetCode打卡——105.仅仅反转字母
  9. 目标检测中图片预处理之图片大小分析
  10. 标准差和方差为什么除n-1