当前位置: 首页 > news >正文

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; // 直接注入// 其他方法...
}

相关文章:

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 Injection2.4 三种方式注入顺序 3…...

mybatisX插件的使用,以及打包成配置

装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己要的包和类; 如果要把自己的配置设置成一个自定义模板&a…...

【初阶数据结构】线性表之单链表

文章目录 前言 一、单链表的概念与结构 1.概念 2.结点 3.性质 二、实现单链表 1.结构的定义 2.链表的打印和结点的申请 3.单链表的尾插和头插 4.单链表的尾删和头删 5.单链表的查找 6.指定位置之前插入数据和指定位置之后插入数据 7.删除pos结点和删除pos之后的结…...

CentOS7通过yum安装JDK

CentOS7通过yum安装JDK 1、卸载自带的JDK 查看已安装的JDK rpm -qa | grep java删除已安装的JDK yum -y remove java-1.8.0-openjdk*验证是否删除成功 查不到版本信息则已删除成功 java -version2、安装JDK sudo yum install java-1.8.0-openjdk java-1.8.0-openjdk-deve…...

c# 常见的几种取整场景

软件取整,通常指的是在计算机软件中对数值进行取整操作,即将一个浮点数或小数转换为整数,同时确定如何处理小数部分。取整操作在编程和数学计算中非常常见,不同的取整方法适用于不同的场景。 常见的取整方法 向零取整&#xff08…...

数据库回滚:大祸临头时

原文地址 什么是数据库回滚? 数据库技术中,回滚是通过撤销对数据库所做的一项或多项更改,将数据库返回到先前状态的操作。它是维护数据完整性和从错误中恢复的重要机制。 什么时候需要数据库回滚? 数据库回滚在以下几个场景中很…...

【GoLang】两个字符串如何比较大小?以及字典顺序的比较规则

在 Go 语言中,字符串的比较是基于字典顺序进行的。 字典顺序的比较规则: 比较两个字符串从左到右逐个字符的Unicode码点值, 若比较结果不相等则将此结果作为字符串大小的结果, 若比较结果相等则比较下一位, 若其中一个…...

5G学习笔记之SNPN系列之UE入网和远程配置

参考:3GPP 23.501 5.30.2.10 Onboarding of UEs for SNPNs 小小协议搬运工 目录 0. NPN系列 1. 概述 2. SNPN作为ONN 2.1 DCS参与的入网主鉴权 2.2 DCS不参与的入网主鉴权 2.3 UE入网 3. PLMN作为ONN 4. 远程配置 0. NPN系列 1. NPN概述 2. NPN R18 3. 【SNPN系列】…...

C#版OpenCv常用函数大全

OpenCvSharp 是 OpenCV 的NET封装,提供了丰富的图像处理和计算机视觉功能。以下是一些常用函数及其详细说明。 1. 图像读取与显示 Cv2.ImRead 功能:读取图像文件并返回一个 Mat 对象。用法:Mat image Cv2.ImRead("path/to/image.jpg&…...

Spring Boot教程之五十二:CrudRepository 和 JpaRepository 之间的区别

Spring Boot – CrudRepository 和 JpaRepository 之间的区别 Spring Boot建立在 Spring 之上,包含 Spring 的所有功能。由于其快速的生产就绪环境,使开发人员能够直接专注于逻辑,而不必费力配置和设置,因此如今它正成为开发人员…...

蓝桥杯备考:数据结构之栈 和 stack

栈的概念以及栈的实现 栈是一种只允许在一端进行插入和删除的线性表 空栈:没有任何元素 入栈:插入元素 出栈:删除元素 栈本身就是一个线性表,我们可以写一个足够大的数组来实现栈 除此之外,我们还需要变量n来记录…...

solidity基础 -- 映射

在区块链的智能合约开发领域,Solidity 作为以太坊上最主流的编程语言之一,拥有诸多强大特性助力开发者构建复杂且高效的去中心化应用。其中,映射(Mapping)是一个极为关键的数据结构,它为合约中的数据存储与…...

Angular 11课程实践:构建高效单页应用的支持代码

本文还有配套的精品资源,点击获取 简介:Angular 11是Google支持的前端框架,适合构建复杂的单页应用(SPA)。本课程将深入介绍Angular核心特性,如组件化、依赖注入、数据绑定和路由,并且涵盖Ang…...

测试用例颗粒度说明

当我们在编写测试用例时,总是会遇到一个问题:如何确定测试用例的颗粒度?测试用例过于粗糙,可能无法全面覆盖系统的细节;而颗粒度过细,又会导致测试重复、冗余。掌握合适的颗粒度,不仅可以提高测…...

ESP32 IDF VScode出现头文件“无法打开 源 文件 ”,并有红色下划线警告

问题背景: ESP32 IDF VScode出现头文件“无法打开 源 文件 ”,并有红色下划线警告: 解决办法: 在工程里面的.vscode文件夹下,检查是否存在c_cpp_properties.json文件,如果没有可以手动创建添加。如图…...

Windows安装ES单机版设置密码

下载ES ES下载链接 我用的是7.17.26 启动前配置 解压之后打开D:\software\elasticsearch-7.17.26\bin\elasticsearch-env.bat 在elasticsearch-env.bat文件中修改jdk的路径 修改前 修改内容 if defined ES_JAVA_HOME (set JAVA"D:\software\elasticsearch-7.17.26\…...

Linux Docker

Docker 的定义 Docker 是一个开源的容器化平台,它允许开发者将应用程序及其依赖项打包成一个可移植的容器。容器是一种轻量级、独立的运行环境,与传统的虚拟机不同,容器共享主机操作系统的内核,通过隔离的文件系统、进程空间和网…...

MSE学习

MSE简介 媒体源拓展&#xff08;Media Source Extensions&#xff0c;简称 MSE&#xff09;是一个由 W3C 制定的标准&#xff0c;它允许 JavaScript 代码通过 AJAX 请求获取媒体数据&#xff0c;并将其提供给 HTML 的 <video> 或 <audio> 元素进行播放。 MSE特点…...

0-基于蚁群优化和带注意力机制的循环神经网络的新型混合算法用于解决旅行商问题(HAL science)(完)

文章目录 AbstractI INTRODUCTIONII 旅行商问题的正式描述III STATE OF THE ARTIV 使用的混合化技术原理4.1 Principle of ACO4.2具有注意机制的自动编码器模型V 蚁群优化与具有注意机制的神经网络的混合5.1 基本思想5.2 解决步骤5.2.1 模型训练5.2.2 寻找解VI EXPERIMENTS6.1 …...

MIUI显示/隐藏5G开关的方法,信号弱时开启手机Wifi通话方法

5G网速虽快&#xff0c;手机功耗也大。 1.取消MIUI强制的5G&#xff0c;手动设置4G的方法&#xff01; 【小米澎湃OS, Xiaomi HyperOS显示/隐藏5G开关的方法】 1.1.小米MIUI系统升级后&#xff0c;被强制连5G&#xff0c;手动设置开关被隐藏&#xff0c;如下图&#xff1a; 1…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋

随着工业以太网的发展&#xff0c;其高效、便捷、协议开放、易于冗余等诸多优点&#xff0c;被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口&#xff0c;具有实时性、开放性&#xff0c;使用TCP/IP和IT标准&#xff0c;符合基于工业以太网的…...

解析“道作为序位生成器”的核心原理

解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制&#xff0c;重点解析"道作为序位生成器"的核心原理与实现框架&#xff1a; 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...

工厂方法模式和抽象工厂方法模式的battle

1.案例直接上手 在这个案例里面&#xff0c;我们会实现这个普通的工厂方法&#xff0c;并且对比这个普通工厂方法和我们直接创建对象的差别在哪里&#xff0c;为什么需要一个工厂&#xff1a; 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类&#xff1a; 两个发…...

基于Uniapp的HarmonyOS 5.0体育应用开发攻略

一、技术架构设计 1.混合开发框架选型 &#xff08;1&#xff09;使用Uniapp 3.8版本支持ArkTS编译 &#xff08;2&#xff09;通过uni-harmony插件调用原生能力 &#xff08;3&#xff09;分层架构设计&#xff1a; graph TDA[UI层] -->|Vue语法| B(Uniapp框架)B --&g…...

OpenGL-什么是软OpenGL/软渲染/软光栅?

‌软OpenGL&#xff08;Software OpenGL&#xff09;‌或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式&#xff08;包括几何处理、光栅化、着色等&#xff09;&#xff0c;不依赖GPU硬件加速。这种模式通常性能较低&#xff0c;但兼容性极强&#xff0c;常用于不支持硬件加速…...