一. 什么是循环依赖

循环依赖,就是两个或则两个以上的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.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循环依赖三级缓存是否可以去掉第三级缓存? - SegmentFault 思否

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

  1. 使用IDEA详解Spring中依赖注入的类型(上)

    使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...

  2. 详解Spring DI循环依赖实现机制

    一个对象引用另一个对象递归注入属性即可实现后续的实例化,同时如果两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环即所谓的循环依赖怎么实现呢属性的互相注入呢? Spring bean生命周期具体 ...

  3. 【转】详解spring 每个jar的作用

    spring.jar 是包含有完整发布模块的单个jar 包.但是不包括mock.jar, aspects.jar, spring-portlet.jar, and spring-hibernate2. ...

  4. 用IDEA详解Spring中的IoC和DI(挺透彻的,点进来看看吧)

    用IDEA详解Spring中的IoC和DI 一.Spring IoC的基本概念 控制反转(IoC)是一个比较抽象的概念,它主要用来消减计算机程序的耦合问题,是Spring框架的核心.依赖注入(DI)是 ...

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

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

  6. applicationContext.xml详解 spring+mybatis+struts

    今天给大家详细解释一项关于Spring的applicationContext.xml文件,这对于初学者来说,应该是很有帮助的, 以下是详解Spring的applicationContext.xml文件 ...

  7. spring循环依赖问题分析

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

  8. spring原理案例-基本项目搭建 02 spring jar包详解 spring jar包的用途

    Spring4 Jar包详解 SpringJava Spring AOP: Spring的面向切面编程,提供AOP(面向切面编程)的实现 Spring Aspects: Spring提供的对Aspec ...

  9. Spring 循环依赖

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

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

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

随机推荐

  1. CentOS升级内核-- CentOS9 Stream/CentOS8 Stream/CentOS7

    官方文档在此 升级原因 当我们安装一些软件(对,我说的就是Kubernetes),可能需要新内核的支持,而CentOS又比较保守,不太升级,所以需要我们手工升级. # 看下目前是什么版本内核 unam ...

  2. c# countDownEvent类

    前言 把异步先总结完吧. countDownEvent 这东西是干什么的呢? 比如说我们比赛跑步,我们需要得出的是第一二三名得出后就可以先统计出来,因为比较重要,后面没有获得获奖名次的可以后续统计出来 ...

  3. szfpga Lattice高速下载器HW-USBN-2B 常见问题解答

    .产品特点 1). 支持windows7,Windows10 操作系统,两个操作系统非常稳定不断线. 2). 支持JTAG 模式,速度快,最高30Mb/s,调试serdes core,不会像hw-us ...

  4. 深入分析C++对象模型之移动构造函数

    接下来我将持续更新"深度解读<深度探索C++对象模型>"系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表. C++11新标准 ...

  5. 力扣237(java&python)-删除链表中的节点(中等)

    题目: 有一个单链表的 head,我们想删除它其中的一个节点 node. 给你一个需要删除的节点 node .你将 无法访问 第一个节点  head. 链表的所有值都是 唯一的,并且保证给定的节点 n ...

  6. 大型企业数据库服务首选,AliSQL这几大企业级功能你了解几个?

    MySQL代表了开源数据库的快速发展,从2004年前后的Wiki.WordPress等轻量级Web 2.0应用起步,到2010年阿里巴巴在电商及支付场景大规模使用MySQL数据库,再到2012年开始阿 ...

  7. 贾扬清演讲实录:一个AI开发者的奇幻漂流

    ​简介:2021阿里灵杰AI工程化峰会,贾扬清深度解读阿里灵杰大数据和AI一体化平台. 演讲人:贾扬清 演讲主题:一个AI开发者的奇幻漂流 活动:2021阿里灵杰AI工程化峰会 对于绝大多数人来说,这 ...

  8. [Docker] 假如宿主机 Nginx 代理到 Docker 的 PHP

    其实没有多少区别,同样 php 镜像启动服务暴露一个端口,nginx 的 proxy_pass 代理过去,唯一要注意的是 nginx 配置的项目路径. nginx 配置的 root 是本地项目路径,给 ...

  9. linux应用程序开发入门

    在上一篇文章中学习了linux字符驱动的开发,需要使用应用程序对完成的驱动进行验证,现在开始学习应用程序的开发. 一.准备材料 开发环境:VMware 操作系统:ubuntu 开发版:湃兔i2S-6U ...

  10. vue+element设置选择日期最大范围(普通版)

    效果是只能跟当天时间有关(30天),下一篇将来的任意时段,比较符合实际 <!DOCTYPE html> <html> <head> <meta charset ...