前言

相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的:

还会提示这么一句:

Requested bean is currently in creation: Is there an unresolvable circular reference?

老铁!这就是发生循环依赖了!

当然这里是一个异常情况。

在我的一篇文章中介绍如何避免 Spring 自调用事务失效,其中网友给建议,说可以在类中注入自身,然后调用,而注入自身的过程也是循环依赖的处理过程。

下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?

什么是循环依赖

Dependency Resolution Process

Spring IoC 容器会在运行时检测到构造函数注入循环引用,并抛出 BeanCurrentlyInCreationException。

所以要避免构造函数注入,可以使用 setter 注入替代。

根据官方文档说明,Spring 会自动解决基于 setter 注入的循环依赖。

当然在咱们工作中现在都使用 @Autowired 注解来注入属性。

PS: @Autowired 是通过反射进行赋值。

这里从我们最经常使用的场景切入,看 Spring 是如何解决循环依赖的?

代码

@Service
public class CircularServiceA { @Autowired
private CircularServiceB circularServiceB;
} @Service
public class CircularServiceB { @Autowired
private CircularServiceC circularServiceC;
} @Service
public class CircularServiceC { @Autowired
private CircularServiceA circularServiceA; }

这里有 A、B、C 三个类,可以看到发生了循环依赖:

但是即使发生了循环依赖,我们依然可以启动 OK,使用并没有任何影响。

Spring 是如何解决循环依赖的

Spring 单例 Bean 的创建 中介绍介绍了使用三级缓存。

singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

singletonFactories: 三级缓存,存储 singletonFactory。

当然,这里看着比较长,可以简化一下:

通过 Debug 来说明生成过程

从 preInstantiateSingletons 方法开始:

添加断点 beanName.equals("circularServiceA")

启动Debug:

会从缓存中获取单例 Bean

这里很显然获取不到,继续执行,创建单例实例

发现是单例再次获取

这里还会从一级缓存获取一次 circularServiceA , 没有获取到,将 circularServiceA 添加到在创建的池子里面 (singletonsCurrentlyInCreation 是一个 set 集合)。

然后会调用工厂方法 createBean(beanName, mbd, args) 创建对象。

在 createBean 中去实例化 Bean 。

判断是否是循环引用,是的话需要添加到三级缓存中。

circularServiceA 不在一级缓存中,则将 circularServiceA 的 singletonFactory 添加到 三级缓存 (singletonFactories) 中,同时从二级缓存中移除。

到这一步为止,circularServiceA 已经在三级缓存中了。

开始对 Bean 的属性进行赋值。

在 populateBean 方法中执行到

PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);

就会对属性进行赋值

在 injet 方法中,回去解决相关依赖。

继续 Debug ,发现解决依赖,最后发现其实又调用回 beanFactory.getBean(beanName);

不过这次创建的是 circularServiceB

下面是调用链:

circularServiceB 的过程和 circularServiceA 的一样,也是创建了三级缓存,然后去创建 circularServiceC

这时候三级缓存里面有它们三个的 singletonFactory 。

circularServiceC 也调用到 doGetBean 方法去获取 circularServiceA

不过这次 调用到 Object sharedInstance = getSingleton(beanName); 的时候, circularServiceA 已经存在了。

这次调用虽然没有从一级缓存 (singletonObjects) 中获取到 circularServiceA,但是 circularServiceA创建中,所以进入判断

在这里执行完之后, circularServiceA 从三级缓存升级到二级缓存

使用反射对 circularServiceC 中的 circularServiceA 进行赋值, 此时 circularServiceA 是在 二级缓存中。

那就比较好奇了,这时候 circularServiceC 里面的 circularServiceA 已经通过反射赋值,这个赋值给的是什么值?

查看代码:

这块是从三级缓存(singletonFactories)中获取的 singletonObject,然后调用

singletonObject = singletonFactory.getObject();

获取的一个对象

这里获取到的是 circularServiceA 的引用,注意 circularServiceA 这时候还没创建完成,只是引用。所以这里赋值的是 circularServiceA 的引用。

到这里 circularServiceC 就创建完了。

然后会将 C 添加到一级缓存和已注册列表中,同时从二级三级缓存中删除 C。

继续执行 B 和 A 的属性赋值以及后续的初始化流程。

至此,循环依赖解决完毕。

总结

Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:

  • singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

  • earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

  • singletonFactories: 三级缓存,存储 singletonFactory。

本文也通过 Debug 来验证了使用三级缓存解决依赖的过程。

不过还有一些问题没有说明:

  1. 循环依赖和代理之间的关系是什么?比如 @Transactional 和 @Async 注解会对循环依赖产生什么影响?
  2. 为什么要用三级缓存?二级缓存不可以么?

相关推荐

Spring 是如何解决循环依赖的?的更多相关文章

  1. Spring是如何解决循环依赖的

    前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过s ...

  2. spring: 我是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  3. 听说你还不知道Spring是如何解决循环依赖问题的?

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

  4. Spring三级缓存解决循环依赖

    前提知识 1.解决循环依赖的核心依据:实例化和初始化步骤是分开执行的 2.实现方式:三级缓存 3.lambda表达式的延迟执行特性 spring源码执行逻辑 核心方法refresh(), popula ...

  5. 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...

  6. Spring 如何解决循环依赖问题?

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...

  7. Spring ioc(4)---如何解决循环依赖

    前面说到对象的创建,那么在创建的过程中Spring是怎么又是如何解决循环依赖的呢.前面提到有个三级缓存.就是利用这个来解决循环依赖.打个比方说实例化A的时候,先将A创建(早期对象)放入一个池子中.这个 ...

  8. Spring解决循环依赖,你真的懂了吗?

    导读 前几天发表的文章SpringBoot多数据源动态切换和SpringBoot整合多数据源的巨坑中,提到了一个坑就是动态数据源添加@Primary接口就会造成循环依赖异常,如下图: 这个就是典型的构 ...

  9. Spring如何解决循环依赖,你真的懂了?

    导读 前几天发表的文章SpringBoot多数据源动态切换和SpringBoot整合多数据源的巨坑中,提到了一个坑就是动态数据源添加@Primary接口就会造成循环依赖异常,如下图: 这个就是典型的构 ...

随机推荐

  1. 对着爬虫网页HTML学习Python正则表达式re

    1.正则表达式初探 用比较经典的例子,查找一段文本中的手机号码.比如对于文本"我现在用的电话是188-8888-8888,之前那个186-6666-6666已经不用了",我们想获取 ...

  2. java基础:switch语句应用,循环的详细介绍以及使用,附练习案列

    1. switch语句 1.1 分支语句switch语句 格式 switch (表达式) { case 1: 语句体1; break; case 2: 语句体2; break; ... default ...

  3. [LeetCode]313. Super Ugly Number超级丑数,丑数系列看这一道就行了

    丑数系列的题看这一道就可以了 /* 和ugly number2差不多,不过这次的质因子多了,所以用数组来表示质因子的target坐标 target坐标指的是这个质因子此次要乘的前任丑数是谁 */ pu ...

  4. [leetcode]669. Trim a Binary Search Tree寻找范围内的二叉搜索树

    根据BST的特点,如果小于L就判断右子树,如果大于R就判断左子树 递归地建立树 public TreeNode trimBST(TreeNode root, int L, int R) { if (r ...

  5. 吞食鱼2(FeedingFrenzyTwo) 修改器

    吞食鱼2(FeedingFrenzyTwo) 修改器 童年回忆系列.小时候特别喜欢玩这类游戏,软件不大,很慢的网速也不会下载太久,然后对配置要求不高,很破的电脑也可以玩得很开心.不过也有糟心的时候啊, ...

  6. HTTP 常用状态码200 301 302 403 500

    200(OK):成功处理了请求. 301 redirect: 301 代表永久性转移(Permanently Moved) //助记 1 永恒,如果你记住了这一条就算这篇博客没白写.302 redir ...

  7. 二进制格式mysql

    1.二进制MySQL安装 #下载二进制格式的mysql软件包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.31- ...

  8. git 合并多次commit

    如果当前分支提交了多次commit,无法merge到master上,就需要合并多出的几次commit 第一步 查看log: git log 第二步 git rebase -i HEAD~2 对最近的两 ...

  9. [开源软件] 腾讯云Linux服务器一键安装LAMP/LNMP/LANMP环境 转

    本帖最后由 我本戏子 于 2015-8-13 22:00 编辑OneinStack是非常优秀的一键PHP/JAVA安装脚本,提供以下环境:lnmp(Linux + Nginx+ MySQL+ PHP) ...

  10. SQL操作符的优化

    操作符优化        IN 操作符 用IN写出来的SQL的优点是比较容易写及清晰易懂,这比较适合现代软件开发的风格. 但是用IN的SQL性能总是比较低的,从ORACLE执行的步骤来分析用IN的SQ ...