Springboot Bean创建流程、三种Bean注入方式(构造器注入、字段注入、setter注入)、循坏依赖问题

文章目录

    • 1 Bean 创建流程
      • 1.1 Bean的扫描注册
      • 1.2 创建Bean的顺序
    • 2 三种Bean注入方式
      • 2.1 构造器注入 | Constructor Injection(推荐)
      • 2.2 字段注入 | Field Injection(常用)
      • 2.3 方法注入 | Setter Injection
      • 2.4 三种方式注入顺序
    • 3 循环依赖
      • 3.1 构造器注入
      • 3.2 字段/setter注入
      • 3.3 乱想:构造器+字段?
      • 3.4 解决方案

1 Bean 创建流程

简单来说,当容器里要放的Bean很多时,Spring会优先创建依赖最少的Bean。

1.1 Bean的扫描注册

Spring启动后首先会根据SpringbootApplication的包扫描配置扫描包里的所有文件,然后将使用了注解标记的类(如@Component、@Service、@Repository、@Configuration)作为组件注册到上下文中,同时解析这些组件之间的依赖关系。

1.2 创建Bean的顺序

组件之间的依赖关系通常会使用图/树结构来表示,如果BeanA依赖BeanB,那么BeanA是BeanB的父节点。在创建Bean的时候,Spring会优先选择树的叶子结点进行创建,因为它不存在依赖,然后再不断向上层进行创建,也就是自底向上创建,这样才能尽量确保在创建某个Bean时,它依赖的Bean已经存在。

在创建Bean的时候,Spring仍会检查它需要的依赖是否已经存在,如果存在则直接注入,如果依赖Bean还没创建,那么会去递归创建依赖的Bean,直到所有依赖都被创建,再创建当前Bean。

2 三种Bean注入方式

2.1 构造器注入 | Constructor Injection(推荐)

构造器注入是在组件的构造函数中注入所需的依赖,它是在Bean创建时就注入依赖,创建流程如1.2。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class BeanA {private final BeanB beanB;private final BeanC beanC;public BeanA(BeanB beanB, BeanC beanC) {this.beanB = beanB;this.beanC = beanC;}// 其他方法...
}@Component
public class BeanB {// 其他方法...
}@Component
public class BeanC {// 其他方法...
}

使用构造器进行依赖注入时,依赖的对象通常会被声明为final,这样当对象创建后,依赖的Bean不会被改变,可以保证类的一致性。

这样注入的优势是能使Bean之间的依赖关系更加清楚,避免了字段注入可能存在的隐式依赖,如果存在问题(比如循环依赖)会在Spring初始化时就抛出异常,而不会等到执行时才出错。

需要注意的是不能显示提供无参构造函数,否则Spring会优先执行无参构造,导致所有依赖的Bean都为null,如果有多个构造函数,选择一个使用@Autowired注解,否则可能报错。

2.2 字段注入 | Field Injection(常用)

字段注入就是使用@Autowired注解自动注入依赖的Bean。它不会在构造函数中注入,而是通过反射在组件构造函数执行后才注入依赖Bean即Bean实例化完成后才注入依赖项)因此不能使用final修饰依赖Bean,因为使用final字段修饰的变量必须在声明时或在构造函数中初始化,而字段注入在构造函数之后执行。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class BeanA {@Autowiredprivate BeanB beanB;
}@Component
public class BeanB {// 其他方法...
}

字段注入是开发时最常用的方式,但由于字段注入是在Bean实例后才注入,属于隐式依赖,所以可能会存在空指针问题,而这个问题只有当程序运行时才出现,因此有一定隐患,所以Spring官方更推荐构造器注入。

2.3 方法注入 | Setter Injection

和字段注入类似,只是需要写好一个setter函数,在setter中注入依赖,一个setter方法通常对应一个依赖,@Autowired注解写在setter方法上:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class BeanA {private final BeanB beanB;private final BeanC beanC;@Autowiredpublic setBeanB(BeanB beanB) {this.beanB = beanB;}@Autowiredpublic setBeanC(BeanC beanC) {this.beanC = beanC;}

setter注入的优势是可以灵活注入bean,相比构造器一次性写入更加清晰一些,缺点和字段注入类似。

2.4 三种方式注入顺序

如果一个组件中同时存在以上三种注入方式,执行顺序是?

按照构造器注入–>字段注入–>方法注入的原则执行。

首先执行构造函数,注入在构造函数初始化的Bean。构造函数执行结束后,Spring将处理字段注入,然后在容器中查找并注入依赖Bean。最后如果存在带有@Autowired注解的setter方法,Spring会再调用这些方法注入依赖。

3 循环依赖

循环依赖指的是两个Bean之间互相需要对方作为成员变量,如BeanA需要注入BeanB,BeanB需要注入BeanA。

3.1 构造器注入

构造器注入时会通过构造函数注入所有必须的依赖,当两个组件BeanA和BeanB之间存在循坏依赖时,执行BeanA的构造函数需要注入BeanB(此时BeanA还未创建),由于BeanB还未生成,因此转而先创建BeanB,执行BeanB的构造函数,而BeanB同样需要注入BeanA,于是出现了死锁情况,两个Bean都无法创建,因此如果使用构造器注入而又出现循环依赖时,Spring会直接抛出BeanCurrentlyInCreationException异常。

3.2 字段/setter注入

使用字段/setter注入在循环依赖时不会抛出异常(但很容易出问题),主要是通过提前暴露Bean的方式来解决的。

因为在循环依赖的情况下,字段/setter注入会先创建当前对象的“代理”或“占位符”实例/引用(非完全实例,但可以拿来使用),然后通过反射等依赖对象存在后再注入依赖,因此不会在创建时出现死锁(没有循环等待),而构造器注入必须注入完全初始化的依赖后才能实例化,因此会死锁。

以BeanA/B为例:

  1. 当容器尝试创建BeanA时候,发现它依赖BeanB,然后转而去创建BeanB。
  2. 创建BeanB时,发现其依赖于BeanA,循环依赖出现,因此会在单例池中创建一个BeanA的占位符实例(未初始化)。
  3. BeanB创建后,Spring会将BeanA的占位符注入到BeanB中,此时BeanB完成注入,它的实例也创建完毕,将被放入单例池。
  4. 返回BeanA的创建,此时BeanA已经有占位符实例,BeanB也有实例,因此可以将BeanB注入BeanA中。
  5. BeanA和BeanB都完成初始化。

占位符实例并不是在 BeanA 开始创建时就生成的,而是依赖关系的解析过程中,当需要 BeanA 时才创建。因为当Spring开始创建一个Bean的时候会标记当前Bean为“正在创建”,如果在它的实例化过程中(递归注入依赖)发现有其他对象请求该bean,则证明循环依赖出现,Spring正是通过这种动态追踪的方式来识别循环依赖的。

如果不存在循环依赖,BeanB已经实例化那么会被直接注入BeanA,这样BeanA也会直接完成初始化实例。没有循环依赖不会生成占位符实例。

3.3 乱想:构造器+字段?

想到了一个组合:如果BeanA使用构造器注入BeanB,而BeanB使用字段注入注入BeanA,那么能否通过占位符实例实现注入呢?

答案是不行,原因主要有三点:

  1. 构造器注入要求注入的依赖必须是完全初始化的实例【核心】。
  2. 构造器注入时,在循环依赖情况下被动生成的占位符实例不允许使用(因为构造函数不允许注入未完全实例化的对象,本质上与第二点一样)。
  3. 构造器注入不会像字段注入那样生成占位符实例,因为就算生成了也不完全,无法使用

因此无论是先创建BeanA还是先创建BeanB都会抛出异常。

先创建BeanA:发现依赖BeanB,转而创建BeanB,又发现BeanB依赖BeanA,因此尝试创建BeanA的占位符实例,但是因为A是构造器注入,必须注入BeanB的完整实例(但并不存在),因此不允许使用占位符实例,失败。

先创建BeanB:发现依赖BeanA,转而创建BeanA,BeanA必须使用完全实例化的BeanB,不会创建BeanB的占位符实例,因此无法达成,失败。

3.4 解决方案

一般情况下需要避免循环依赖,如果存在,可以尝试将一些依赖关系移除,重构依赖关系,降低耦合。或者可以使用@Lazy注解延迟Bean的加载,懒加载可以让Bean在被使用时才注入。

@Component
public class BeanA {@Autowired@Lazyprivate BeanB beanB; // 延迟注入// 其他方法...
}@Component
public class BeanB {@Autowiredprivate BeanA beanA; // 直接注入// 其他方法...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/66732.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue实现虚拟列表滚动

<template> <div class"cont"> //box 视图区域Y轴滚动 滚动的是box盒子 滚动条显示的也是因为box<div class"box">//itemBox。 一个空白的盒子 计算高度为所有数据的高度 固定每一条数据高度为50px<div class"itemBox" :st…

STM32小实验2

定时器实验 TIM介绍 TIM&#xff08;Timer&#xff09;定时器 定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中断 16位计数器、预分频器、自动重装寄存器的时基单元&#xff0c;在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断…

HTB:Timelapse[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 提取并保存靶机TCP开放端口号 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机UD…

【贵州省】乡镇界arcgis格式shp数据乡镇名称和编码内容下载测评

shp数据字段乡镇名称和编码&#xff0c;坐标是wgs84&#xff0c;数据为SHP矢量格式&#xff0c;下载下来直接加载进ArcMap即可使用 下载地址&#xff1a;https://download.csdn.net/download/zhongguonanren99/14928126

[免费]微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序(高校就业)招聘系统(Springboot后端Vue管理端)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序(高校就业)招聘系统(Springboot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…

“AI智能实训系统:让学习更高效、更轻松!

大家好&#xff0c;作为一名资深产品经理&#xff0c;今天我来跟大家聊聊一款备受瞩目的产品——AI智能实训系统。在这个人工智能技术飞速发展的时代&#xff0c;AI智能实训系统应运而生&#xff0c;为广大学习者提供了全新的学习体验。那么&#xff0c;这款产品究竟有哪些亮点…

Linux下字符设备驱动编写(RK3568)

文章目录 一 基础知识概念特点常见应用场景 二 linux 下的字符设备字符设备在 /dev 目录下用 ls -l 命令查看字符设备文件类型主设备号和次设备号 三 字符驱动模块的编写1. 头文件引入2. 定义错误码枚举3. 设备操作函数定义4. 关键结构体与变量定义5. 驱动入口函数&#xff08;…

【ROS2】RViz2加载URDF模型文件

1、RViz2加载URDF模型文件 1)运行RViz2 rviz22)添加组件:RobotModel 3)选择通过文件添加 4)选择URDF文件,此时会报错,需要修改Fixed Frame为map即可 5)因为没有坐标转换,依然会报错,下面尝试解决 2、运行坐标转换节点 1)运行ROS节点:robot_state_publishe

大数据组件(三)快速入门实时计算平台Dinky

大数据组件(三)快速入门实时计算平台Dinky Dinky 是一个开箱即用的一站式实时计算平台&#xff08;同样&#xff0c;还有StreamPark&#xff09;&#xff0c;以 Apache Flink 为基础&#xff0c;连接数据湖仓等众多框架&#xff0c;致力于流批一体和湖仓一体的建设与实践。 Di…

TANGO - 数字人全身动作生成

文章目录 一、关于 TANGO演示视频&#xff08;YouTube&#xff09;&#x1f4dd;发布计划 二、⚒️安装克隆存储库构建环境 三、&#x1f680;训练和推理1、推理2、为自定义字符创建图形 一、关于 TANGO TANGO 是 具有分层音频运动嵌入 和 扩散插值的共语音手势视频再现 由东…

1月9日星期四今日早报简报微语报早读

1月9日星期四&#xff0c;农历腊月初十&#xff0c;早报#微语早读。 1、上海排查47家“俄罗斯商品馆”&#xff1a;个别店铺被责令停业&#xff0c;立案调查&#xff1b; 2、西藏定日县已转移受灾群众4.65万人&#xff0c;检测到余震646次&#xff1b; 3、国家发改委&#x…

Zemax 序列模式下的扩束器

扩束器结构原理 扩束器用于增加准直光束&#xff08;例如激光束&#xff09;的直径&#xff0c;同时保持其准直。它通常用于激光光学和其他需要修改光束大小或发散度的应用。 在典型的扩束器中&#xff0c;输入光束是准直激光器&#xff0c;或光束进入第一个光学元件。当光束开…

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法,以及自动化实时数据采集

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法&#xff0c;以及自动化实时数据采集 mmWave Studio提供的功能完全够用了 不用去纠结用DCA1000低延迟、无GUI传数据 速度最快又保证算力无非就是就是Linux板自己写驱动做串口和UDP 做雷达产品应用也不会采用DCA1000的…

Kubernetes Gateway API-4-TCPRoute和GRPCRoute

1 TCPRoute 目前 TCP routing 还处于实验阶段。 Gateway API 被设计为与多个协议一起工作&#xff0c;TCPRoute 就是这样一个允许管理TCP流量的路由。 在这个例子中&#xff0c;我们有一个 Gateway 资源和两个 TCPRoute 资源&#xff0c;它们按照以下规则分配流量&#xff1…

提升决策支持:五大报表软件功能全面评测

本文将为大家介绍五款功能强大的报表软件&#xff0c;包括山海鲸报表、JReport、Power BI、Zoho Analytics 和 SAP Crystal Reports。这些工具各具特色&#xff0c;能够帮助企业快速生成数据报表并进行深度分析。无论是数据可视化、报表定制、自动化生成还是与其他系统的集成&a…

Backend - C# EF Core 执行迁移 Migrate

目录 一、创建Postgre数据库 二、安装包 &#xff08;一&#xff09;查看是否存在该安装包 &#xff08;二&#xff09;安装所需包 三、执行迁移命令 1. 作用 2. 操作位置 3. 执行&#xff08;针对visual studio&#xff09; 查看迁移功能的常用命令&#xff1a; 查看…

GESP202312 四级【小杨的字典】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202312 四级] 小杨的字典 题目描述 在遥远的星球&#xff0c;有两个国家 A 国和 B 国&#xff0c;他们使用着不同的语言&#xff1a;A 语言和 B 语言。小杨是 B 国的翻译官&#xff0c;他的工作是将 A 语言的文章翻译成 B 语言的文章…

【软考网工笔记】计算机基础理论与安全——网络规划与设计

HFC 混合光纤同轴电缆网 HFC: Hybrid Fiber - Coaxial 的缩写&#xff0c;即混合光纤同轴电缆网。是一种经济实用的综合数字服务宽带网接入技术。 HFC 通常由光纤干线、同轴电缆支线和用户配线网络三部分组成&#xff0c;从有线电视台出来的节目信号先变成光信号在干线上传输…

KubeVirt 进阶:设置超卖比、CPU/MEM 升降配、在线磁盘扩容

前两篇文章&#xff0c;我们分别介绍 Kubevirt 的安装、基本使用 以及 将 oVirt 虚拟机迁移到 KubeVirt&#xff0c;我们留了两个ToDo&#xff0c;一个是本地磁盘的动态分配&#xff0c;一个是固定 IP 的需求&#xff0c;本期我们先解决第一个&#xff0c;本地磁盘的动态分配。…

自动化脚本本地可执行但是Jenkins上各种报错怎么解决

作者碎碎念&#xff1a; 测试环境 Jenkinsdockerpythonunittest&#xff0c; 测试问题&#xff1a;本人在写关于SAP4Me网站的自动化脚本时遇到一个问题 本地怎么都跑的通 但是一上Jenkins会出现各种各样的问题 因为在Jenkins里面脚本是放在docker环境里面跑的 所以环境的差异…