Spring 是如何解决循环依赖的?
前言
相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的:
还会提示这么一句:
Requested bean is currently in creation: Is there an unresolvable circular reference?
老铁!这就是发生循环依赖了!
当然这里是一个异常情况。
在我的一篇文章中介绍如何避免 Spring 自调用事务失效,其中网友给建议,说可以在类中注入自身,然后调用,而注入自身的过程也是循环依赖的处理过程。
下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?
什么是循环依赖
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 来验证了使用三级缓存解决依赖的过程。
不过还有一些问题没有说明:
- 循环依赖和代理之间的关系是什么?比如 @Transactional 和 @Async 注解会对循环依赖产生什么影响?
- 为什么要用三级缓存?二级缓存不可以么?
相关推荐
- Spring 源码学习 16:单例 Bean 创建
- Spring 源码学习 15:finishBeanFactoryInitialization(重点)
- Spring 源码学习 14:initApplicationEventMulticaster
最新文章
- nyist 593 Take it easy
- iConvert Icons 图标转换生成利器,支持Windows, Mac OS X, Linux, iOS,和Android等系统
- 二分+最短路 uvalive 3270 Simplified GSM Network(推荐)
- 传输层之TCP
- 【2013长沙区域赛】部分题解 hdu4791—4801
- 存储过程中update,然后用sql%判断update是否成功的存储过程
- [置顶] 利用CXF发布webService的小demo
- Android中怎样获取SD卡路径
- OSINT系列:网站信任评估WOT
- Effective Java 第三版——47. 优先使用Collection而不是Stream来作为方法的返回类型
- [LeetCode] 111. Minimum Depth of Binary Tree_Easy tag:DFS
- 【转】R语言 RStudio快捷键
- django select_related()和反射结合
- Git for Windows之基础环境搭建与基础操作
- pig入门案例
- P2467 [SDOI2010]地精部落
- DDD领域模型企业级系统(一)
- python第三方库PIL安装的各种坑
- 迷你MVVM框架 avalonjs 0.96发布
- Web Service与Apache CXF 框架
热门文章
- [TSCTF-J] relax
- 在linux下搭建NFS服务器实现文件共享
- idea中Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property manually.
- java动态代理实现与原理详细分析(转)
- java的注释方法
- VIM操作快捷键
- File对象目录列表器
- 入门oj 6451: The XOR Largest Pair之二
- Redis 设计与实现 9:五大数据类型之集合
- dede 织梦的安装 出现dir