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

Spring的循环依赖

循环依赖概述

循环依赖其实也很好理解,可以将这个词拆分成两部分,一个是循环,一个是依赖。循环,顾名思义就是指形成了一个闭合环路,也就是闭环。依赖就是指某个事件的发生要依赖另一个事件。

在Spring中的循环依赖就是指一个或者多个Bean之间存在着互相依赖的关系,并且形成了循环调用。例如,在Spring中,A依赖B,B又依赖A,A和B之间就形成了相互依赖的关系。创建A对象时,发现A对象依赖了B对象,此时先去创建B对象。创建B对象时,发现B对象又依赖了A对象,此时又去创建A对象。创建A对象时,发现A对象依赖了B对象....如果Spring不去处理这种情况,就会发生死循环,一直会创建A对象和B对象,直到抛出异常为止。

同理,在Spring中多个对象之间也有可能存在循环依赖,例如,A依赖B,B依赖C,C又依赖A,A、B、C之间形成了互相依赖的关系,这也是一种循环依赖。

循环依赖场景

Spring中的循环依赖场景总体上可以分成单例Bean的setter循环依赖、多例Bean的setter循环依赖、代理对象的setter循环依赖、构造方法的循环依赖和DependsOn的循环依赖。如图20-5所示。

Spring是支持基于单例Bean的setter方法的循环依赖的,不过有一种特殊情况需要注意。本节,我们就一起实现基于单例Bean的setter方法的循环依赖的特殊情况,具体实现步骤如下所示。

(1)新增SpecialCircularBeanA类

SpecialCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanA。

@Component
public class SpecialCircularBeanA {@Autowiredprivate SpecialCircularBeanB specialCircularBeanB;@Overridepublic String toString() {return "SpecialCircularBeanA{" +"specialCircularBeanB=" + specialCircularBeanB +'}';}
}

可以看到,在SpecialCircularBeanA类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanA类型的单例Bean,并且在SpecialCircularBeanA类型的Bean对象中,会依赖SpecialCircularBeanB类型的Bean对象。同时,在SpecialCircularBeanA类中重写了toString()方法,打印了依赖的SpecialCircularBeanB类型的对象。

(2)新增SpecialCircularBeanB类

SpecialCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanB。

@Component
public class SpecialCircularBeanB {@Autowiredprivate SpecialCircularBeanA specialCircularBeanA;@Overridepublic String toString() {return "SpecialCircularBeanB{" +"specialCircularBeanA=" + specialCircularBeanA +'}';}
}

可以看到,在SpecialCircularBeanB类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanB类型的单例Bean,并且在SpecialCircularBeanB类型的Bean对象中,会依赖SpecialCircularBeanA类型的Bean对象。同时,在SpecialCircularBeanB类中重写了toString()方法,打印了依赖的SpecialCircularBeanA类型的对象。

(3)新增SpecialCircularConfig类

SpecialCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.config.SpecialCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.special"})
public class SpecialCircularConfig {
}

可以看到,在SpecialCircularConfig类上标注了@Configuration注解,说明SpecialCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了扫描的包。

(4)新增SpecialCircularTest类

SpecialCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.SpecialCircularTest。

public class SpecialCircularTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpecialCircularConfig.class);SpecialCircularBeanA specialCircularBeanA = context.getBean(SpecialCircularBeanA.class);System.out.println(specialCircularBeanA);context.close();}
}

可以看到,在SpecialCircularTest类的main()方法中,传入SpecialCircularConfig类的Class对象后,创建IOC容器,随后从IOC容器中获取SpecialCircularBeanA类型的Bean对象并进行打印,最后关闭IOC容器。

(5)运行SpecialCircularTest类

运行SpecialCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,程序抛出了StackOverflowError异常。 

其实,从本质上讲,这个异常不是Spring抛出的,而是JVM抛出的栈溢出错误。

解决思路

其实要打破上述这个循环的链条,关键点在于,将bean实例化和bean属性注入这2步分开,且允许在属性注入的时候,注入一个已经实例化但还未进行属性注入的bean。即让一个已经实例化的bean,提前暴露出来,可以被其他bean拿到引用进行属性注入,而这个提前暴露的bean的属性输入可以在后续过程中再完成,因为我们的目标bean在进行属性注入的时候,只要拿到这个提前暴露bean的引用即可。

这个思路也跟上面说的不支持构造方法输入的bean循环依赖是呼应的,因为实例化这一步就使用到了构造方法,如果是构造方法注入,这个bean都无法实例化出来,就没有可能进行提前暴露了。顺着这个思路,我们很自然可以想到使用一个map来保存那些实例化之后的bean,这个bean可能仅仅是实例化,还未进行属性注入,其他bean如果依赖它,就可以从这map中获取到并进行注入。

我们在这里引入了一个缓存池。

当我们需要创建 AService 的实例的时候,会首先通过 Java 反射创建出来一个原始的 AService,这个原始 AService 可以简单理解为刚刚 new 出来(实际是刚刚通过反射创建出来)还没设置任何属性的 AService,此时,我们把这个 AService 先存入到一个缓存池中。

接下来我们就需要给 AService 的属性设置值了,同时还要处理 AService 的依赖,这时我们发现 AService 依赖 BService,那么就去创建 BService 对象,结果创建 BService 的时候,发现 BService 依赖 AService,那么此时就先从缓存池中取出来 AService 先用着,然后继续 BService 创建的后续流程,直到 BService 创建完成后,将之赋值给 AService,此时 AService 和 BService 就都创建完成了。

可能有小伙伴会说,BService 从缓存池中拿到的 AService 是一个半成品,并不是真正的最终的 AService,但是小伙伴们要知道,咱们 Java 是引用传递(也可以认为是值传递,只不过这个值是内存地址),BService 当时拿到的是 AService 的引用,说白了就是一块内存地址而已,根据这个地址找到的就是 AService,所以,后续如果 AService 创建完成后,BService 所拿到的 AService 就是完整的 AService 了。

那么上面提到的这个缓存池,在 Spring 容器中有一个专门的名字,就叫做 earlySingletonObjects,这是 Spring 三级缓存中的二级缓存,这里保存的是刚刚通过反射创建出来的 Bean,这些 Bean 还没有经历过完整生命周期,Bean 的属性可能都还没有设置,Bean 需要的依赖都还没有注入进来。另外两级缓存分别是:

  • singletonObjects:这是一级缓存,一级缓存中保存的是所有经历了完整生命周期的 Bean,即一个 Bean 从创建、到属性赋值、到各种处理器的执行等等,都经历过了,就存到 singletonObjects 中,当我们需要获取一个 Bean 的时候,首先会去一级缓存中查找,当一级缓存中没有的时候,才会考虑去二级缓存。

  • singletonFactories:这是三级缓存。在一级缓存和二级缓存中,缓存的 key 是 beanName,缓存的 value 则是一个 Bean 对象,但是在三级缓存中,缓存的 value 是一个 Lambda 表达式,通过这个 Lambda 表达式可以创建出来目标对象的一个代理对象。

有的小伙伴可能会觉得奇怪,按照上文的介绍,一级缓存和二级缓存就足以解决循环依赖了,为什么还冒出来一个三级缓存?那就得考虑 AOP 的情况了!

相关文章:

  • Vite - 项目打包从 0 到 1(完美解决打包后访问白屏问题)
  • Python第二语言(八、Python包)
  • 解决富文本中抖音视频无法播放的问题——403
  • HTML静态网页成品作业(HTML+CSS)—— 非遗皮影戏介绍网页(6个页面)
  • 后端启动项目端口冲突问题解决
  • 【随手记】maplotlib.use函数设置图像的呈现方式
  • Android FirebaseApp.initializeApp(this)无法初始化
  • 璨与序列 题解(stl,dfs)
  • 【Python入门与进阶】Python如何处理不同进制的数据
  • Spring Cloud Bus 消息总线基础入门与实践总结
  • 数字化那点事:一文读懂智慧城市
  • CATIA P3 V5-6R 中文版软件下载安装 达索CATIA三维设计软件获取
  • Vitis HLS 学习笔记--移除内存分配malloc
  • 活久见!谁想的这种办法让大模型PK
  • 最新下载:Paragon NTFS for Mac 15【软件附加安装教程】
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • 2017年终总结、随想
  • CentOS 7 防火墙操作
  • create-react-app做的留言板
  • express.js的介绍及使用
  • java8 Stream Pipelines 浅析
  • java多线程
  • java中的hashCode
  • mysql 5.6 原生Online DDL解析
  • Mysql数据库的条件查询语句
  • Node + FFmpeg 实现Canvas动画导出视频
  • SegmentFault 2015 Top Rank
  • 分享一份非常强势的Android面试题
  • 实现菜单下拉伸展折叠效果demo
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 用jQuery怎么做到前后端分离
  • 正则学习笔记
  • 《码出高效》学习笔记与书中错误记录
  • 7行Python代码的人脸识别
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 阿里云API、SDK和CLI应用实践方案
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​水经微图Web1.5.0版即将上线
  • # 数据结构
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #pragma multi_compile #pragma shader_feature
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (1)常见O(n^2)排序算法解析
  • (10)ATF MMU转换表
  • (2)STM32单片机上位机
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转)linux 命令大全
  • (转)项目管理杂谈-我所期望的新人
  • (转载)CentOS查看系统信息|CentOS查看命令
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .DFS.
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET Project Open Day(2011.11.13)