【Spring】依赖注入的方式:构造方法、setter注入、字段注入
在Spring框架中,除了构造器注入(Constructor Injection)和Setter注入(Setter Injection),还有一种依赖注入方式:字段注入(Field Injection)。字段注入通过在Bean的字段上直接使用@Autowired(或@Resource、@Inject)注解来注入依赖。这种方式在Spring中常用于单例Bean,但也有其局限性和争议。
以下是对字段注入的详细说明,包括代码示例、优缺点、与构造器/Setter注入的对比,以及在单例Bean循环依赖中的表现。
1. 字段注入(Field Injection)
-
定义:通过在Bean的私有字段上添加
@Autowired注解,Spring直接通过反射将依赖注入到字段中,无需构造器或Setter方法。 -
特点:
- 依赖注入由Spring容器在Bean创建后通过反射完成。
- 字段通常是私有的,无需提供Getter/Setter,代码简洁。
- 依赖注入的时机在Bean实例化后、初始化前(类似Setter注入)。
-
代码示例:
@Component public class MyService {public String process() {return "Processed by MyService";} }@Controller public class MyController {@Autowiredprivate MyService myService; // 字段注入@GetMapping("/test")public String test() {return myService.process();} }- Spring会通过反射将
MyService的单例实例注入到MyController的myService字段。
- Spring会通过反射将
-
配置方式:
- 仅需在字段上添加
@Autowired(或@Resource、@Inject)。 - 不需要XML或Java配置显式指定字段注入,Spring自动处理。
- 如果字段是可选依赖,可设置
@Autowired(required = false):@Autowired(required = false) private MyService myService;
- 仅需在字段上添加
2. 字段注入与循环依赖
-
单例Bean中的循环依赖:
- 字段注入的注入时机与Setter注入类似,发生在Bean实例化后、初始化前。
- Spring通过三级缓存(
singletonObjects、earlySingletonObjects、singletonFactories)解决单例Bean的循环依赖。 - 字段注入支持循环依赖的解决,行为与Setter注入一致。例如:
@Component public class BeanA {@Autowiredprivate BeanB beanB; }@Component public class BeanB {@Autowiredprivate BeanA beanA; }- 解决流程:
- 创建
BeanA,实例化后放入三级缓存(ObjectFactory)。 - 为
BeanA注入beanB,触发BeanB创建,BeanB放入三级缓存。 BeanB需要BeanA,从三级缓存获取BeanA的早期引用,注入到beanB字段。BeanB完成,放入一级缓存;BeanA继续注入beanB,完成并放入一级缓存。
- 创建
- 结果:循环依赖通过三级缓存成功解决,
BeanA和BeanB相互引用。
- 解决流程:
-
非单例Bean(如
prototype):- 字段注入无法解决原型作用域的循环依赖,因为Spring不缓存原型Bean。
- 会抛出
BeanCurrentlyInCreationException,需使用@Lazy或ObjectProvider解决。
-
构造器注入对比:
- 字段注入与Setter注入类似,支持循环依赖的自动解决。
- 构造器注入由于依赖在实例化时注入,无法利用三级缓存解决循环依赖,需
@Lazy或改用字段/Setter注入。
3. 字段注入的优缺点
优点
- 代码简洁:
- 无需编写构造器或Setter方法,减少样板代码。
- 适合快速开发或小型项目。
- 直观:
- 依赖直接在字段上声明,易于查看Bean的依赖关系。
- 支持循环依赖:
- 与Setter注入类似,字段注入天然支持单例Bean的循环依赖解决。
- 灵活性:
- 支持可选依赖(
@Autowired(required = false)),字段可以为空。
- 支持可选依赖(
缺点
- 隐藏依赖关系:
- 依赖未通过构造器或Setter显式声明,难以通过代码接口了解Bean的完整依赖。
- 违反“显式优于隐式”的原则。
- 测试困难:
- 字段注入依赖Spring的反射机制,单元测试无法通过构造器或Setter传入Mock对象。
- 需使用反射工具(如
ReflectionTestUtils)或PowerMock修改私有字段,增加测试复杂性。
- 不可变性缺失:
- 字段注入的依赖无法使用
final修饰,可能被运行时修改(例如通过反射或手动赋值),影响线程安全。
- 字段注入的依赖无法使用
- 耦合Spring框架:
- 字段注入依赖
@Autowired等Spring注解,Bean与Spring容器强耦合,难以脱离Spring使用。
- 字段注入依赖
- 潜在空指针风险:
- 如果忘记配置依赖或Spring未正确注入,可能导致运行时
NullPointerException(尤其是required = false时)。
- 如果忘记配置依赖或Spring未正确注入,可能导致运行时
- 不推荐在现代Spring中:
- Spring官方和社区(如Spring Boot)更推荐构造器注入,字段注入被视为“过时”或“不优雅”的方式。
4. 字段注入 vs 构造器注入 vs Setter注入
| 特性 | 字段注入 | 构造器注入 | Setter注入 |
|---|---|---|---|
| 代码简洁性 | 最简洁,无需方法 | 需要构造器,稍复杂 | 需要Setter方法,中等复杂 |
| 依赖强制性 | 可选(required = false) | 强制,必须提供依赖 | 可选,依赖可以为空 |
| 不可变性 | 不支持(非final) | 支持(final修饰) | 不支持,依赖可修改 |
| 循环依赖 | 支持(三级缓存) | 不支持(需@Lazy) | 支持(三级缓存) |
| 线程安全 | 较低(可修改字段) | 较高(不可变) | 较低(可修改) |
| 测试友好 | 困难(需反射) | 简单(通过构造器Mock) | 中等(通过Setter Mock) |
| 耦合Spring | 高(依赖注解) | 低(可无注解) | 中等(需注解或XML) |
| 推荐度 | 不推荐(仅简单场景) | 推荐(现代Spring首选) | 次选(可选依赖或循环依赖) |
5. 单例Bean中字段注入的行为
- 单例Bean:
- 默认情况下,Spring容器为每个Bean定义创建单一实例,字段注入的依赖也是单例Bean的同一实例。
- 多个请求访问
MyController,共享同一个MyController实例及其myService字段。
- 线程安全:
- 如果
myService字段仅用于读取(无修改),字段注入在单例Bean中是线程安全的。 - 如果运行时通过反射或其他方式修改
myService字段,可能引发线程安全问题(类似Setter注入)。
- 如果
- 循环依赖:
- 字段注入与Setter注入一样,利用Spring的三级缓存解决单例Bean的循环依赖。
- 注入时机在Bean实例化后,允许Spring先创建Bean再注入早期引用。
6. 字段注入的替代方案
由于字段注入的缺点,推荐以下替代方案:
-
构造器注入(首选):
@Controller public class MyController {private final MyService myService;@Autowiredpublic MyController(MyService myService) {this.myService = myService;}@GetMapping("/test")public String test() {return myService.process();} }- 不可变、测试友好、显式依赖。
- 使用Lombok的
@RequiredArgsConstructor进一步简化:@Controller @RequiredArgsConstructor public class MyController {private final MyService myService;@GetMapping("/test")public String test() {return myService.process();} }
-
Setter注入(次选):
@Controller public class MyController {private MyService myService;@Autowiredpublic void setMyService(MyService myService) {this.myService = myService;}@GetMapping("/test")public String test() {return myService.process();} }- 适合可选依赖或循环依赖场景。
-
解决循环依赖:
- 如果字段注入用于解决循环依赖,可改用Setter注入或构造器注入+
@Lazy:@Component public class BeanA {private final BeanB beanB;@Autowiredpublic BeanA(@Lazy BeanB beanB) {this.beanB = beanB;} }
- 如果字段注入用于解决循环依赖,可改用Setter注入或构造器注入+
7. 字段注入的使用场景
尽管不推荐,字段注入在以下场景可能仍被使用:
- 快速原型开发:小型项目或PoC(概念验证),追求开发速度。
- 简单Bean:依赖关系简单、无需测试或修改的场景。
- 遗留代码:早期Spring项目中常见字段注入,维护时可能继续使用。
- 非核心代码:如配置类、工具类,依赖固定且无复杂逻辑。
注意:即使在这些场景中,也应尽量迁移到构造器注入,以提高代码质量和可维护性。
8. 如何避免字段注入的问题
- 强制构造器注入:
- 配置Spring Boot的
spring.main.allow-bean-definition-overriding=false,强制显式依赖。 - 使用静态分析工具(如SonarQube)检测字段注入。
- 配置Spring Boot的
- 单元测试:
- 避免字段注入,确保通过构造器或Setter传入Mock对象。
- 示例(使用Mockito):
@Test public void testController() {MyService mockService = mock(MyService.class);when(mockService.process()).thenReturn("Mocked");MyController controller = new MyController(mockService);assertEquals("Mocked", controller.test()); }
- 代码规范:
- 团队约定优先使用构造器注入,禁用字段注入。
- 使用Lombok或IDE模板减少构造器样板代码。
9. 总结
- 字段注入:
- 通过
@Autowired直接注入字段,代码简洁但隐藏依赖。 - 支持单例Bean的循环依赖(通过三级缓存),与Setter注入类似。
- 通过
- 缺点:
- 测试困难、不可变性缺失、耦合Spring、潜在空指针风险。
- 不推荐在现代Spring项目中使用。
- 推荐:
- 优先使用构造器注入,确保不可变性和测试友好。
- 次选Setter注入,用于可选依赖或循环依赖。
- 字段注入仅限快速原型或遗留代码,尽量迁移到构造器注入。
- 循环依赖:
- 字段注入支持单例Bean循环依赖,但构造器注入需
@Lazy或改用字段/Setter注入。
- 字段注入支持单例Bean循环依赖,但构造器注入需
相关文章:
【Spring】依赖注入的方式:构造方法、setter注入、字段注入
在Spring框架中,除了构造器注入(Constructor Injection)和Setter注入(Setter Injection),还有一种依赖注入方式:字段注入(Field Injection)。字段注入通过在Bean的字段上…...
mybatis实现增删改查1
文章目录 19.MyBatis查询单行数据MapperScan 结果映射配置核心文件Results自定义映射到实体的关系 多行数据查询-完整过程插入数据配置mybatis 控制台日志 更新数据删除数据小结通过id复用结果映射模板xml处理结果映射 19.MyBatis 数据库访问 MyBatis,MyBatis-Plus…...
Git,本地上传项目到github
一、Git的安装和下载 https://git-scm.com/ 进入官网,选择合适的版本下载 二、Github仓库创建 点击右上角New新建一个即可 三、本地项目上传 1、进入 要上传的项目目录,右键,选择Git Bash Here,进入终端Git 2、初始化临时仓库…...
基于flask+vue框架的灯饰安装维修系统u49cf(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
系统程序文件列表 项目功能:用户,工单人员,服务项目,订单记录,服务记录,评价记录 开题报告内容 基于 FlaskVue 框架的灯饰安装维修系统开题报告 一、选题背景与意义 (一)选题背景 随着城市化进程的加速与居民生活品质的显著提升…...
【算法】BFS-解决FloodFill问题
目录 FloodFill问题 图像渲染 岛屿数量 岛屿的最大面积 被围绕的区域 FloodFill问题 FloodFill就是洪水灌溉的意思,假设有下面的一块田地,负数代表是凹地,正数代表是凸地,数字的大小表示凹或者凸的程度。现在下一场大雨&…...
GIS开发笔记(10)基于osgearth实现二三维地图的一键指北功能
一、实现效果 二、实现原理 获取视图及地图操作器,通过地图操作器来重新设置视点,以俯仰角 (0.0)和偏航角 (-90.0)来设置。 osgEarth::Util::Viewpoint(…) 这里创建了一个新的 Viewpoint 对象,表示一个特定的视角。构造函数的参数是: 第一个参数:是视角名称。 后面的 6 个…...
Spring Boot日志系统详解:Logback与SLF4J的默认集成
大家好呀!👋 今天我们来聊聊Spring Boot中一个超级重要但又经常被忽视的功能——日志系统! 一、日志系统的重要性 首先,咱们得明白为什么日志这么重要?🤷♂️ 想象一下,你正在玩一个超级复…...
【C++】Json-Rpc框架项目介绍(1)
项目介绍 RPC(Remote Procedure Call)即远程过程调用,是一种通过网络从远程计算机程序中请求服务而不需要了解底层网络实现细节的一种 协议 。 RPC(Remote Procedure Call)可以使用多种网络协议进行通信,如…...
Docker 部署 PostgreSQL 数据库
Docker 部署 PostgreSQL 数据库 基于 Docker 部署 PostgreSQL 数据库一、拉取 PostgreSQL 镜像二、运行 PostgreSQL 容器三、运行命令参数详解四、查看容器运行状态 基于 Docker 部署 PostgreSQL 数据库 一、拉取 PostgreSQL 镜像 首先,确保你的 Docker 环境已正确…...
用 Go 优雅地清理 HTML 并抵御 XSS——Bluemonday
1、背景与动机 只要你的服务接收并回显用户生成内容(UGC)——论坛帖子、评论、富文本邮件正文、Markdown 等——就必须考虑 XSS(Cross‑Site Scripting)攻击风险。浏览器在解析 HTML 时会执行脚本;如果不做清理&#…...
Python爬虫从入门到实战详细版教程
Python爬虫从入门到实战详细版教程 文章目录 Python爬虫从入门到实战详细版教程书籍大纲与内容概览第一部分:爬虫基础与核心技术1. 第1章:[爬虫概述](https://blog.csdn.net/qq_37360300/article/details/147431708?spm=1001.2014.3001.5501)2. 第2章:HTTP协议与Requests库…...
window上 elasticsearch v9.0 与 jmeter5.6.3版本 冲突,造成es 启动失败
[2025-04-22T11:00:22,508][ERROR][o.e.b.Elasticsearch ] [AIRUY] fatal exception while booting Elasticsearchjava.nio.file.NoSuchFileException: D:\Program Files\apache-jmeter-5.6.3\lib\logkit-2.0.jar 解决方案: 降低 es安装版本 ,选择…...
【C++初阶】第15课—模版进阶
文章目录 1. 模版参数2. 模版的特化2.1 概念2.2 函数模版特化2.3 类模板特化2.3.1 全特化2.3.2 偏特化 3. 模版的分离和编译4. 总结 1. 模版参数 模版参数分为类型形参和非类型参数之前我们写过的大量代码,都是用模版定义类的参数类型,跟在class和typena…...
黑阈免激活版:智能管理后台,优化手机性能
在使用安卓手机的过程中,许多用户会遇到手机卡顿、电池续航不足等问题。这些问题通常是由于后台运行的应用程序过多,占用大量系统资源导致的。今天,我们要介绍的 黑阈免激活版,就是这样一款由南京简域网络科技工作室开发的手机辅助…...
C++17 新特性简解
C17 新特性简解 一、核心语言特性 1. 结构化绑定(Structured Bindings) 用途:解构复合类型(如元组、结构体)为独立变量 示例: #include <iostream> #include <tuple>int main() {// 解构 st…...
神经网络的 “成长密码”:正向传播与反向传播深度解析(四)
引言 在神经网络的神秘世界里,正向传播和反向传播是驱动模型学习和进化的核心机制。它们如同神经网络的 “左右脑”,正向传播负责信息的前向流动与初步处理,反向传播则通过优化权重参数来提升模型性能,二者相辅相成,共…...
Mujoco robosuite 机器人模型
import ctypes import os# 获取当前脚本所在的目录 script_dir os.path.dirname(os.path.abspath(__file__))# 构建库文件的相对路径 lib_relative_path os.path.join(dynamic_models, UR5e, Jb.so)# 拼接成完整的路径 lib_path os.path.join(script_dir, lib_relative_path…...
在Ubuntu 18.04下编译OpenJDK 11
在Ubuntu 18.04下编译OpenJDK 11 源码下载地址: 链接: https://pan.baidu.com/s/1QAdu-B6n9KqeBakGlpBS3Q 密码: 8lho Linux下的环境要求 不同版本的jdk会要求在不同版本的Ubuntu下编译,不要用太高版本的Ubuntu或者gcc,特别是gcc…...
K8s:概念、特点、核心组件与简单应用
一、引言 在当今云计算和容器技术蓬勃发展的时代,Kubernetes(简称 K8s)已成为容器编排领域的事实标准。它为管理容器化应用提供了高效、可靠的解决方案,极大地简化了应用的部署、扩展和运维过程。无论是小型初创公司还是大型企业…...
STM32的定时器输出PWM时,死区时间(DTR)如何计算
在 STM32F429(以及所有 STM32F4 “高级定时器”)中,死区时间由 TIMx_BDTR 寄存器的 8 位 “Dead‑Time Generator” 字段 DTG[7:0] 来配置。其计算分三步: 计算死区时钟周期 tDTS TIM1 时钟源为 APB2 定时器时钟(PCL…...
STC32G12K128单片机GPIO模式SPI操作NorFlash并实现FatFS文件系统
STC32G12K128单片机GPIO模式SPI操作NorFlash并实现FatFS文件系统 NorFlash简介NorFlash操作驱动代码文件系统测试代码 NorFlash简介 NOR Flash是一种类型的非易失性存储器,它允许在不移除电源的情况下保留数据。NOR Flash的名字来源于其内部结构中使用的NOR逻辑门。…...
ClickHouse 设计与细节
1. 引言 ClickHouse 是一款备受欢迎的开源列式在线分析处理 (OLAP) 数据库管理系统,专为在海量数据集上实现高性能实时分析而设计,并具备极高的数据摄取速率 1。其在各种行业中得到了广泛应用,包括众多知名企业,例如超过半数的财…...
MySQL基础安装和学习
MySQL 是一种开源的关系型数据库管理系统(RDBMS),由瑞典公司 MySQL AB 开发,后被 Oracle 公司收购。它是一种基于客户端/服务器架构的数据库系统,广泛应用于 Web 应用开发和企业级数据管理。 MySQL 使用 SQL(Structured Query Language,结构化查询语言)作为与数据库交…...
智能体MCP 实现数据可视化分析
参考: 在线体验 https://www.doubao.com/chat/ 下载安装离线体验 WPS软件上的表格分析 云上创建 阿里mcp:https://developer.aliyun.com/article/1661198 (搜索加可视化) 案例 用cline 或者cherry studio实现 mcp server:excel-mcp-server、quickchart-mcp-server...
再看开源多模态RAG的视觉文档(OCR-Free)检索增强生成方案-VDocRAG
前期几个工作提到,基于OCR的文档解析RAG的方式进行知识库问答,受限文档结构复杂多样,各个环节的解析泛化能力较差,无法完美的对文档进行解析。因此出现了一些基于多模态大模型的RAG方案。如下: 【RAG&多模态】多模…...
生产环境大数据平台权限管理
引言:数据资产保护的生死线 在金融行业某头部企业发生的数据泄露事件中,由于权限管理漏洞导致千万级用户信息外泄,直接经济损失超过2.3亿元。这个案例揭示了生产环境大数据平台权限管理的重要性和复杂性。本文将深入探讨从权限模型设计到实施…...
深入浅出 NVIDIA CUDA 架构与并行计算技术
🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、CUDA为何重要:并行计算的时代 2、NVIDIA在…...
FPGA系列之DDS信号发生器设计(DE2-115开发板)
一、IP核 IP(Intellectual Property)原指知识产权、著作权等,在IC设计领域通常被理解为实现某种功能的设计。IP模块则是完成某种比较复杂算法或功能(如FIR滤波器、FFT、SDRAM控制器、PCIe接口、CPU核等)并且参数可修改的电路模块,…...
Rust: 从内存地址信息看内存布局
内存布局其实有几个:address(地址)、size(大小)、alignment(对齐位数,2 的自然数次幂,2,4,8…)。 今天主要从address来看内存的布局。 说明&…...
【Dv3Admin】从零搭建Git项目安装·配置·初始化
项目采用 Django 与 Vue3 技术栈构建,具备强大的后端扩展能力与现代前端交互体验。完整实现了权限管理、任务队列、WebSocket 通信、系统配置等功能,适用于构建中后台管理系统与多租户平台。 本文章内容涵盖环境搭建、虚拟环境配置、前后端部署、项目结…...
