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

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

3天内不再提示

关于Spring的循环依赖问题

Android编程精选 来源:博客园 作者:青石路 2022-06-14 17:21 次阅读

前情回顾

一探

Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗中讲到了循环依赖问题

同样说明了Spring只能解决setter方式的循环依赖,不能解决构造方法的循环依赖

重点介绍了Spring是如何解决setter方式的循环依赖,感兴趣的可以去看下

二探

既然Spring不能解决构造方法的循环依赖,那么它是如何甄别构造方法循环依赖的了?

所以进行了二探:再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

从源码的角度讲述了Spring是如何判定构造方法循环依赖、原型循环依赖的

感兴趣的可以去看下

大家跟源码的时候,一定要注意版本!!!

项目模拟

自认为经过了前两探,对Spring循环依赖的问题已了若指掌,可面对线上突如其来的循环依赖问题,楼主竟然没能一眼看出来!!!

这楼主能忍?于是楼主又跟起了Spring源码,看看问题到底出在哪?

SpringBoot版本是2.0.3.RELEASE

线上服务采用k8s部署,本地环境未采用k8s部署

本地启动从未出现循环依赖问题,线上环境也只是偶发的pod启动失败(提示信息直指循环依赖)

问题偶发,而非必现,很是头疼,但问题还是得解决,从提示信息着手呗

根据错误提示信息,楼主模拟出了一个简化的工程,方便我们进行问题排查

eefcb9ae-d443-11ec-bce3-dac502259ad0.png

非常简单,完整地址:spring-other-circular-reference

我们来看下类图

ef226186-d443-11ec-bce3-dac502259ad0.png

MyListenerMyServiceMyManager很常规,特殊的是MyConfigMySender

ef484bda-d443-11ec-bce3-dac502259ad0.png

ef60c5e8-d443-11ec-bce3-dac502259ad0.png

问题复现

如果按上述工程结构,本地很难复现问题 ,反正楼主是没复现出来

我们稍做调整,将MySender前置,如下

ef9f542a-d443-11ec-bce3-dac502259ad0.gif

启动失败,错误信息如下:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myConfig': Unsatisfied dependency expressed through field 'myListener'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myListener': Unsatisfied dependency expressed through field 'myService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myServiceImpl': Unsatisfied dependency expressed through field 'myManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myManager': Unsatisfied dependency expressed through field 'mySender'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mySender': Requested bean is currently in creation: Is there an unresolvable circular reference?

此刻的Is there an unresolvable circular reference?让楼主感到了陌生

问题分析

我们从以下几个方面来分析

BeanDefinition 扫描

目前XML方式的Bean定义越来越少,除了一些遗留的老项目,基本看不到XML方式的Bean定义了

所以我们只关注注解方式的Bean定义的扫描

文件夹的扫描顺序与文件夹名字的升序一致,文件的顺序与文件名的升序一致,如下所示

f06ca5ce-d443-11ec-bce3-dac502259ad0.png

有兴趣的可以去跟下ConfigurationClassParser类中doProcessConfigurationClass方法;楼主做了下简单的总结

f0849e04-d443-11ec-bce3-dac502259ad0.png

@ComponentScan的处理早于@Bean

BeanDefinition扫描过程中,会按扫描顺序会往DefaultListableBeanFactorybeanDefinitionMap中添加BeanDefinition,往beanDefinitionNames添加BeanName

我们来跟下源码,看是不是如上所说

f0c444c8-d443-11ec-bce3-dac502259ad0.gif

先被扫描的BeanDefinitionBeanName会被先添加到beanDefinitionNames

BeanDefinition 覆盖

MyConfig中通过@Bean定义了MySender,而MySender类上又用了@Component进行修饰

那创建MySender实例的时候到底调用的哪个构造方法?(有参还是无参?)

关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?从源码的角度分析了这个问题

结论是:SpringBoot 2.0.3.RELEASE中,@Configuration + @Bean修饰的BeanDefinition会覆盖掉@Component修饰的BeanDefinition

也就说MySender类上的@Component其实没用,加不加效果是一样的,这里说的没用效果仅仅指的是MySenderBeanDefinition

Bean 实例化顺序

BeanDefinition用来构建实例,那么MySender上的@Component就有作用了,它决定了MySender的实例化顺序

是先于MyConfigMyListenerMyServiceImplMyManager实例化的

我们来看下Bean的实例化顺序

f164c63c-d443-11ec-bce3-dac502259ad0.gif

理论上来讲,先被扫描的Bean会先被实例化;Bean实例化的过程中会填充属性,可能会导致后被扫描的Bean提前被实例化

如果Bean之间没有依赖,那么会严格按照Bean的扫描顺序实例化

再看问题

我们再回到前面的问题

f20bec64-d443-11ec-bce3-dac502259ad0.png

这种情况下,我们分析下Is there an unresolvable circular reference?是如何产生的

相较于MyConfigMyListenerMyManagerMyServiceImplMySender是最先被扫描到的,所以它最先被实例化

因为MyConfig中通过@Bean修饰了MySenderBeanDefinition

f28332ba-d443-11ec-bce3-dac502259ad0.png

会覆盖掉MySender自身的无参BeanDefinition

所以会通过MySender的有参构造方法来创建MySender实例

因为有参构造方法依赖myListener,所以去Spring容器中找MyListener实例,没有找到则创建,然后填充MyListener实例的属性

以此类推,实例的创建过程如下所示:

f2d5de3e-d443-11ec-bce3-dac502259ad0.png

Is there an unresolvable circular reference?就此产生

相当于是变种的构造方法循环依赖

最初状态

我们还原MySender位置

f2f71892-d443-11ec-bce3-dac502259ad0.png

此时最先实例化的是MyConfig,实例化过程如下

f320f982-d443-11ec-bce3-dac502259ad0.png

对象是都可以正常实例化、初始化的

这种情况理论上来讲是不会出现Is there an unresolvable circular reference?

线上问题

一通分析下来,还是没能找到线上Is there an unresolvable circular reference?的原因

很是尴尬,但是我萌生了这样的想法:是不是在k8s部署过程中,BeanDefinition的扫描会有偶发的随机性?

问题修复

虽然我们没能找到线上问题的确切原因,但还是有办法去根治这个问题的

Spring不能处理构造方法循环依赖,那我们就去规避它

删掉MyConfigMySender改成

f34d389e-d443-11ec-bce3-dac502259ad0.png

MySender改成

f3695b96-d443-11ec-bce3-dac502259ad0.png

还有@PostConstruct等,方式有很多,只要不产生构造方法循环依赖就好

总结

1、BeanDefinition扫描顺序

如果我们去跟源代码就会发现,以启动类为起点,扫描启动类同级目录下的所有文件夹

按文件夹名升序顺序进行扫描,会递归扫描每个文件夹

文件扫描也是按文件名升序顺序进行

从线上问题来看,对这个扫描顺序,楼主是持怀疑态度的:是Spring会偶发的随机扫描,还是pod会导致偶发的随机扫描

2、BeanDefinition覆盖

只要我们读了源码,了解Spring对各个注解的扫描顺序,就清楚它们的替换关系了

BeanDefinition覆盖并不会影响BeanDefinition的扫描顺序

也就是不会改变BeanNamebeanDefinitionNames中的位置,即不会影响Bean的示例化顺序

3、Bean实例化顺序

理论上来讲,先被扫描到的就先被实例化,但实例化过程中的属性填充会打乱这个顺序,会将被依赖的对象提前实例化

4、Spring版本

一定要结合版本来看问题

版本不同,底层实现可能会不同

原文标题:记一次线上偶现的Spring循环依赖问题

文章出处:【微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

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

    关注

    8

    文章

    633

    浏览量

    29137
  • 循环
    +关注

    关注

    0

    文章

    92

    浏览量

    15955
  • spring
    +关注

    关注

    0

    文章

    338

    浏览量

    14307

原文标题:记一次线上偶现的Spring循环依赖问题

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    java spring教程

    Spring核心概念介绍控制反转(IOC)依赖注入(DI)集合对象注入等Bean的管理BeanFactoryApplicationContextSpring 在web中的使用
    发表于 09-11 11:09

    什么是java spring

    。在SSH项目中管理事务以及对象的注入Spring是非侵入式的:基于Spring开发的系统中的对象一般不依赖Spring的类。组成 Spring
    发表于 09-11 11:16

    Spring工作原理

    依赖关系核心:bean工厂;在Spring中,bean工厂创建的各个实例称作bean二.AOP(Aspect-Oriented Programming): 面向方面编程1.代理的两种方式:静态代理
    发表于 07-10 07:41

    Spring笔记分享

    ; 可以管理所有的组件(类)Spring的优良特性1) 非侵入式:基于Spring开发的应用中的对象可以不依赖Spring的API2) 依赖
    发表于 11-04 07:51

    spring教程ppt

    主要内容Spring 概述Spring 整体结构Spring实例Spring核心概念介绍控制反转(IOC)依赖注入(DI)
    发表于 09-11 11:00 138次下载
    <b class='flag-5'>spring</b>教程ppt

    Spring认证」什么是Spring GraphQL?

    spring-boot-starter-webflux HTTP、WebSocket 弹簧 WebFlux 依赖{    实现 'org.springframework.experimental
    的头像 发表于 08-10 14:08 796次阅读
    「<b class='flag-5'>Spring</b>认证」什么是<b class='flag-5'>Spring</b> GraphQL?

    Spring开发过程中依赖注入的几个知识点

    转自丨https://juejin.cn/post/6844904056230690824 本章的内容主要是想探讨我们在进行 Spring 开发过程当中,关于依赖注入的几个知识点。感兴趣的读者可以
    的头像 发表于 08-27 09:18 1627次阅读

    Spring Validation的使用

    之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation。本文会详细介绍Spring Validat
    的头像 发表于 09-08 10:31 869次阅读

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

    参考图中 spring 解决循环依赖 的过程可知,spring 利用三级缓中的 objectFactory 生成并返回一个 early 对象,提前暴露这个 early 地址,供其他对象
    的头像 发表于 12-22 10:34 514次阅读

    怎样使用Kiuwan保护Spring Boot应用程序呢?

    Spring Boot 提供了快速轻松地构建基于Spring 的应用程序所需的工具、功能和依赖项。
    的头像 发表于 03-16 09:10 758次阅读

    SpringBoot循环依赖的症状和解决方案

    循环依赖是指在Spring Boot 应用程序中,两个或多个类之间存在彼此依赖的情况,形成一个循环依赖
    的头像 发表于 05-06 15:30 794次阅读

    Spring Boot的启动原理

    可能很多初学者会比较困惑,Spring Boot 是如何做到将应用代码和所有的依赖打包成一个独立的 Jar 包,因为传统的 Java 项目打包成 Jar 包之后,需要通过 -classpath 属性
    的头像 发表于 10-13 11:44 620次阅读
    <b class='flag-5'>Spring</b> Boot的启动原理

    Spring Boot 的设计目标

    ,这样我们就可以尽快的上手。 使用 Spring Boot 来不仅可以创建基于 war 方式部署的传统Java应用程序,也可以通过创建独立的不依赖任何容器(如 tomcat 等)
    的头像 发表于 10-13 14:56 554次阅读
    <b class='flag-5'>Spring</b> Boot 的设计目标

    Spring依赖注入的方式

    Spring 是一个开源的轻量级框架,可以用于构建企业级应用程序。其最重要的特性之一是依赖注入(Dependency Injection,DI),这是一种设计模式,它可以帮助我们解耦代码、提高
    的头像 发表于 11-22 15:12 461次阅读

    Spring依赖注入的四种方式

    Spring框架中,依赖注入是一种核心的概念和机制。通过依赖注入,我们可以让对象之间的依赖关系更加松散,并且能够方便地进行单元测试和模块化开发。在
    的头像 发表于 12-03 15:11 1899次阅读