详解Spring循环依赖
一. 什么是循环依赖
循环依赖
,就是两个或则两个以上的bean
互相依赖对方,最终形成闭环
。比如“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;类似以下代码:
public class A {
private B b;
}
public class B {
private A a;
}
常规情况下,会出现以下情况:
- 通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。
- A对象需要注入B对象,发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后,会放入对象缓存里)。
- 通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。
- B对象需要注入A对象,发现对象池里还没有A对象。
- 创建A对象,循环以上步骤。
Spring 循环依赖的场景有两种:
- 构造器的循环依赖。
- 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.java
protected 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>
// 获得 bean
singletonObject = singletonFactory.getObject();
// 添加 bean 到 早期单例对象的缓存 中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从 singletonFactories 中移除对应的 ObjectFactory
this.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> 判断是否有后置处理
// 如果有后置处理,则允许后置处理修改 BeanDefinition
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
// 后置处理修改 BeanDefinition
applyMergedBeanDefinitionPostProcessors(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 的属性
// 则会递归初始依赖 bean
populateBean(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循环依赖的更多相关文章
- 使用IDEA详解Spring中依赖注入的类型(上)
使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...
- 详解Spring DI循环依赖实现机制
一个对象引用另一个对象递归注入属性即可实现后续的实例化,同时如果两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环即所谓的循环依赖怎么实现呢属性的互相注入呢? Spring bean生命周期具体 ...
- 【转】详解spring 每个jar的作用
spring.jar 是包含有完整发布模块的单个jar 包.但是不包括mock.jar, aspects.jar, spring-portlet.jar, and spring-hibernate2. ...
- 用IDEA详解Spring中的IoC和DI(挺透彻的,点进来看看吧)
用IDEA详解Spring中的IoC和DI 一.Spring IoC的基本概念 控制反转(IoC)是一个比较抽象的概念,它主要用来消减计算机程序的耦合问题,是Spring框架的核心.依赖注入(DI)是 ...
- 这个 Spring 循环依赖的坑,90% 以上的人都不知道
1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...
- applicationContext.xml详解 spring+mybatis+struts
今天给大家详细解释一项关于Spring的applicationContext.xml文件,这对于初学者来说,应该是很有帮助的, 以下是详解Spring的applicationContext.xml文件 ...
- spring循环依赖问题分析
新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...
- spring原理案例-基本项目搭建 02 spring jar包详解 spring jar包的用途
Spring4 Jar包详解 SpringJava Spring AOP: Spring的面向切面编程,提供AOP(面向切面编程)的实现 Spring Aspects: Spring提供的对Aspec ...
- Spring 循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...
- Springboot源码分析之Spring循环依赖揭秘
摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...
随机推荐
- Android 开发入门(5)
0x07 数据存储 (1)共享参数 SharedPreferences a. 用法 用法 SharedPreferences 是 Android 的一个轻量级存储工具,采用的存储结构为键值对的方式 共 ...
- 润乾报表如何从 mongodb 中取数
MongoDB 属于 NoSql 中的基于分布式文件存储的文档型数据库,是非关系数据库当中功能最丰富,最像关系数据库的.它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较 ...
- 重新点亮shell————awk 控制语句[十三]
前言 简单介绍一下控制语句. 正文 例子1: 例子2: 例子3 for循环: 例子4, sum会复用: 同样,其他的while 和 do while 也是可以在awk中使用的. 结 下一节awk数组.
- 痞子衡嵌入式:在i.MXRT1xxx系列上用NAND型启动设备时可用两级设计缩短启动时间
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在i.MXRT1xxx系列上用NAND型启动设备时可用两级设计缩短启动时间. 去年痞子衡写过一篇骚操作文章 <借助i.MXRT10 ...
- CF1832B Maximum Sum 题解
[题目描述] 给定一个长度为 \(n\) 的数列,其中每个元素互不相同,进行 \(k\) 次操作,每次可以选择删除序列中最小的两个数或最大的一个数.求操作后剩余数的和的最大值. [思路] 我们构造一组 ...
- Apache Flink 在京东的实践与优化
简介: Flink 助力京东实时计算平台朝着批流一体的方向演进. 本文整理自京东高级技术专家付海涛在 Flink Forward Asia 2020 分享的议题<Apache Flink 在京 ...
- dotnet 读 WPF 源代码笔记 为什么自定义的 UserControl 用户控件不能跨程序集继承
从设计上,用户控件 UserControl 就不是一个合适用来多次继承的类型,更不要说进行跨程序集继承自定义的 UserControl 用户控件.对于大部分的用户控件来说,都是采用组合现有的控件来实现 ...
- WPF 布局 在有限空间内让两个元素尽可能撑开的例子
我在尝试写一个显示本机 WIFI 热点的账号和密码的控件,要求此控件在有限的空间内显示.但是尽可能显示出热点的账号和密码.而热点的账号和密码是用户配置的,也许长度很长.我的需求是在假如账号的长度较短的 ...
- K8s控制器(8)---Deployment
一.Deployment控制器概念.原理解读 1.1 Deployment概述 # Deployment官方文档 https://kubernetes.io/docs/concepts/workloa ...
- navicat15安装以及破解
一. 下载 链接:https://pan.baidu.com/s/173rqp-DZJ3Om_QNN0NxbEg 提取码:zop2 二. 安装 2.1 解压刚才的文件 2.2 安装navicat15. ...