四、spring源码循环依赖的处理之doCreateBean方法的执行流程(文字描述)

2023-11-18

(还写了一篇,内容和这个差不多的,emm....那篇更简洁,算是半伪码形式....https://blog.csdn.net/qq_36951116/article/details/100078947

 

其实createBean方法没做什么事。主要就是:

(1)调用resolveBeforeInstantiation方法,作用是

在调用doCreateBean使用spring自身创建bean的流程之前,即在创建bean之前,执行了InstantiationAwareBeanPostProcessor类型的后置处理器,这种后置处理器可以用来自己创建bean的,并且这个自己创建的bean不用走后面doCreateBean这个bean创建流程。如果存在这种后置处理器,那么会由这种后置处理器去创建bean,如果这种后置处理器返回了自身创建的bean,即返回非null的话,createBean就不会再去调用doCreateBean了,而是直接把这个bean实例给返回。

(AOP的实现就是实现了这种后置处理器)

总之,通过实现这种后置处理器,就可以把自己创建的bean直接当做单例bean了(像填充属性初始化什么的,那都要自己做了)

(2)如果没有InstantiationAwareBeanPostProcessor类型的后置处理器,或者这种后置处理器没有自己创建bean,则createBean方法会调用doCreateBean去执行bean的创建流程。

 

doCreateBean执行流程大致

以A和B相互循环依赖为例子:

     <bean id="a" class="com.mine.bean.A">
        <property name="b" ref="b"/>
    </bean>

    <bean id="b" class="com.mine.bean.B">
        <property name="a" ref="a"/>
    </bean>

(1)A在创建过程中,先通过singletonFactories把实例暴露出去(执行addSingletonFactory)

(2)接下来执行populateBean方法填充属性时A需要注入B的实例,

(3)调用getBean获取B实例,但在getBean-》doGetBean-》getSingleton中没有从缓存中获取到bean,意味着B的实例还没有创建,在接下来就要创建B了。

(4)创建B时,B这个时候也得执行populateBean方法,执行过程中发现B需要注入A的实例(循环依赖了),

(5)调用容器的getBean获取A的实例。

(6)在getBean-》doGetBean-》getSingleton中通过singletonFactories获取到了第(1)步中A早期暴露出来的A实例。此时A还是没有创建完成的,A还是停在上面的第(2)步的创建过程,所以通过getSingleton获取A的实例是获取到提前暴露的A。

(7)在getSingleton中从singletonFactories获取到了提前暴露的A之前,不仅要把A从singletonFactories移除,还要把A放入earlySingletonObjects中,这表示A的早期暴露实例已经被其他人获取了,已经被其他人拿去使用了(这里是被B拿去注入了)。

(8)然后doGetBean返回A,方法回溯到第(5)步,然后再回到第(4)步,成功获取到A,此时可以把A注入到B了。

(注意,这个时候B持有了A的早期暴露的实例,earlySingletonObjects的作用就是记录A早期暴露的实例已经被别人获取了

(9)第(4)步的doGetBean执行结束,返回B。回溯到(2),A注入了B。(B不一定就是早期暴露的那个,也可能是被后置处理器处理后的代理,B的实例是早期暴露的还是被代理了的不影响,可以略过括号这段话)

(10)第(2)步的populateBean执行完,也就是A的属性填充玩了,接下来的下一行代码是:

exposedObject = initializeBean(beanName, exposedObject, mbd);

initializeBean内部会执行后置处理器,这里面的后置处理器是用户自定义的,可能是返回一个替换bean,也可能是返回一个代理的bean,那看写这个后置处理器的人想干嘛了。
 

(11)接下来,判断initializeBean是否返回了一个新的bean (用A+用于和之前早期暴露的A区分开):

如果不相等,那就需要判断A是否被其他bean注入了,如果A已经被其他bean注入了,并且initializeBean返回的是A+的话,

由于A!=A+,spring就会抛异常。

这么做就是为了保证A+和A不会同时出现在应用中,为了保证A这个类的bean是单例的。否则会出现早期暴露的A被注入到了其他bean中,A+却是被保留在spring容器中。同时存在A和A+,这样就不是单例的了。

//这里beanName是"a"
//exposedObject为后置处理器返回的A的实例A+(不清楚有没有被替换或者代理,后面进行判断)
exposedObject = initializeBean(beanName, exposedObject, mbd);
//省略代码.......


//getSingleton("a", false) 表示获取被其他bean获取过的原始的A实例
Object earlySingletonReference = getSingleton(beanName, false);

//如果A的原始实例没有被其他bean获取过,那就什么事也没有了,直接返回A
//如果被获取过,那就继续
if (earlySingletonReference != null) {
	//判断initializeBean是否产生了A+,而不是原来的A
	if (exposedObject == bean) {
		exposedObject = earlySingletonReference;
	}
	//如果initializeBean产生了A+,然后A已经被其他bean依赖了
	else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
	
		//获取依赖了A的bean,这些bean有些创建完成了,有些还没创建完。
		String[] dependentBeans = getDependentBeans(beanName);
		
		//已经完全创建完成,并且依赖了A的bean集合
		//(而正在创建中的,虽然也注入了原始的"a",但在创建过程中还可以改成新的"a")
		Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
		
		//添加已经创建完成的且依赖A的bean(属性注入,后置处理都搞完了的那种bean)
		for (String dependentBean : dependentBeans) {
		
			//如果bean是否已经创建完成了,就添加到集合中
			if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
				actualDependentBeans.add(dependentBean);
			}
		}
		
		//如果存在已经创建完成的bean注入了A,spring无法再操作创建完成的bean了,
        //此时必须抛异常,否则无法保证A的实例是单例的,会导致A和A+同时存在。
		if (!actualDependentBeans.isEmpty()) {
			throw new BeanCurrentlyInCreationException(beanName,
					"Bean with name '" + beanName + "' has been injected into other beans [" +
					StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
					"] in its raw version as part of a circular reference, but has eventually been " +
					"wrapped. This means that said other beans do not use the final version of the " +
					"bean. This is often the result of over-eager type matching - consider using " +
					"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
		}
	}
}

上面抛异常的原因是因为循环依赖导致早期暴露的bean被出入+后置处理器产生了新的实例 共同产生的。spring 直接给你抛异常了。。所以碰到这种问题的时候,要么去除循环依赖,要么,去除或者修改后置处理器。。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

四、spring源码循环依赖的处理之doCreateBean方法的执行流程(文字描述) 的相关文章

  • EncodedResource类解读

    EncodedResource类解读 EncodedResource介绍 EncodedResource是spring中Resource编码相关的封装类 EncodedResource里面封装了一个Resource成员属性 其实主要功能就是
  • 【Spring源码】createBean()

    目录 1 resolveBeanClass 2 prepareMethodOverrides 3 resolveBeforeInstantiation 1 applyBeanPostProcessorsBeforeInstantiation
  • 【Spring源码】Spring流程

    1 初始化AnnotationBeanDefinitionReader 2 初始化ClassPathBeanDefinitionScanner 3 执行register 注册配置类 4 执行refresh 先初始化比如BeanFactory
  • Spring源码之事件监听机制(下)

    文章目录 前言 一 手写事件监听机制框架 1 准备 2 事件监听接口 3 事件管理器 4 事件发布器 5 需求 6 编码 二 观察者模式 1 概述 2 UML图 3 Coding验证 小结 前言 这篇文章接的是上篇文章Spring源码之事件
  • Spring启动执行流程梳理

    注 本文梳理启动流程使用的Spring版本 4 0 2 RELEASE 使用spring配置 都需要在web xml中配置一个spring的监听器和启动参数 context param 如下
  • Spring之Bean生命周期源码解析-Bean销毁

    这篇文章是我在系统学习Spring源码之后 基于自己对Spring源码的理解 来详细分析Spring之Bean的销毁过程 目录 前言 一 注册有销毁逻辑的Bean 1 判断当前Bean是否需要销毁 1 1 判断当前Bean是否有销毁方法 1
  • 【Spring源码】BeanPostProcessor

    org springframework beans factory support AbstractAutowireCapableBeanFactory 八次调用时机 1 是否需要代理 resolveBeforeInstantiation
  • Spring源码剖析之IOC容器创建流程

    ApplicationContextConfiguration为核心配置类 ApplicationContext applicationContext new AnnotationConfigApplicationContext Appli
  • Spring事务(三)——传播属性之REQUIRED

    事务是与连接对象紧密相关的 事务属性用来控制事务流转 Spring事务的传播属性有以下几种 Propagation REQUIRED 如果当前没有事务 就新建一个事务 如果已经存在一个事务中 则加入到这个事务中 默认属性 也是最常使用 Pr
  • Spring源码分析之createBean主流程分析

    我们知道 在调用getBean获取bean实例的实例 首先会从缓存中获取bean实例 如果没有获取到 就会去创建bean的时候 关于获取bean实例 可以参考Spring源码分析之getBean主流程分析 而本文将会对创建bean实例的主流
  • BeanFactoryPostProcessor扩展

    Configuration ComponentScan com example public class AppConfig Component public class User public User private String na
  • spring加载流程之ConfigurationClassPostProcessor

    spring加载流程之ConfigurationClassPostProcessor ConfigurationClassPostProcessor postProcessBeanDefinitionRegistry processConf
  • Spring之启动过程源码解析

    Spring创建Bean 会经过一系列生命周期的流程 而Spring启动 其实就是为了后续创建Bean做一些准备工作 本篇以及下一篇文章都是来详细分析Spring的启动过程 目录 一 Spring启动的大致流程 二 Spring加载流程之A
  • 深入分析RestController与Controller

    RestController和 Controller注解 我们都知道RestController默认都只提供Rest风格接口返回值 针对不需要返回页面的Controller都采用RestController进行注解 下面根据源码简单分析一下
  • IDEA导入Spring源码环境搭建

    一 环境准备 1 Spring源码包 下载地址 https github com spring projects spring framework 2 gradle工具 下载地址 http downloads gradle org dist
  • Spring源码:PropertyValues类及属性注入二

    主代码 1 RuntimeBeanReference类型 2 RuntimeBeanNameReference类型 3 BeanDefinitionHolder类型 4 BeanDefinition类型 5 ManagedArray类型 6
  • 【Spring源码】createBeanInstance()

    目录 创建实例 createBeanInstance 有参构造 autowireConstructor 无参构造 instantiateBean 实例化策略 instantiate createBeanInstance英文版 autowir
  • Spring源码:PropertyValues类及属性注入一

    概要 相关类 属性注入 概要 Spring获取Bean的实例时 需要把配置的属性值解析到PropertyValues 然后填充入BeanWrapper中 相关类 MutablePropertyValues类 PropertyValues接口
  • Spring源码之Bean的生命周期

    Spring已经成为了目前最流行的第三方开源框架之一 我们在充分享受Spring IOC容器带来的便捷时 也应该考虑一下Spring这个大工厂是如何将一个个的Bean生产出来的 我们一起来讨论一下Spring中Bean的生命周期 Sprin
  • Spring之循环依赖源码解析

    目录 1 什么是循环依赖 2 为什么会出现循环依赖 3 面对循环依赖问题 我们该如何思考解决 4 Spring是怎么解决循环依赖的 5 总结 1 什么是循环依赖 有两个类Order Customer Order对象依赖了Customer对象

随机推荐

  • Vue实现点击复制文本功能

    1 功能 在页面中实现点击复制内容到粘贴板的功能 2 安装clipboard npm install clipboard 3 封装工具类 新建clipboard js 内容如下 import Vue from vue import Clip
  • 嵌入式(标准IO)

    嵌入式自学第十三天 1 Linux IO进程课程目的 学习编写linux应用程序 APP 2 Linux文件的种类 常规文件 目录文件 字符文件 块文件 链接文件 相当于windows快捷方式 3 IO的概念 I input 输入设备 比如
  • 关于Python模块shelve存储的对象,改变字典的值时必须将获取的副本赋给一个临时变量的更好解决方法

    文章的关键点 将函数open的参数writeback设置为True就不需要引用一个临时变量 开始文章的叙述 如何将数据存储到文件中 如果需要的是简单的存储方案 模块shelve可替你完成大部分工作 你只需提供一个文件名即可 对于模块shel
  • Vector非线程安全

    Vector的方法加了同步锁 但对于复合操作未加锁 是非线程安全的 如经典的put if absent 线程的安全性包括线程的可见性 有序性 原子性 if vector contains element vector add element
  • Modbus 485继电器开、关、读 串口指令

    以下均为地址01时的命令 开继电器1 01 06 00 00 00 01 48 0A 关继电器1 01 06 00 00 00 00 89 CA 开继电器2 01 06 00 01 00 01 19 CA 关继电器2 01 06 00 01
  • Java NIO 的前生今世 之二 NIO Channel 小结

    Java NIO Channel 通常来说 所有的 NIO 的 I O 操作都是从 Channel 开始的 一个 channel 类似于一个 stream java Stream 和 NIO Channel 对比 我们可以在同一个 Chan
  • 高淇Struts2.0教程之视频笔记(4)

    4 struts2配置文件 struts xml 详解 4 1指定web应用默认字符集
  • java几位_JAVA 获得数字第几位的几种方法总结(转)

    JAVA获得某个数字的最后第几位是什么的方法 比如 1234567890 则获得最后第三位 则返回8 public class Test 字典 里面保存的是1 10 100 1000 直到long的最大允许位数 private static
  • Latex插入图片并引用

    插入图片主要为viso文件和excel图表 两种图片如果直接转pdf的话会有大片空白区域 不利于排版 解决办法 excel图表粘贴到viso中 后面两中图片处理方法相同 点击另存 保存格式为可移植网络图形 即png格式 保存路径为latex
  • 2023华为od机试统一考试B卷【阿里巴巴找黄金宝箱(III)】

    题目描述 贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面贴有一个数字 阿里巴巴念出一个咒语数字 查看宝箱是否存在两个不同箱子 这两个箱子上贴的数字相同 同时这两个箱子的编号之差的绝
  • 2022蓝桥杯学习——1.递归和递推

    递归 关于递归 所有的递归都可以转换成一棵递归搜索树 我们需要考虑的是枚举的顺序 例题 1 递归实现指数型枚举 题目描述 从 1 n 这 n 个整数中随机选取任意多个 输出所有可能的选择方案 输入格式 输入一个整数 n 输出格式 每行输出一
  • vue实现鼠标放上去就有提示_Vue实现鼠标经过文字显示悬浮框效果的示例代码

    需求 在所做的Vue项目中 需要在鼠标移动文字框的时候显示一些详细信息 最终实现的效果如下 鼠标经过button的时候 可以在光标附近显示出一个悬浮框 显示框里面显示时间和值的信息 鼠标移出button元素的时候 这个显示框会消失 分析 涉
  • C++编写优先队列打印任务

    打印机的打印队列中 每一个打印任务都有一个优先级 为1 9的一个整数 9的优先级最高 1的优先级最低 打印按如下方法进行 1 取出打印队列中队首的打印任务J 2 如果打印队列中存在优先级高于J的打印任务 则将J移动到打印队列的队尾 否则 打
  • 微软鼠标测试软件,第一款win8鼠标:微软Sculpt全球首测

    1Sculpt触控鼠标 带来全新感受 中关村在线键鼠频道原创 微软硬件在外设产品研发上 一直致力于以领先的科技带给用户超凡的体验 从早期的IE3 0 到越野蓝影 再到Arc Touch Touch Mouse等等 微软硬件在的每一次技术革命
  • 企业怎么选择固定资产管理系统

    资产管理 无论在企业还是在事业单位 都是管理人员重要的工作 随着计算机技术的普及 资产管理系统 已经有了相对清晰的管理流程及其配套的管理软件 资产管理系统是面向资产密集型企业的企业信息化解决方案的总称 它以提高资产管理效率 降低企业管理成本
  • VS2019 preview 卡在正在加载解决方案

    VS2019 解决方案 或者项目 卡 正在加载 的解决办法 1 关闭VS 2 去C Users
  • 微信小程序css篇----定位(position)

    昨天2017的微信公开课pro如期进行了 小程序将于2017年1月9日对个人开放 公司项目的demo版做了个大概 过程中花的时间最多的还是页面布局 所以后面将花一段时间将css的属性在小程序里过一篇 虽然小程序里面对于css支持 但没有说明
  • 使用labview 的http协议实现post和get,带解析

    1 创建一个新项目 右键点击我的电脑 新建web服务 然后就弹出web资源和启动VI 2 web资源新建一个VI HTTPMethed 1 vi 用来相应post data的数据 右键可以显示方法url HTTPMethed 1内容如下 3
  • shell脚本实现文件移动、复制等操作

    如题 在此做一记录 方便查阅 bin bash 将一个目录下的一些文件移动到另一个目录下 raw dir home liuyi evt test 可修改绝对路径 mkdir home liuyi evt bp 创建新的文件目录 for el
  • 四、spring源码循环依赖的处理之doCreateBean方法的执行流程(文字描述)

    还写了一篇 内容和这个差不多的 emm 那篇更简洁 算是半伪码形式 https blog csdn net qq 36951116 article details 100078947 其实createBean方法没做什么事 主要就是 1 调