Spring Ioc源码分析 之 Bean的加载(5):循环依赖处理(populateBean())
首先回顾下Bean加载的主流程:
1.如果是单例模式,从factoryBeanInstanceCache 缓存中获取BeanWrapper 实例对象并删除缓存
2.调用 createBeanInstance() 实例化 bean
3.后置处理
4.单例模式的循环依赖处理
5.属性填充
6.初始化 bean 实例对象
7.依赖检查
8.注册 DisposableBean
本文我们主要分析第4步:
一、循环依赖是什么?
循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:
Spring中的循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现依赖了 B,这时就会去初始化 B,然后又发现 B 依赖 C,跑去初始化 C,初始化 C 的时候发现依赖了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。
一般来说,Spring 循环依赖的情况有两种:
- 构造器的循环依赖
对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖。 - field 属性的循环依赖
我们之前提到,Spring 只解决 scope 为 singleton 的循环依赖。对于scope 为 prototype 的 bean ,Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。
为什么 Spring 不处理 prototype bean 呢?其实如果理解 Spring 是如何解决 singleton bean 的循环依赖就明白了
。这里先留个疑问,我们先来看下 Spring 是如何解决 singleton bean 的循环依赖的。
二、解决singleton循环依赖
在AbstractBeanFactory 的 doGetBean()方法中,我们根据BeanName去获取Singleton Bean的时候,会先从缓存获取。
代码如下:
//DefaultSingletonBeanRegistry.java@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存缓存 singletonObjects 中加载 beanObject singletonObject = this.singletonObjects.get(beanName);// 缓存中的 bean 为空,且当前 bean 正在创建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 加锁synchronized (this.singletonObjects) {// 从 二级缓存 earlySingletonObjects 中获取singletonObject = this.earlySingletonObjects.get(beanName);// earlySingletonObjects 中没有,且允许提前创建if (singletonObject == null && allowEarlyReference) {// 从 三级缓存 singletonFactories 中获取对应的 ObjectFactoryObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//从单例工厂中获取beansingletonObject = singletonFactory.getObject();// 添加到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);// 从三级缓存中删除this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
这段代码涉及的3个关键的变量,分别是3个级别的缓存,定义如下:
/** Cache of singleton objects: bean name --> bean instance */
//单例bean的缓存 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name --> ObjectFactory */
//单例对象工厂缓存 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name --> bean instance */
//预加载单例bean缓存 二级缓存
//存放的 bean 不一定是完整的
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
getSingleton()的逻辑比较清晰:
1.首先,尝试从一级缓存singletonObjects中获取单例Bean。
2.如果获取不到,则从二级缓存earlySingletonObjects中获取单例Bean。
3.如果仍然获取不到,则从三级缓存singletonFactories中获取单例BeanFactory。
4.最后,如果从三级缓存中拿到了BeanFactory,则通过getObject()把Bean存入二级缓存中,并把该Bean的三级缓存删掉。
- 2.1、三级缓存
看到这里可能会有些疑问,这3个缓存怎么就解决了singleton循环依赖了呢?
先别着急,我们现在分析了获取缓存的代码,再来看下存储缓存的代码。在 AbstractAutowireCapableBeanFactory 的 doCreateBean() 方法中
,有这么一段代码:
// AbstractAutowireCapableBeanFactory.java
boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式&& this.allowCircularReferences // 允许循环依赖&& isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 是否正在被创建
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));
}
这段代码就是put三级缓存singletonFactories
的地方,其核心逻辑是,当满足以下3个条件时,把bean加入三级缓存中:
1.单例
2.允许循环依赖
3.当前单例Bean正在创建
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 方法
,代码如下:
// DefaultSingletonBeanRegistry.java
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);}}
}
从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的关键。同时这段代码发生在 createBeanInstance(...)
方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还没有完善(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(已经有内存地址了,可以根据对象引用定位到堆中对象),能够被认出来了。
- 2.2、一级缓存
到这里我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 DefaultSingletonBeanRegistry 中,可以发现这个 addSingleton(String beanName, Object singletonObject) 方法
,代码如下:
// DefaultSingletonBeanRegistry.java
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {//添加至一级缓存,同时从二级、三级缓存中删除。this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}
该方法是在 #doGetBean(...)
方法中,处理不同 scope 时,如果是 singleton调用的,如下图所示:
也就是说,一级缓存里面是完整的Bean。
小结:
1.一级缓存里面是完整的Bean,是当一个Bean完全创建后才put
2.三级缓存是不完整的BeanFactory,是当一个Bean在new之后就put(没有属性填充、初始化)
3.二级缓存是对三级缓存的易用性处理,只不过是通过getObject()方法从三级缓存的BeanFactory中取出Bean
总结
现在我们再来回顾下Spring解决单例循环依赖的方案:
1.Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三级缓存中)。
2. 这样,一旦下一个 bean 创建的时候需要依赖 bean,则从三级缓存中获取。
举个栗子:
比如我们团队里要报名参加活动,你不用上来就把你的生日、性别、家庭信息什么的全部填完,你只要先报个名字,统计下人数就行,之后再慢慢完善你的个人信息。
核心思想:提前暴露,先用着
最后来描述下就上面那个循环依赖 Spring 解决的过程:
- 首先 A 完成初始化第一步并将自己提前曝光出来(通过 三级缓存 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来
- 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来
- 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A已经添加至缓存中(三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过ObjectFactory#getObject() 方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中
- 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了
浓缩成一张图:
最后,为什么多例模式不能解决循环依赖呢?
因为多例模式下每次new() Bean都不是一个,如果按照这样存到缓存中,就变成单例了。
文章参考:https://juejin.im/post/5e927e27f265da47c8012ed9
文章转自:https://cloud.tencent.com/developer/article/1520922
Spring Ioc源码分析 之 Bean的加载(5):循环依赖处理(populateBean())相关推荐
- Spring Ioc源码分析 之 Bean的加载(6):属性填充(populateBean())
"属性填充",也是在populateBean()方法中. 首先回顾下CreateBean的主流程: 如果是单例模式,从factoryBeanInstanceCache 缓存中获取B ...
- Spring Ioc源码分析 之 Bean的加载(4):实例化Bean(createBeanInstance()方法)
实例化 Bean 在doCreateBean()代码 <2> 处,有一行代码instanceWrapper = createBeanInstance(beanName, mbd, args ...
- Spring Ioc源码分析 之 Bean的加载(7):初始化
接着分析doCreateBean()的第6步--初始化 bean 实例对象 首先回顾下CreateBean的主流程: 如果是单例模式,从factoryBeanInstanceCache 缓存中获取Be ...
- Spring Ioc 源码分析(一)--Spring Ioc容器的加载
1.目标:熟练使用spring,并分析其源码,了解其中的思想.这篇主要介绍spring ioc 容器的加载 2.前提条件:会使用debug 3.源码分析方法:Intellj idea debug 模式 ...
- 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决
往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 往期博客完成了xml文件加载 ...
- java获取当前周一_Java互联网架构-Spring IOC源码分析
欢迎关注头条号:java小马哥 周一至周日下午三点半!精品技术文章准时送上!!! 精品学习资料获取通道,参见文末 源码介绍之前,看几个问题: Bean的承载对象是什么? Bean的定义如何存储的? B ...
- Spring Ioc 源码分析(一)- XML 解析
2019独角兽企业重金招聘Python工程师标准>>> 注 :源码对应版本为 4.2.8.RELEASE 引入Spring依赖的时候,是否发现spring依赖有spring-bean ...
- Myabtis源码分析五-Mybatis配置加载完全图解,建造者模式的使用,涵盖Java各种技术栈
private SqlSessionFactory sqlSessionFactory; @Before public void init() throws IOException { //----- ...
- nhibernate源码分析之六: Criteria数据加载
ICriteria是使用Expression进行数据加载的接口, 提供了设置表达式(Expression), 排序方式(Order), 分页记录等操作. 它使用一种类似于SQL语句where表达表的方 ...
最新文章
- 单调队列多重背包时间复杂度O(vn)
- Pandas批量删除dataframe列名中的后缀实战:使用rstrip函数批量删除列名中的后缀(suffix)、使用replace函数批量删除列名中的后缀(suffix)
- MATLAB中文件的读写和数据的导入导出
- H3C交换机配置本地和远程登录用户名和密码
- 云炬Android开发笔记 11主界面-商品分类开发(多布局Section RecyclerView)
- qt-embedded-linux-opensource-src-4.5.3移植到s3c6410
- shell 提取sql 的字段名表名_Mysql 常用SQL语句集锦(仅学习)
- 在线民宿满意度测评项目[开源]
- [裴礼文数学分析中的典型问题与方法习题参考解答]5.1.25
- python文本自动伪原创_现在有哪些好用的伪原创工具?
- 腾讯云云直播、云点播
- 从项目管理角度如何保证质量
- 美团点评_给出两个字符串(可能包含空格),找出其中最长的公共连续子串,输出其长度。
- 新三板上市企业专利数据(含发明专利、实用新型专利、外观设计专利)
- 永久存储:腌制一缸美味的泡菜+习题复习
- 自学篇之如何高效地学习网络安全
- 第二章:恶意软件动态分析基础
- Lync Server 2010拓扑图规划详解
- Flask智能图书推荐系统
- uniapp中开发模拟拼多多sku轮播选择器
热门文章
- Mac终端显示 bogon的问题
- 关于bootstrap的一些想法
- 正确理解HTML,XHTML页面的头部doctype定义
- iis 6.0上部署.net 2.0和4.0网站
- Unity 配置:typeConverter的使用
- ASP.NET Performance Monitoring, and When to Alert Administrators
- 优先队列的数组实现(有序)
- Python:C语言扩展
- 如何制作python检查小软件_如何用Python制作整蛊小程序
- 组会20211008《kEMPO1粒子模拟核心代码注释》