关于spring的@Bean注解放入引用Bean中初始化失败分析

 更新时间:2023年07月21日 09:55:50   作者:孔天逸  
这篇文章主要介绍了关于spring的@Bean注解放入引用Bean中初始化失败分析,Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理,产生这个Bean对象的方法Spring只会调用一次,需要的朋友可以参考下

以下讨论的问题及术语均在SpringBoot框架下,问题十分小众,仅做整理记录。

1. Bean依赖属性

Bean依赖属性的注入顺序,与代码定义顺序无关;

最好是将@Bean注解配置的Bean放在@Configuration注解修饰的专门用于配置的类中;

2. 问题背景

为了方便,将使用注解(@Bean)方法生成的Bean的方法体定义在了使用此Bean的类中

代码结构如下(为了描述方便,后文我们姑且将initBeanTestService叫做外层Bean,needInitBean叫做内层Bean):

错误代码

编写单元测试

运行printInitBeanValue方法,并在方法体内打断点便于观察属性值,

单元测试:

单元测试

运行单元测试会发现,通过内层Bean的属性值needInitValue的值为null,而外层Bean的属性值needInitValue有值

说明在初始化needInitBean时,外层Bean的属性值initValue并未注入成功,

运行结果:

测试结果

简单理下思路,因为外层Bean的类通过@Service注解进行修饰,所以SpringBoot在启动时会扫描到此注解进行Bean的初始化

初始化时会发现此Bean依赖initValueneeInitBean两个属性,读配置拿到initValue的值

然后去容器中查找是否有needInitBean存在,显然并不存在,于是要先初始化needInitBean,即内层Bean;

内层bean的初始化,依赖于外层bean的initValue属性值

从现象来看,此时initValue无值,我们有以下疑问:

此initValue为什么没有值?外层Bean按理说应该已经初始化一半了。

3. 调用栈追踪

为了解释上述问题1,我们在@Bean注解修饰的方法体内打断点,从内层Bean的初始化开始,沿着断点处的调用栈倒着追踪,

1.首先是一些反射包下的方法;

2.一些BeanFactory初始化bean的方法;

3.找到AbstractBeanFactory中,发现此处开始创建needInitBean,那么上边的调用方就是初始化此Bean的触发点;

4.找到CommonAnnotationBeanPostProcessor,发现是此处为触发点;

5.在CommonAnnotationBeanPostProcessor一番游历,发现此处的逻辑是向外层Bean中注入依赖,找到319行,findResourceMetadata,此方法为找到需要注入的属性或方法的元数据,紧接着321行,为依赖注入逻辑(当然,若依赖是Bean,则去BeanFactory请求,找不到则进行初始化);

注入

点进去findResourceMetadata方法看看他是咋找要注入的属性的,包了一层缓存,主要逻辑在buildResourceMetadata方法,这里我们会发现,他遍历了各个属性和方法,找到有特定注解的属性和方法,放到了待注入的列表。其中注解就包括了我们熟悉的,也是外层bean中needInitBean头上的@Resource。但是并没有发现我们同样熟悉的@Value@Autowire

resource

6.继续跟着调用栈往下走,到AbstractAutowireCaptableBeanFactory中,发现有一个循环去遍历BeanPostProceccer, 并过滤出InstantiationAwareBeanPostProcessor,对创建中的Bean进行处理,展开BeanPostProceccer的列表,会发现我们上边看到的CommonAnnotationBeanPostProcessor后边还有个AutowiredAnnotationBeanPostProcessor,此类也继承自InstantiationAwareBeanPostProcessor, 所以也会遍历到,然后我们就会发现他与5中描述的逻辑类似,也是先找到需要注入的属性,然后执行注入。不同的是它解析@Value@Autowire注解的属性为需要注入的属性;

在这里插入图片描述

7.上面提到的遍历逻辑,是在对外层Bean进行依赖注入,即外层Bean的初始化过程,因为外层Bean是@Service注解修饰的,所以会在SpringBoot启动时扫描到进行初始化

所以我们再往下走没几步就到了SpringApplication.run

4. 问题出现逻辑梳理

  1. 应用启动,扫描@Service注解修饰的外层Bean,对其进行初始化;
  2. Bean的初始化由若干实现InstantiationAwareBeanPostProcessor接口的类在一个循环中依次对Bean进行处理;
  3. 循环中负责依赖注入的类CommonAnnotationBeanPostProcessor发现属性needInitBean@Resource修饰,需要进行注入,此时BeanFactory中没有needInitBean这个Bean,故对其进行初始化,此时外层Bean的initValue还没有注入进来,所以内层Bean初始化needInitValuenull
  4. 循环中负责依赖注入的类AutowiredAnnotationBeanPostProcessor发现属性initValue@Value修饰,需要进行注入,执行注入;
  5. 完成外层Bean的创建;

5. 结论

通过上述追踪,我们可以得出出现我们最初问题的原因:由于@Value@Resource在注入时并非用一个类进行注入,存在先后关系

故虽然外层Bean已经初始化一半去初始化内层Bean,initValue仍然没有值。

另外退一步说,如果我们使用的是@Autowire,而不是@Resource@Autowire@Value是由同一个BeanPostProceccer进行注入的

是不是@Value写在前面,本程序就能通呢?

运行了一下是可以的,然而这并不严谨,因为就算是同一个BeanPostProceccer进行注入, 其属性的注入顺序是依赖反射包下的Class.getDeclaredFields方法获得的,而此方法注释明确写道,返回的数组是无序的。

所以我们尽量还是避免这种写法,将@Bean注解配置的Bean放在@Configuration注解修饰的专门用于配置的类中较为稳妥。

ps: 如果我们将initValue使用属性注入,而needInitBean使用@Autowire修饰setter注入,可以保证严谨,因为目前的实现都是先进行属性注入在进行方法注入,不提倡。

到此这篇关于关于spring的@Bean注解放入引用Bean中初始化失败分析的文章就介绍到这了,更多相关spring@Bean注解引用Bean内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 通过实例解析Python文件操作实现步骤

    通过实例解析Python文件操作实现步骤

    这篇文章主要介绍了通过实例解析Python文件操作实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Selenium alert 弹窗处理的示例代码

    Selenium alert 弹窗处理的示例代码

    这篇文章主要介绍了Selenium alert 弹窗处理的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Python利用脚本实现自动发送电子邮件

    Python利用脚本实现自动发送电子邮件

    这篇文章主要为大家详细介绍了Python如何利用脚本实现自动发送电子邮件功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-01-01
  • python中的sys模块和os模块

    python中的sys模块和os模块

    这篇文章主要介绍了python中的sys模块和os模块,sys模块提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数,os模块提供了多数操作系统的功能接口函数,下文更多相关内容需要的小伙伴可以参考一下
    2022-03-03
  • 基于Python实现迪杰斯特拉和弗洛伊德算法

    基于Python实现迪杰斯特拉和弗洛伊德算法

    这篇文章主要为大家详细介绍了基于Python实现迪杰斯特拉和弗洛伊德算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • Python分支语句与循环语句应用实例分析

    Python分支语句与循环语句应用实例分析

    这篇文章主要介绍了Python分支语句与循环语句应用,结合具体实例形式详细分析了Python分支语句与循环语句各种常见应用操作技巧与相关注意事项,需要的朋友可以参考下
    2019-05-05
  • python添加模块搜索路径和包的导入方法

    python添加模块搜索路径和包的导入方法

    今天小编就为大家分享一篇python添加模块搜索路径和包的导入方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-01-01
  • Python中lambda排序的六种方法

    Python中lambda排序的六种方法

    本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • Python Django 通用视图和错误视图的使用代码

    Python Django 通用视图和错误视图的使用代码

    这篇文章主要介绍了Python Django 通用视图和错误视图的使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • python压缩文件夹内所有文件为zip文件的方法

    python压缩文件夹内所有文件为zip文件的方法

    这篇文章主要介绍了python压缩文件夹内所有文件为zip文件的方法,可实现简单的zip文件压缩功能,需要的朋友可以参考下
    2015-06-06

最新评论