0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从源码层面深度剖析Spring循环依赖

OSC开源社区 来源:OSCHINA 社区 2022-12-22 10:34 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群


以下举例皆针对单例模式讨论图解参考:https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

1、Spring 如何创建 Bean?

对于单例 Bean 来说,在 Spring 容器整个生命周期内,有且只有一个对象。Spring 在创建 Bean 过程中,使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的:
    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

以 com.gyh.general 包下的 OneBean 为例,debug springboot 启动过程,分析 spring 是如何创建 bean 的。

参考图中spring 创建 bean的过程。其中最关键的几步有:1.getSingleton(beanName, true)依次从一二三级缓存中查找 bean 对象,如果缓存中存在对象,则直接返回 (early);2.createBeanInstance(beanName, mbd, args)选一个合适的构造函数,new 实例对象 (instance),此时的 instance 中依赖的属性还都是 null,属于半成品;3.singletonFactories.put(beanName, oneSingletonFactory)利用上一步的 instance,构建一个 singletonFactory,并将其放到三级缓存中;4.populateBean(beanName, mbd, instanceWrapper)填充 bean:为该 bean 定义的属性创建对象或赋值;5.initializeBean("one",oneInstance, mbd)初始化 bean:对 bean 进行初始化或其他加工,如生成代理对象 (proxy);6.getSingleton(beanName, false)依次在一二级缓存中查找,检查是否有因循环依赖导致提前生成的对象,有的话与初始化后的对象是否一致;

2、Spring 如何解决循环依赖?

以 com.gyh.circular.threeCache 包下的 OneBean 和 TwoBean 为例 ,两个 Bean 相互依赖(即形成闭环)。参考图中spring 解决循环依赖的过程可知,spring 利用三级缓中的 objectFactory 生成并返回一个 early 对象,提前暴露这个 early 地址,供其他对象依赖注入使用,以此解决循环依赖问题。

3、Spring 不能解决哪些循环依赖?

3.1 循环中使用了@Async注解

3.1.1 为什么循环中使用了@Async 会报错?

以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个 bean 相互依赖,且 oneBean 中的方法使用了@Async 注解,此时启动 spring 失败,报错信息为:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.并通过 debug 代码,发现报错位置在 AbstractAutowireCapableBeanFactory#doCreateBean 方法内,由于 earlySingletonReference != null 且 exposedObject != bean,导致报错。5a2ee708-813f-11ed-8abf-dac502259ad0.png结合流程图中spring 解决循环依赖及上述图片中可知:1.行 1 中 bean 为 createBeanInstance 创建的实例 (address1)2.行 2 中 exposedObject 为 initializeBean 后生成的代理对象 (address2)3.行 3 中 earlySingletonReference 为 getEarlyBeanReference 时创建的对象【此处地址同 bean (address1)】深层原因为:先前 TwoBean 在 populateBean 时已经依赖了地址为 address1 的 earlySingletonReference 对象,而此时 OneBean 经过 initializeBean 之后,返回了地址为 address2 的新对象,导致 spring 不知道哪个才是最终版的 bean,所以报错。earlySingletonReference 是如何生成的,参考 getSingleton ("one", true) 过程。

3.1.2 循环中使用了@Async 一定会报错吗?

依然以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个 bean 相互依赖,使 TwoBean (非 OneBean) 中的方法使用了@Async 注解,此时启动 spring 成功,并未报错。debug 代码可知:虽然 TwoBean 使用了 @Async 注解,但其 earlySingletonReference = null; 故不会引起报错。5a6698f6-813f-11ed-8abf-dac502259ad0.png深层原因为:OneBean 先被创建,TwoBean 后创建,再整条链路中,并未在三级缓存中查找过 TwoBean 的 objectFactory 。(OneBean 在创建过程中,被找过两次,即 one-> two ->one;TwoBean 的创建过程中,只找过它一次,即 two ->one。)由此可得:@Async 造成循环依赖报错的先约条件为:1.循环依赖中的 Bean 使用了 @Async 注解2.且这个 Bean,比循环内其他 Bean 先创建。3.注:一个 Bean 可能会同时存在于多个循环内;只要存在它是某个循环内第一个被创建的 Bean,那么就会报错。

3.1.3 为什么循环中使用了 @Transactional 不会报错?

已知使用了 @Transactional 注解的 Bean,Spring 也会为其生成代理对象,但为什么这种 Bean 在循环里时不会产生报错呢?以 com.gyh.circular.transactional 包下的 OneBean 和 TwoBean 为例,两个 Bean 相互依赖,且 OneBean 中的方法使用了 @Transactional 注解,启动 Spring 成功,并不会报错。debug 代码可知,生成 OneBean 过程中,虽然 earlySingletonReference != null,但 initializeBean 之后的 exposedObject 和 原始实例的地址相同(即 initializeBean 步骤中,并未对实例生成代理),所以不会产生报错。5ad3f7c0-813f-11ed-8abf-dac502259ad0.png3.1.4 为什么同样是代理会产生两种不同的现象?同样是生成代理对象,同样是参与到循环依赖中,会产生不同现象的原因是:当他们处在循环依赖中时,生成代理的节点不同:1.@Transactional 在 getEarlyBeanReference 时生成代理,提前暴露出代理之后的地址(即最终地址);2.@Async 在 initializeBean 时生成代理,导致提前暴露出去的地址不是最终地址,造成报错。为什么 @Async 不能在 getEarlyBeanReference 时生成代理呢?对比下两者执行的代码过程发现:两者都是在 AbstractAutoProxyCreator#getEarlyBeanReference 的方法对原始实例对象进行包装,如下图5afa0d48-813f-11ed-8abf-dac502259ad0.png使用 @Transactional 的 Bean 在 create proxy 时,获取到一个 advice ,随即生成了代理对象 proxy.5b3be77c-813f-11ed-8abf-dac502259ad0.png而使用 @Async 的 Bean 在 create proxy 时,没有获取到 advice,不能被代理.5b6c1d7a-813f-11ed-8abf-dac502259ad0.png

3.1.5 为什么 @Async 在 getEarlyBeanReference 时不能返回一个 advice?

在 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法内,其主要做的事情有:1.找到当前 spring 容器中所有的 Advisor2.返回适配当前 bean 的所有 Advisor第一步返回的 Advisor 有 BeanFactoryCacheOperationSourceAdvisor 和 BeanFactoryTransactionAttributeSourceAdvisor,并无处理 Async 相关的 Advisor.刨根问底,追查为什么第一步不会返回处理 Async 相关的 Advisor?已知使用 @Async @Transactional @Cacheable 需要提前进行开启,即提前标注 @EnableAsync、@EnableTransactionManagement、@EnableCaching 。以 @EnableTransactionManagement、@EnableCaching 为例,在其注解定义中,引入了 Selector 类,Selector 中又引入了 Configuration 类,在 Configuration 类中,创建了对应 Advisor 并放到了 spring 容器中,所以第一步才能得到这两个 Advisor.而 @EnableAsync 的定义中引入的 Configuration 类,创建的是 AsyncAnnotationBeanPostProcessor 并非一个 Advisor,所以第一步不会得到它,所以 @Async 的 bean 不会在这一步被代理。

3.2 构造函数引起的循环依赖

以 com.gyh.circular.constructor 包下的 OneBean 和 TwoBean 为例,两个类的构造函数中各自依赖对方,启动 spring,报错:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?debug 代码可知,两个 bean 在根据构造函数 new instance 时,就已经陷入的死循环,无法提前暴露可用的地址,所以只能报错。

4、如何解决以上循环依赖报错?

1.不用 @Async,将需要异步操作的方法,放到线程池中执行。(推荐)2.提出 @Async 标注的方法。(推荐)3.将使用 @Async 的方法提出到单独的类中,该类只做异步处理,不做其他业务依赖,即避免形成循环依赖,从而解决报错问题。参考 com.gyh.circular.async.extract 包。4.尽量不使用构造函数依赖对象。(推荐)5.破坏循环(不推荐)即不形成闭环,在开发之前,规划好对象依赖,方法调用链,尽量做到不使用循环依赖。(较难,随着迭代开发不断变化,很可能产生循环)6.破坏创建顺序(不推荐)7.由于使用 @Async 注解的所在类,比循环依赖内其他类先创建时才会报错,那么想办法使该类不先于其他类先创建,也可解决该问题,如:@DependsOn、@Lazy

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 源码
    +关注

    关注

    8

    文章

    689

    浏览量

    31554
  • spring
    +关注

    关注

    0

    文章

    345

    浏览量

    16081

原文标题:从源码层面深度剖析 Spring 循环依赖

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    HM博学谷狂野AI大模型第四期

    ”式的学习路径。学员将深入源码层级,剖析自注意力机制是如何通过矩阵运算捕捉序列特征,位置编码是如何注入时序信息,以及前馈神经网络与残差连接是如何层层堆叠构建起深度的特征提取空间。通过这种源码
    发表于 05-01 17:30

    线性系列AC - DC电源供应器深度剖析

    线性系列AC - DC电源供应器深度剖析 在电子设备的世界里,电源供应器就像是设备的“心脏”,为整个系统提供稳定而可靠的电力。今天,我们就来详细探讨Bel Power Solutions生产的线性
    的头像 发表于 04-05 13:40 1123次阅读

    MAX66240:深度安全认证芯片的技术剖析与应用探索

    MAX66240:深度安全认证芯片的技术剖析与应用探索 一、引言 在当今数字化时代,数据安全至关重要。无论是门禁系统、资产追踪,还是医疗设备等领域,都需要可靠的安全认证解决方案。Maxim
    的头像 发表于 04-03 15:20 167次阅读

    WebRTC源码深度解析(完结) (讠果xingkeit-top)#WebRTC #源码

    源码
    jf_82580774
    发布于 :2026年03月30日 15:22:21

    TMS320VC5507 DSP深度剖析特性到应用的全方位指南

    TMS320VC5507 DSP深度剖析特性到应用的全方位指南 一、引言 在现代电子设计领域,数字信号处理器(DSP)扮演着至关重要的角色。TMS320VC5507作为一款高性能、低功耗的定点
    的头像 发表于 03-09 10:45 443次阅读

    车规蓝牙模块技术深度剖析

    在汽车电子化迅猛发展的当下, 车规蓝牙模块 ——这一集成蓝牙功能的PCBA(印刷电路板组装件),已成为推动汽车智能化、网联化的核心力量。本文将从技术层面深入剖析车规蓝牙模块的应用、性能指标、安全性
    的头像 发表于 03-04 14:04 944次阅读

    CAN协议的深度剖析

    单元(ECU)之间的高效通信问题。本文将从技术原理、帧结构、错误处理机制、应用场景及未来发展趋势等方面,对CAN协议进行深度剖析
    的头像 发表于 03-03 17:08 877次阅读
    CAN协议的<b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    串口协议的深度剖析

    串口通信协议作为电子设备间数据交互的基础技术,自20世纪60年代诞生以来,始终在工业控制、嵌入式系统和物联网等领域扮演着核心角色。本文将从技术原理、协议架构、应用场景及未来演进四个维度,对串口协议展开深度剖析
    的头像 发表于 03-02 17:32 1309次阅读

    深度剖析TL431与TL432:特性到应用的全方位指南

    深度剖析TL431与TL432:特性到应用的全方位指南 在电子工程师的日常设计工作中,电压参考器件是不可或缺的组成部分。TI的TL431和TL432作为经典的精密可编程参考器件,凭借其出色的性能
    的头像 发表于 03-02 15:25 381次阅读

    德州仪器PCM1789-Q1音频DAC深度剖析特性到应用

    德州仪器PCM1789-Q1音频DAC深度剖析特性到应用 在音频领域,一款高性能的数字-to-模拟转换器(DAC)对于音质的提升起着至关重要的作用。今天,我们就来深入了解一下德州仪器(Texas
    的头像 发表于 01-30 15:40 372次阅读

    可重触发单稳态多谐振荡器:原理到应用的深度剖析

    可重触发单稳态多谐振荡器:原理到应用的深度剖析 在电子电路设计领域,可重触发单稳态多谐振荡器是一种非常实用的电路元件,它能够在特定的触发条件下产生精确的脉冲信号,广泛应用于脉冲整形、定时、延时等
    的头像 发表于 01-27 14:20 404次阅读

    TLE989x EvalBoard with TQFP/LQFP spring socket v01_1 评估板深度解析

    TLE989x EvalBoard with TQFP/LQFP spring socket v01_1 评估板深度解析 在电子设计领域,评估板是我们探索和验证新器件性能的重要工具。今天,我们就来
    的头像 发表于 12-20 10:40 2462次阅读

    一款基于Java+Spring Boot+Vue的智慧随访管理系统源码

    智慧随访管理系统源码,一款基于Java+Spring Boot+Vue的B/S架构医院随访管理系统源码,采用前后端分离技术(Ant-Design+MySQL5),具有自主版权和落地案例。 随访管理
    的头像 发表于 11-13 15:38 588次阅读
    一款基于Java+<b class='flag-5'>Spring</b> Boot+Vue的智慧随访管理系统<b class='flag-5'>源码</b>

    力芯微高压LDO系列技术深度剖析电路架构到场景适配逻辑

    力芯微高压LDO系列技术深度剖析 ,能覆盖多场景需求,关键在于突破“宽压适配-低功耗平衡-噪声抑制”技术矛盾, 电路架构到场景适配逻辑 如下: 宽输入电压的实现:高压耐受性设计​ ET5H7XX
    的头像 发表于 09-22 14:05 817次阅读

    接口到架构:工控一体机定制化的深度技术剖析

    在工业4.0与数字化转型的浪潮中,工控一体机作为工业自动化与信息化融合的核心载体,正通过深度定制化技术重构工业控制系统的底层逻辑。硬件接口的灵活配置到系统架构的模块化设计,定制化已成为解决复杂
    的头像 发表于 06-17 16:47 802次阅读