读完这篇文章你将会收获到

  • Spring 循环依赖可以分为哪两种
  • Spring 如何解决 setter 循环依赖
  • Spring 为何是三级缓存 , 二级不行 ?
  • Spring 为啥不能解决构造器循环依赖

概述

循环依赖就是循环引用,两个或以上的 bean 相互持有对方。比如说 beanA 引用 beanB , beanB 引用 beanCbeanC 引用 beanA , 它们之间的引用关系构成一个环。

Spring 如何解决循环依赖

Spring 中的循环依赖包括

  • 构造器循环依赖
  • setter 循环依赖

构造器的依赖

Spring 对于构造器的依赖、无法解决。只会抛出 BeanCurrentlyInCreationException 异常。

	protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

setter 的循环依赖

不管是 autowireByName 还是 autowireByType 都是属于这种。Spring 默认是能够解决这种循环依赖的,主要是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤的 bean 来完成的。而且只能解决 singleton 类型的循环依赖、对于 prototype 类型的是不支持的,因为 Spring 没有缓存这种类型的 bean

Spring 是如何解决的

其实很简单、在 Spring 获取单例流程(一) 中我们曾提及过三级缓存

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName);
// 这个bean 正处于 创建阶段
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 并发控制
synchronized (this.singletonObjects) {
// 单例缓存是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否运行获取 bean factory 创建出的 bean
if (singletonObject == null && allowEarlyReference) {
// 获取缓存中的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将对象缓存到 earlySingletonObject中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从工厂缓冲中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

Spring 解决 setter 循环依赖的关键点就是在这里,主要是 singletonFactories 这个 Map

我们可以先梳理一下整体的流程

beanA --> beanB --> beanC -->beanA

以上面为例子、我们先假设它们是构造器的循环依赖

  1. Spring 初始化完成之后、接收到一个 getBean 的调用请求、请求 beanA
  2. Spring 发现三级缓存中都没有 beanA 的存在、所以开始创建 beanA 的流程
  3. beanA 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanA 正在创建中
  4. 兜兜转转,发现我要 new 一个 beanA 的对象、我要先获得一个 beanB 的对象、好、我们就进行一个 getBean(beanB)
  5. Spring 发现三级缓存中都没有 beanB 的存在、所以开始创建 beanB 的流程
  6. beanB 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanB 正在创建中
  7. 兜兜转转,发现我要 new 一个 beanB 的对象、我要先获得一个 beanC 的对象、好、我们就进行一个 getBean(beanC)
  8. Spring 发现三级缓存中都没有 beanC 的存在、所以开始创建 beanC 的流程
  9. beanC 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanC 正在创建中
  10. 兜兜转转,发现我要 new 一个 beanC 的对象、我要先获得一个 beanA 的对象、好、我们就进行一个 getBean(beanA)
  11. Spring 发现三级缓存中都没有 beanA 的存在、所以开始创建 beanA 的流程
  12. beanA 放入到 singletonsCurrentlyInCreation 集合中去、但是在这个时候、插入到集合中失败、直接抛出异常

而假如我们是一个 setter 的循环依赖

  1. Spring 初始化完成之后、接收到一个 getBean 的调用请求、请求 beanA
  2. 先判断三级缓存中有没有 beanA ,如果没有则往下进行
  3. beanA 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanA 正在创建中
  4. 兜兜转转,终于创建了一个 beanA , 但是这个时候的 beanA 是一个不完整的状态、因为很多属性没有被赋值、比如说 beanA 中的成员变量 beanB 现在还是一个 null 的状态
  5. 然后判断是否需要将当前创建的不完整的 beanA 加入到第三级缓存中,正常来说都是会被加入到第三级缓存中的
  6. 加入第三级缓存以后、进行一个属性填充,这个时候发现需要填充一个 beanB 对象
  7. 然后如上面那样、先看看三级缓存有没有 beanB ,如果没有则创建一个并不完整的 beanB、然后加入到第三级缓存中、然后发现需要填充一个 beanC 的属性
  8. 然后如上面那样、先看看三级缓存有没有 beanC ,如果没有则创建一个并不完整的 beanC、然后加入到第三级缓存中、然后发现需要填充一个 beanA 的属性
  9. 这个时候,先看看三级缓存中有没有 beanA ,发现在第三级缓冲中有不完整的 beanA、将其从第三级缓存中移除出来、放入到第二级缓存中,然后返回给 beanC 用于填充属性
  10. 然后 beanC 的 属性填充完毕,则将其从 singletonsCurrentlyInCreation 集合中移除掉,代表 beanC 已经真正的创建好了
  11. 然后将 beanC 加入到第一级缓存中,并将其从第三级缓存中移除,并返回给 beanBbeanB 也如 beanC 那样处理
  12. beanA 也如 beanBbeanC 那样处理、加入到第一级缓存中、然后从第二级缓存中移除
  13. 结束

其实上面的屁话又长又臭,但是流程还是非常简单的

为啥是三级缓存,二级不行吗?

/**
* Cache of singleton objects: bean name to bean instance.
* 存放的是单例 bean、对应关系是 bean Name --> bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Cache of early singleton objects: bean name to bean instance.
* 存放的早期的 bean、对应的关系 也是 beanName --> bean instance
* 与 singletonObjects 区别在于 earlySingletonObjects 中存放的bean 不一定是完整的、
* bean 在创建过程中就加入到 earlySingletonObjects 中了、所以在bean创建过程中就可以通过getBean 方法获取、
* 这个Map 也是解决循环依赖的关键所在
**/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /**
* Cache of singleton factories: bean name to ObjectFactory.
* 存放的是 ObjectFactory 、可以理解为创建单例bean的factory、对应关系是 bean name --> objectFactory
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

我们来看看从第三级缓存升级到第二级缓存究竟发生了什么

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
// 默认实现
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}

其实只要有二级缓存也是可以的,虽然可以达到解决 setter 循环依赖的问题、但是却无法给用户提供一个扩展接口(当存在循环依赖的)。

就好比说、上面的例子、在循环依赖的关系中,当 beanA第三级缓存升级到第二级缓存的时候,我们可以在其升级的时候去设置一些 beanA 的属性或者做一些其他事情,我们只需要在 beanA 的类中实现 SmartInstantiationAwareBeanPostProcessor 接口即可

但是单纯只有二级缓存的话,当我们创建好一个没有完成初始化的 bean 的时候、要么就直接调用 ObjectFactorygetObject 方法获取经过回调的 bean 放入到第二级缓存(不管这个 bean 存不存在一个循环引用的关系链中),要么就直接放刚刚创建好的没有完成初始化的 bean 放入到第二级缓存。无论是哪种情况,都无法达到这样一个需求:当存在循环依赖的时候,我们作为用户需要对其进行一些设置或者一些其他的操作

为啥不能解决构造函数的循环依赖

如果按照解决 setter 循环依赖的流程、是否能够解决?先将一个不完整的 bean 放入到第三级缓存中,然后提供出去给其他 bean 依赖。但是呢,问题是我无法创建出这么一个不完整的 bean 在一个构造函数依赖的关系中,参数不全,再牛皮也不能把

Spring — 循环依赖的更多相关文章

  1. spring循环依赖问题分析

    新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...

  2. Spring 循环依赖

    循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...

  3. Springboot源码分析之Spring循环依赖揭秘

    摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...

  4. Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)

    本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...

  5. Spring循环依赖的解决

    ## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...

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

    1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...

  7. 帮助你更好的理解Spring循环依赖

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  8. spring 循环依赖的一次 理解

    前言: 在看spring 循环依赖的问题中,知道原理,网上一堆的资料有讲原理. 但今天在看代码过程中,又产生了疑问. 疑问点如下: // 疑问点: 先进行 dependon 判断String[] de ...

  9. 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

    本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...

随机推荐

  1. Java实现 LeetCode 609 在系统中查找重复文件(阅读理解+暴力大法)

    609. 在系统中查找重复文件 给定一个目录信息列表,包括目录路径,以及该目录中的所有包含内容的文件,您需要找到文件系统中的所有重复文件组的路径.一组重复的文件至少包括二个具有完全相同内容的文件. 输 ...

  2. Java实现 LeetCode 443 压缩字符串

    443. 压缩字符串 给定一组字符,使用原地算法将其压缩. 压缩后的长度必须始终小于或等于原数组长度. 数组的每个元素应该是长度为1 的字符(不是 int 整数类型). 在完成原地修改输入数组后,返回 ...

  3. Java实现 蓝桥杯VIP 算法训练 暗恋

    算法训练 暗恋 时间限制:1.0s 内存限制:256.0MB 问题描述 同在一个高中,他却不敢去找她,虽然在别人看来,那是再简单不过的事.暗恋,是他唯一能做的事.他只能在每天课间操的时候,望望她的位置 ...

  4. Java实现 LeetCode 165 比较版本号

    165. 比较版本号 比较两个版本号 version1 和 version2. 如果 version1 > version2 返回 1,如果 version1 < version2 返回 ...

  5. Java实现 LeetCode 87 扰乱字符串

    87. 扰乱字符串 给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树. 下图是字符串 s1 = "great" 的一种可能的表示形式. grea ...

  6. IDEA突然无法运行

    可能是你类的main方法被idea的智能提示改了 PS: 小编经常用智能提示,它给我把main方法的static关键字删掉了好几次,当时怎么也没想到是把main方法改了 ~难受

  7. java实现第五届蓝桥杯供水设施

    供水设施 X星球的居民点很多.Pear决定修建一个浩大的水利工程,以解决他管辖的N个居民点的供水问题.现在一共有N个水塔,同时也有N个居民点,居民点在北侧从1号到N号自西向东排成一排:水塔在南侧也从1 ...

  8. Python接口自动化测试脚本-实现禅道登录

    未来应用方向:UI自动化测试或接口自动化测试发现的Bug可自动录入禅道,带截图与相关报错信息. #!/usr/bin/env python # -*- coding: UTF-8 -*- '''=== ...

  9. Mysql(Mariadb)数据库主从

    Mysql主从复制的实现原理图大致如下: MySQL之间数据复制的基础是以二进制日志文件(binary log file)来实现的,一台MySQL数据库一旦启用二进制日志后,其作为master,它数据 ...

  10. 第一章04-Activity中常用的标志位

    Activity的LaunchMode Android中提供了四中Activity的启动模式 1. standard 2. singleTop 3. singleTask 4. signleInsta ...