当前位置: 首页 > news >正文

Spring循环依赖解决方案

解决方案

使用提前暴露机制+三级缓存进行解决

singletonObjects一级缓存,存放完整的 Bean。
earlySingletonObjects二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 init 方法。
singletonFactories三级缓存(用Map类型的该参数来存放beanName和beanFactory之间的关系),存放的是 Bean 工厂(通过BeanFactory的getObject拿到实例,然后重新将值赋给二级缓存)。

这里只用 A,B 形成的循环依赖来举例:

  1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
  2. 为 A 创建一个 Bean 工厂,并放入到 singletonFactories 中。
  3. 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
  4. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
  5. 为 B 创建一个 Bean 工厂,并放入到 singletonFactories 中。
  6. 发现 B 需要注入 A 对象,此时在一级、二级未发现对象 A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A(通过工厂生成半成品Bean,提前暴露),并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A 还是一个半成品,并没有完成属性填充和执行初始化方法)
  7. 将对象 A 注入到对象 B 中。
  8. 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
  9. 对象 A 得到对象 B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
  10. 对象 A 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。

思考1:

只使用一级缓存,是否可以解决循环依赖

答案是否定的,Bean的创建过程 createBeanInstance --> populateBean --> initializeBean,可以推断出是在initializeBean才插入一级缓存的

由上面推断 initializeBean 的时候记录缓存,在循环依赖的情况,需要在 populateBean(第二阶段) 的时候再去注入循环依赖的 bean,此时,缓存中是没有循环依赖的 bean 的,就会导致 bean 重新创建实例。

如果被循环依赖的 bean 是一个 AOP 增强的代理 bean 的话,bean 的原始引用和最终产生的 AOP 增强 bean 的引用是不一样的,一级缓存就搞不定了。

当 aop 代理 bean 被循环依赖时,getBean() 操作从缓存中获取到的是一级缓存中存放的原始对象,而不是代理对象。

  • 假如createBeanInstance 之后就生成代理对象提前放入一级缓存呢?

答案是否定的,如果在 populateBean 之前生成的是一个代理对象的话,会导致一个问题 :jdk proxy 产生的代理对象是实现的目标类的接口,jdk proxy 的代理类通过 BeanWrapper 去利用反射设置值时会因为找不到相应的属性或者方法而报错。

思考2:

不需要第三级缓存,是否可以解决循环依赖?

答案是可以的,二级缓存来解决循环依赖的话,那么每个 bean 的创建流程中都需要插入一个流程——创建 bean 的早期引用(提前暴露)放入二级缓存。其实,在真实的开发中,绝大部分的情况下都不涉及到循环依赖而且 createBeanInstance --> populateBean --> initializeBean 这个流程也更加符合常理。所以,猜想 Spring 不用二级缓存来解决循环依赖问题,是为了保证处理时清晰明了,bean 的创建就是三个阶段: createBeanInstance --> populateBean --> initializeBean
只有碰到 AOP 代理 bean 被循环依赖时的场景,才去特殊处理,提前生成 AOP 代理 bean

思考3:

去掉二级缓存,是否可以解决循环依赖?

答案是否定的,在AOP的前提下,这么一种场景,B 和 C 都依赖了 A。singletonFactory.getObject() 获取的是代理对象。多次调用该方法返回的代理对象是不同的。

思考4:

Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?

这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。Spring 的做法就是在 ObjectFactory 中去提前创建代理对象。

 Spring 需要三级缓存的目的是为了在没有循环依赖的情况下,延迟代理对象的创建

http://www.lryc.cn/news/461976.html

相关文章:

  • 解决 IntelliJ IDEA 运行时 “Command line is too long“ 问题
  • 鸿蒙网络编程系列5-TCP连接超时分析
  • 金蝶云星空移动字段后关闭页面后重新打开无效
  • 幂律分布笔记
  • 一些NLP代表性模型
  • 低代码移动端开发:未来的趋势与挑战
  • 【Linux】嵌入式Linux系统的组成、u-boot编译
  • Qt打开excel文件,并读取指定单元格数据
  • 适合下班回家做的小副业,用AI做视频,几天时间3000+
  • git的基本操作 + 分支管理
  • VRRP
  • 个人健康系统|个人健康数据管理系统|基于小程序+java的个人健康数据管理系统设计与实现(源码+数据库+文档)
  • R语言统计分析——折线图
  • 前端怎么实现电子签名
  • 数字后端零基础入门系列 | Innovus零基础LAB学习Day1
  • 鼠标移入盒子,盒子跟随鼠标移动
  • css的简单问题
  • 使⽤ Override 和 New 关键字进⾏版本控制(C#)
  • JavaScript 15章:模块化编程
  • qt creator 开发环境的安装
  • Xilinx远程固件升级(二)——STARTUPE2原语的使用
  • DynamicExpresso
  • 从Naive RAG到Agentic RAG:基于Milvus构建Agentic RAG
  • Linux 环境chrony设置服务器间时间同步一致
  • MetaCTO确认将放弃QuestPro2及轻量化头显正在开发中
  • 深度学习 .exp()
  • 从数据管理到功能优化:Vue+TS 项目实用技巧分享
  • SSD |(六)FTL详解(上)
  • 程序报错:ModuleNotFoundError: No module named ‘code.utils‘; ‘code‘ is not a package
  • 【closerAI ComfyUI】电商模特一键换装解决方案来了!细节到位无瑕疵!再加上flux模型加持,这个工作流不服不行!