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

  • 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. (一)用less+gulp+requireJs 搭建项目(了解less)

    项目完结 做个总结: 公司网站重构,整站都需要重写,终于有机会接触下 less,gulp和requireJs,因为以前的工作就是写几个活动页,并没有机会用这些工具,废话不多说,先看下完成后的项目目录: ...

  2. Java实现 LeetCode 640 求解方程(计算器的加减法计算)

    640. 求解方程 求解一个给定的方程,将x以字符串"x=#value"的形式返回.该方程仅包含'+',' - '操作,变量 x 和其对应系数. 如果方程没有解,请返回" ...

  3. JavaScript实现登录滑动验证

    来自于GitHub, 如何快速访问GitHub 先附上效果图 划到一半停止回自己回去的 PS: 附上代码,有需要自己更改, <!DOCTYPE html> <html lang=&q ...

  4. Java实现 LeetCode 242 有效的字母异位词

    242. 有效的字母异位词 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词. 示例 1: 输入: s = "anagram", t = " ...

  5. Java实现微生物增殖

    微生物增殖 假设有两种微生物 X 和 Y X出生后每隔3分钟分裂一次(数目加倍),Y出生后每隔2分钟分裂一次(数目加倍). 一个新出生的X,半分钟之后吃掉1个Y,并且,从此开始,每隔1分钟吃1个Y. ...

  6. CentOS7.6操作系统安装实例以及Linux版本、哲学思想介绍

    Linux起源1991年的10月5日,Torvalds在comp.os.minix新闻组上发布消息,正式向外宣布他自行编写的完全自由免费的内核诞生(Freeminix-like kernel sour ...

  7. SpringBoot爬坑系列

    1.日志篇 现象 由于日志配置采用原来SpringMVC项目中的log4j.properties 文件,日志采用springboot自带的jar包会出现打印不出日志的情况. 解决 引入原日志包 < ...

  8. <WP8开发学习笔记>获取手机的常用型号(如Lumia920,而非RM-822)

    之前WP7时代可以用API获得WP手机的型号如lumia510,但是到了WP8后用APi只能获得硬件版本号了如RM-822,这种型号可以让我们更详细的了解具体的硬件版本,比如国行和港行,设备版本号不一 ...

  9. 懒羊羊找朋友(struct实现优先排序)

    4907: 懒羊羊找朋友(点击) 时间限制: 1 Sec  内存限制: 128 MB                                                           ...

  10. Selenium和ChromeDriver的安装与配置

    安装安装selenium: win: pip install seleniumliunx: pip3 install selenium12安装ChromeDriver, 该工具供selenium使用C ...