开心一刻
心里一直在想明天该以何种方式祭拜列祖列宗,彻夜难眠,辗转反侧,最好下定了决心
给弟发了个微信:别熬夜了,早上早点起来,咱俩去上坟
弟:知道了,哥
我:记得带上口罩
弟:坟就在家后边的山上,这么近带什么口罩?
我:就你这逼样,好意思见列祖列宗?
弟:我知道了,那哥你带吗?
我:我也带
前情回顾
一探
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
总结
BeanDefinition
如果我们去跟源代码就会发现,以启动类为起点,扫描启动类同级目录下的所有文件夹
按文件夹名升序顺序进行扫描,会递归扫描每个文件夹
文件扫描也是按文件名升序顺序进行
Spring 会偶发的随机扫描,还是 pod
BeanDefinition
Spring
BeanDefinition 覆盖并不会影响 BeanDefinition
BeanName 在 beanDefinitionNames 中的位置,即不会影响 Bean
Bean
理论上来讲,先被扫描到的就先被实例化,但实例化过程中的属性填充会打乱这个顺序,会将被依赖的对象提前实例化
Spring
一定要结合版本来看问题
版本不同,底层实现可能会不同