Spring — 循环依赖
读完这篇文章你将会收获到
Spring循环依赖可以分为哪两种Spring如何解决setter循环依赖Spring为何是三级缓存 , 二级不行 ?Spring为啥不能解决构造器循环依赖
概述
循环依赖就是循环引用,两个或以上的 bean 相互持有对方。比如说 beanA 引用 beanB , beanB 引用 beanC , beanC 引用 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
以上面为例子、我们先假设它们是构造器的循环依赖
Spring初始化完成之后、接收到一个getBean的调用请求、请求beanASpring发现三级缓存中都没有beanA的存在、所以开始创建beanA的流程- 将
beanA放入到singletonsCurrentlyInCreation集合中去、代表着beanA正在创建中 - 兜兜转转,发现我要
new一个beanA的对象、我要先获得一个beanB的对象、好、我们就进行一个getBean(beanB) Spring发现三级缓存中都没有beanB的存在、所以开始创建beanB的流程- 将
beanB放入到singletonsCurrentlyInCreation集合中去、代表着beanB正在创建中 - 兜兜转转,发现我要
new一个beanB的对象、我要先获得一个beanC的对象、好、我们就进行一个getBean(beanC) Spring发现三级缓存中都没有beanC的存在、所以开始创建beanC的流程- 将
beanC放入到singletonsCurrentlyInCreation集合中去、代表着beanC正在创建中 - 兜兜转转,发现我要
new一个beanC的对象、我要先获得一个beanA的对象、好、我们就进行一个getBean(beanA) Spring发现三级缓存中都没有beanA的存在、所以开始创建beanA的流程- 将
beanA放入到singletonsCurrentlyInCreation集合中去、但是在这个时候、插入到集合中失败、直接抛出异常
而假如我们是一个 setter 的循环依赖
Spring初始化完成之后、接收到一个getBean的调用请求、请求beanA- 先判断三级缓存中有没有
beanA,如果没有则往下进行 - 将
beanA放入到singletonsCurrentlyInCreation集合中去、代表着beanA正在创建中 - 兜兜转转,终于创建了一个
beanA, 但是这个时候的beanA是一个不完整的状态、因为很多属性没有被赋值、比如说beanA中的成员变量beanB现在还是一个null的状态 - 然后判断是否需要将当前创建的不完整的
beanA加入到第三级缓存中,正常来说都是会被加入到第三级缓存中的 - 加入第三级缓存以后、进行一个属性填充,这个时候发现需要填充一个
beanB对象 - 然后如上面那样、先看看三级缓存有没有
beanB,如果没有则创建一个并不完整的beanB、然后加入到第三级缓存中、然后发现需要填充一个beanC的属性 - 然后如上面那样、先看看三级缓存有没有
beanC,如果没有则创建一个并不完整的beanC、然后加入到第三级缓存中、然后发现需要填充一个beanA的属性 - 这个时候,先看看三级缓存中有没有
beanA,发现在第三级缓冲中有不完整的beanA、将其从第三级缓存中移除出来、放入到第二级缓存中,然后返回给beanC用于填充属性 - 然后
beanC的 属性填充完毕,则将其从singletonsCurrentlyInCreation集合中移除掉,代表beanC已经真正的创建好了 - 然后将
beanC加入到第一级缓存中,并将其从第三级缓存中移除,并返回给beanB,beanB也如beanC那样处理 beanA也如beanB、beanC那样处理、加入到第一级缓存中、然后从第二级缓存中移除- 结束
其实上面的屁话又长又臭,但是流程还是非常简单的




为啥是三级缓存,二级不行吗?
/**
* 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 的时候、要么就直接调用 ObjectFactory 的 getObject 方法获取经过回调的 bean 放入到第二级缓存(不管这个 bean 存不存在一个循环引用的关系链中),要么就直接放刚刚创建好的没有完成初始化的 bean 放入到第二级缓存。无论是哪种情况,都无法达到这样一个需求:当存在循环依赖的时候,我们作为用户需要对其进行一些设置或者一些其他的操作
为啥不能解决构造函数的循环依赖
如果按照解决 setter 循环依赖的流程、是否能够解决?先将一个不完整的 bean 放入到第三级缓存中,然后提供出去给其他 bean 依赖。但是呢,问题是我无法创建出这么一个不完整的 bean 在一个构造函数依赖的关系中,参数不全,再牛皮也不能把


Spring — 循环依赖的更多相关文章
- spring循环依赖问题分析
新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...
- Spring 循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...
- Springboot源码分析之Spring循环依赖揭秘
摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...
- Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)
本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...
- Spring循环依赖的解决
## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...
- 这个 Spring 循环依赖的坑,90% 以上的人都不知道
1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...
- 帮助你更好的理解Spring循环依赖
网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...
- spring 循环依赖的一次 理解
前言: 在看spring 循环依赖的问题中,知道原理,网上一堆的资料有讲原理. 但今天在看代码过程中,又产生了疑问. 疑问点如下: // 疑问点: 先进行 dependon 判断String[] de ...
- 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...
随机推荐
- Java实现 蓝桥杯 算法训练 Pollution Solution
试题 算法训练 Pollution Solution 问题描述 作为水污染管理部门的一名雇员,你需要监控那些被有意无意倒入河流.湖泊和海洋的污染物.你的其中一项工作就是估计污染物对不同的水生态系统(珊 ...
- Java实现 LeetCode 999 车的可用捕获量(简单搜索)
999. 车的可用捕获量 在一个 8 x 8 的棋盘上,有一个白色车(rook).也可能有空方块,白色的象(bishop)和黑色的卒(pawn).它们分别以字符 "R"," ...
- Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
542. 01 矩阵 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离. 两个相邻元素间的距离为 1 . 示例 1: 输入: 0 0 0 0 1 0 0 0 0 输出: 0 0 0 ...
- Java实现 LeetCode 445 两数相加 II
445. 两数相加 II 给定两个非空链表来代表两个非负整数.数字最高位位于链表开始位置.它们的每个节点只存储单个数字.将这两数相加会返回一个新的链表. 你可以假设除了数字 0 之外,这两个数字都不会 ...
- Java实现蓝桥杯模拟树的叶结点数量
问题描述 一棵包含有2019个结点的树,最多包含多少个叶结点? 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分. ...
- Java实现 蓝桥杯VIP 算法训练 连接字符串
算法训练 连接字符串 时间限制:1.0s 内存限制:512.0MB 编程将两个字符串连接起来.例如country与side相连接成为countryside. 输入两行,每行一个字符串(只包含小写字母, ...
- java实现串逐位和(C++)
给定一个由数字组成的字符串,我们希望得到它的各个数位的和. 比如:"368" 的诸位和是:17 这本来很容易,但为了充分发挥计算机多核的优势,小明设计了如下的方案: int f(c ...
- Java实现第八届蓝桥杯购物单
购物单 题目描述 小明刚刚找到工作,老板人很好,只是老板夫人很爱购物.老板忙的时候经常让小明帮忙到商场代为购物.小明很厌烦,但又不好推辞. 这不,XX大促销又来了!老板夫人开出了长长的购物单,都是有打 ...
- 【Spring注解驱动开发】自定义TypeFilter指定@ComponentScan注解的过滤规则
写在前面 Spring的强大之处不仅仅是提供了IOC容器,能够通过过滤规则指定排除和只包含哪些组件,它还能够通过自定义TypeFilter来指定过滤规则.如果Spring内置的过滤规则不能够满足我们的 ...
- 04.Java基础语法
一.Java源程序结构与编程规范 一个完整的Java源程序应该包含下列部分 package语句,至多一句,必须放在源程序第一句 import语句,没有或者若干句,必须放在所有类定义前 public c ...