前情回顾
一探
Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗中讲到了循环依赖问题
同样说明了Spring只能解决setter方式的循环依赖,不能解决构造方法的循环依赖
重点介绍了Spring是如何解决setter方式的循环依赖,感兴趣的可以去看下
二探
既然Spring不能解决构造方法的循环依赖,那么它是如何甄别构造方法循环依赖的了?
所以进行了二探:再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?
从源码的角度讲述了Spring是如何判定构造方法循环依赖、原型循环依赖的
感兴趣的可以去看下
大家跟源码的时候,一定要注意版本!!!
项目模拟
自认为经过了前两探,对Spring循环依赖的问题已了若指掌,可面对线上突如其来的循环依赖问题,楼主竟然没能一眼看出来!!!
这楼主能忍?于是楼主又跟起了Spring源码,看看问题到底出在哪?
SpringBoot版本是2.0.3.RELEASE
线上服务采用k8s部署,本地环境未采用k8s部署
本地启动从未出现循环依赖问题,线上环境也只是偶发的pod启动失败(提示信息直指循环依赖)
问题偶发,而非必现,很是头疼,但问题还是得解决,从提示信息着手呗
根据错误提示信息,楼主模拟出了一个简化的工程,方便我们进行问题排查
非常简单,完整地址:spring-other-circular-reference
我们来看下类图
MyListener、MyService、MyManager很常规,特殊的是MyConfig和MySender
问题复现
如果按上述工程结构,本地很难复现问题 ,反正楼主是没复现出来
我们稍做调整,将MySender前置,如下
启动失败,错误信息如下:
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定义的扫描
文件夹的扫描顺序与文件夹名字的升序一致,文件的顺序与文件名的升序一致,如下所示
有兴趣的可以去跟下ConfigurationClassParser类中doProcessConfigurationClass方法;楼主做了下简单的总结
@ComponentScan的处理早于@Bean
BeanDefinition扫描过程中,会按扫描顺序会往DefaultListableBeanFactory的beanDefinitionMap中添加BeanDefinition,往beanDefinitionNames添加BeanName
我们来跟下源码,看是不是如上所说
先被扫描的BeanDefinition的BeanName会被先添加到beanDefinitionNames
BeanDefinition 覆盖
MyConfig中通过@Bean定义了MySender,而MySender类上又用了@Component进行修饰
那创建MySender实例的时候到底调用的哪个构造方法?(有参还是无参?)
关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?从源码的角度分析了这个问题
结论是:SpringBoot 2.0.3.RELEASE中,@Configuration + @Bean修饰的BeanDefinition会覆盖掉@Component修饰的BeanDefinition
也就说MySender类上的@Component其实没用,加不加效果是一样的,这里说的没用、效果仅仅指的是MySender的BeanDefinition
Bean 实例化顺序
BeanDefinition用来构建实例,那么MySender上的@Component就有作用了,它决定了MySender的实例化顺序
是先于MyConfig、MyListener、MyServiceImpl、MyManager实例化的
我们来看下Bean的实例化顺序
理论上来讲,先被扫描的Bean会先被实例化;Bean实例化的过程中会填充属性,可能会导致后被扫描的Bean提前被实例化
如果Bean之间没有依赖,那么会严格按照Bean的扫描顺序实例化
再看问题
我们再回到前面的问题
这种情况下,我们分析下Is there an unresolvable circular reference?是如何产生的
相较于MyConfig、MyListener、MyManager、MyServiceImpl,MySender是最先被扫描到的,所以它最先被实例化
因为MyConfig中通过@Bean修饰了MySender的BeanDefinition
会覆盖掉MySender自身的无参BeanDefinition
所以会通过MySender的有参构造方法来创建MySender实例
因为有参构造方法依赖myListener,所以去Spring容器中找MyListener实例,没有找到则创建,然后填充MyListener实例的属性
以此类推,实例的创建过程如下所示:
Is there an unresolvable circular reference?就此产生
相当于是变种的构造方法循环依赖
最初状态
我们还原MySender位置
此时最先实例化的是MyConfig,实例化过程如下
对象是都可以正常实例化、初始化的
这种情况理论上来讲是不会出现Is there an unresolvable circular reference?
线上问题
一通分析下来,还是没能找到线上Is there an unresolvable circular reference?的原因
很是尴尬,但是我萌生了这样的想法:是不是在k8s部署过程中,BeanDefinition的扫描会有偶发的随机性?
问题修复
虽然我们没能找到线上问题的确切原因,但还是有办法去根治这个问题的
Spring不能处理构造方法循环依赖,那我们就去规避它
删掉MyConfig,MySender改成
或MySender改成
还有@PostConstruct等,方式有很多,只要不产生构造方法循环依赖就好
总结
1、BeanDefinition扫描顺序
如果我们去跟源代码就会发现,以启动类为起点,扫描启动类同级目录下的所有文件夹
按文件夹名升序顺序进行扫描,会递归扫描每个文件夹
文件扫描也是按文件名升序顺序进行
从线上问题来看,对这个扫描顺序,楼主是持怀疑态度的:是Spring会偶发的随机扫描,还是pod会导致偶发的随机扫描
2、BeanDefinition覆盖
只要我们读了源码,了解Spring对各个注解的扫描顺序,就清楚它们的替换关系了
BeanDefinition覆盖并不会影响BeanDefinition的扫描顺序
也就是不会改变BeanName在beanDefinitionNames中的位置,即不会影响Bean的示例化顺序
3、Bean实例化顺序
理论上来讲,先被扫描到的就先被实例化,但实例化过程中的属性填充会打乱这个顺序,会将被依赖的对象提前实例化
4、Spring版本
一定要结合版本来看问题
版本不同,底层实现可能会不同
-
源码
+关注
关注
8文章
633浏览量
29137 -
循环
+关注
关注
0文章
92浏览量
15955 -
spring
+关注
关注
0文章
338浏览量
14307
原文标题:记一次线上偶现的Spring循环依赖问题
文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论