@Bean放入其引用Bean中初始化失败分析

2023-05-16

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


1. 先说重点

  1. Bean依赖属性的注入顺序,与代码定义顺序无关;
  2. 最好是将@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. 6中提到的遍历逻辑,是在对外层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注入,可以保证严谨,因为目前的实现都是先进行属性注入在进行方法注入,不提倡。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@Bean放入其引用Bean中初始化失败分析 的相关文章

随机推荐

  • 0.嵌入式控制器EC实战 Embedded Controller开发概述

    文章目录 1 嵌入式控制器EC概述2 EC芯片框图 xff08 IT8502为例 xff09 3 General Purpose I O Port GPIO 4 SMBus xff08 System Management Bus xff0c
  • 12.嵌入式控制器EC实战 SMBus概述

    文章目录 SMBus概述 ACPI规范中的函数返回值含义 SMBus h中的宏定义含义 EC中SMBus各个读写函数分析 bRWSMBus读写函数 在嵌入式控制器EC中 SMBus的起到的作用有两个 第一个是通过SMBus读取智能电池中的相
  • Win10下开机自动启动运行bat脚本并打开cmd运行命令

    场景 xff1a 本菜鸟有一台工作站安装了windows10操作系统 xff0c 机器设置的是开机自动启动 xff0c 但是维护人员无法一直在机房 xff0c 一旦机房断电重启就会导致工作站中运行的程序无法重新启动 xff0c 需要维护人员
  • 银河麒麟V10系统 syslog和kern.log文件过大问题解决,定时清理日志文件

    文章目录 1 新建clear log sh脚本文件 2 设置cron任务 3 解决不执行的方法 需求 在使用银河麒麟V10系统时 var log kern log 和 var log syslog两个文件随着使用的时间增长会一直增大 最后可
  • 2.龙芯2k1000 linux3.10内核编译过程

    龙芯2k1000 linux3 10内核编译过程 文章目录 龙芯2k1000 linux3 10内核编译过程 xff08 一 xff09 在Ubuntu环境下载并配置交叉编译链 xff08 二 xff09 下载linux3 10内核源码 x
  • Vulkan实战之逻辑设备和队列

    文章目录 介绍指定要创建的队列指定使用的设备特性创建逻辑设备检索队列句柄最终代码 介绍 在选择要使用的物理设备之后 xff0c 我们需要设置一个逻辑设备来与它接口 逻辑设备创建过程类似于实例创建过程 xff0c 并描述了我们想要使用的特性
  • Vulkan实战之Window surface

    文章目录 创建window surface查询演示支持创建surface队列最终代码 由于Vulkan是一个平台无关的API xff0c 因此它不能自己直接与窗口系统接口交互 为了在Vulkan和窗口系统之间建立连接并将结果显示到屏幕上 x
  • 飞腾UEFI配置GPIO—飞腾FT2000/4 (D2000/8) GPIO引脚配置及使用

    文章目录 一 FT2000 4 GPIO介绍 GPIO 接口信号说明 专用GPIO GPIO0 A1 GPIO0 A7 SCI 介绍 二 UEFI下配置GPIO相关寄存器 GPIO相关相关引脚功能配置说明 UEFI配置GPIO相关代码 UE
  • stm32-sbus数据接收,并通过CAN转发给车辆控制

    1 xff09 串口程序 代码如下 xff1a span class token macro property span class token directive hash span span class token directive
  • 基于stm32的无线多点温度采集系统设计

    本科时候做过关于ds18b20温度传感器的课程设计 xff0c 当时好像是先用单片机A采集温度 xff0c 其中用矩阵键盘设置报警值 xff0c 然后通过232串口将温度值传给单片机B xff0c 单片机B上的数码管显示 xff0c 同时单
  • 无刷电机和桨叶的选择

    无刷电机和桨叶的选择 无刷电机的kv值越高 就要配越小的螺旋桨 简单说 高kv配小桨 低kv配大桨 无刷电机KV值定义为 转速 V xff0c 意思为输入电压增加1伏特 xff0c 无刷电机空转转速增加的转速值 由此无刷电机电压的输入与电机
  • Intel RealSense Win10+QT+Cmaker 开发环境搭建

    文章目录 一 Intel RealSense SDK开发工具下载安装二 QT 43 CMaker配置Intel RealSense SDK2 0开发环境 一 Intel RealSense SDK开发工具下载安装 从官方github上下载S
  • NVIDIA Jetson不同系列对比

    文章目录 一 NVIDIA Jetson介绍二 NVIDIA Jetson模组比较 一 NVIDIA Jetson介绍 NVIDIA Jetson 是世界领先的平台 xff0c 适用于自主机器和其他嵌入式应用程序 该平台包括 Jetson
  • SLAM笔记五——EKF-SLAM

    上一节主要讲解了EKF的基本原理 xff0c 这一次主要关注如何将EKF算法应用在SLAM上 EKF SLAM 现在的问题就是解决下面这个概率分布的估计问题 xff1a 阴影部分为未知 这里我们需要确定均值和方差到底是什么 xff1f 假设
  • HTTP协议-报文解析

    概述 HTTP xff08 超文本传输协议 xff09 是一个基于请求与响应模式的 无状态的 应用层的协议 xff0c 常基于TCP的连接方式 HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成 请求消息和响应消息的组成 xff1
  • 站在巨人的肩膀上——Linux信号量操作

    感谢那些让我度过此学海的无名勇士 信号量简介 xff1a 在对于临界区资源管理的过程中 xff0c 多个程序同时访问一个共享资源经常容易引发一系列问题 xff1a 如死锁 xff0c 结果不唯一等等 xff0c 在1965年 xff0c 由
  • 详解常用的ROS内置消息类型

    1 std msgs 该类型是ROS内置的标准消息类型 xff0c 是最基础的消息类型 xff08 1 xff09 对于单类型 xff0c 下表是其与C 43 43 和python的对应关系 xff1a Primitive TypeSeri
  • 【Robomaster-ICRA-AI挑战赛跳坑篇】(二、TX2刷机)

    TX2刷机与程序下载 TX2安装在步兵车上作为机载计算机使用 xff0c 跑上层代码 xff0c RoboRTS代码托管在github上 xff1a https github com RoboMaster RoboRTS xff0c 目前一
  • 字符串目录判断

    tcschr tcsrchr 好处 xff1a 是可以不管是用unicode 编码还是其他 代码都不用改 C 43 43 标准库函数提供了字符和字符串的操作函数 xff0c 并提供了其UNICODE版本 xff0c 如 xff1a 1 tc
  • @Bean放入其引用Bean中初始化失败分析

    以下讨论的问题及术语均在SpringBoot框架下 xff0c 问题十分小众 xff0c 仅做整理记录 1 先说重点 Bean依赖属性的注入顺序 xff0c 与代码定义顺序无关 xff1b 最好是将 64 Bean注解配置的Bean放在 6