【Spring】依赖注入的方式:构造方法、setter注入、字段注入

news2025/4/24 4:50:08

在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 {
        @Autowired
        private MyService myService; // 字段注入
    
        @GetMapping("/test")
        public String test() {
            return myService.process();
        }
    }
    
    • Spring会通过反射将MyService的单例实例注入到MyControllermyService字段。
  • 配置方式

    • 仅需在字段上添加@Autowired(或@Resource@Inject)。
    • 不需要XML或Java配置显式指定字段注入,Spring自动处理。
    • 如果字段是可选依赖,可设置@Autowired(required = false)
      @Autowired(required = false)
      private MyService myService;
      

2. 字段注入与循环依赖

  • 单例Bean中的循环依赖

    • 字段注入的注入时机与Setter注入类似,发生在Bean实例化后、初始化前。
    • Spring通过三级缓存singletonObjectsearlySingletonObjectssingletonFactories)解决单例Bean的循环依赖。
    • 字段注入支持循环依赖的解决,行为与Setter注入一致。例如:
      @Component
      public class BeanA {
          @Autowired
          private BeanB beanB;
      }
      
      @Component
      public class BeanB {
          @Autowired
          private BeanA beanA;
      }
      
      • 解决流程
        1. 创建BeanA,实例化后放入三级缓存(ObjectFactory)。
        2. BeanA注入beanB,触发BeanB创建,BeanB放入三级缓存。
        3. BeanB需要BeanA,从三级缓存获取BeanA的早期引用,注入到beanB字段。
        4. BeanB完成,放入一级缓存;BeanA继续注入beanB,完成并放入一级缓存。
      • 结果:循环依赖通过三级缓存成功解决,BeanABeanB相互引用。
  • 非单例Bean(如prototype

    • 字段注入无法解决原型作用域的循环依赖,因为Spring不缓存原型Bean。
    • 会抛出BeanCurrentlyInCreationException,需使用@LazyObjectProvider解决。
  • 构造器注入对比

    • 字段注入与Setter注入类似,支持循环依赖的自动解决。
    • 构造器注入由于依赖在实例化时注入,无法利用三级缓存解决循环依赖,需@Lazy或改用字段/Setter注入。

3. 字段注入的优缺点

优点
  1. 代码简洁
    • 无需编写构造器或Setter方法,减少样板代码。
    • 适合快速开发或小型项目。
  2. 直观
    • 依赖直接在字段上声明,易于查看Bean的依赖关系。
  3. 支持循环依赖
    • 与Setter注入类似,字段注入天然支持单例Bean的循环依赖解决。
  4. 灵活性
    • 支持可选依赖(@Autowired(required = false)),字段可以为空。
缺点
  1. 隐藏依赖关系
    • 依赖未通过构造器或Setter显式声明,难以通过代码接口了解Bean的完整依赖。
    • 违反“显式优于隐式”的原则。
  2. 测试困难
    • 字段注入依赖Spring的反射机制,单元测试无法通过构造器或Setter传入Mock对象。
    • 需使用反射工具(如ReflectionTestUtils)或PowerMock修改私有字段,增加测试复杂性。
  3. 不可变性缺失
    • 字段注入的依赖无法使用final修饰,可能被运行时修改(例如通过反射或手动赋值),影响线程安全。
  4. 耦合Spring框架
    • 字段注入依赖@Autowired等Spring注解,Bean与Spring容器强耦合,难以脱离Spring使用。
  5. 潜在空指针风险
    • 如果忘记配置依赖或Spring未正确注入,可能导致运行时NullPointerException(尤其是required = false时)。
  6. 不推荐在现代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. 字段注入的替代方案

由于字段注入的缺点,推荐以下替代方案:

  1. 构造器注入(首选)

    @Controller
    public class MyController {
        private final MyService myService;
    
        @Autowired
        public 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();
          }
      }
      
  2. Setter注入(次选)

    @Controller
    public class MyController {
        private MyService myService;
    
        @Autowired
        public void setMyService(MyService myService) {
            this.myService = myService;
        }
    
        @GetMapping("/test")
        public String test() {
            return myService.process();
        }
    }
    
    • 适合可选依赖或循环依赖场景。
  3. 解决循环依赖

    • 如果字段注入用于解决循环依赖,可改用Setter注入或构造器注入+@Lazy
      @Component
      public class BeanA {
          private final BeanB beanB;
      
          @Autowired
          public BeanA(@Lazy BeanB beanB) {
              this.beanB = beanB;
          }
      }
      

7. 字段注入的使用场景

尽管不推荐,字段注入在以下场景可能仍被使用:

  • 快速原型开发:小型项目或PoC(概念验证),追求开发速度。
  • 简单Bean:依赖关系简单、无需测试或修改的场景。
  • 遗留代码:早期Spring项目中常见字段注入,维护时可能继续使用。
  • 非核心代码:如配置类、工具类,依赖固定且无复杂逻辑。

注意:即使在这些场景中,也应尽量迁移到构造器注入,以提高代码质量和可维护性。


8. 如何避免字段注入的问题

  1. 强制构造器注入
    • 配置Spring Boot的spring.main.allow-bean-definition-overriding=false,强制显式依赖。
    • 使用静态分析工具(如SonarQube)检测字段注入。
  2. 单元测试
    • 避免字段注入,确保通过构造器或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());
      }
      
  3. 代码规范
    • 团队约定优先使用构造器注入,禁用字段注入。
    • 使用Lombok或IDE模板减少构造器样板代码。

9. 总结

  • 字段注入
    • 通过@Autowired直接注入字段,代码简洁但隐藏依赖。
    • 支持单例Bean的循环依赖(通过三级缓存),与Setter注入类似。
  • 缺点
    • 测试困难、不可变性缺失、耦合Spring、潜在空指针风险。
    • 不推荐在现代Spring项目中使用。
  • 推荐
    • 优先使用构造器注入,确保不可变性和测试友好。
    • 次选Setter注入,用于可选依赖或循环依赖。
    • 字段注入仅限快速原型或遗留代码,尽量迁移到构造器注入。
  • 循环依赖
    • 字段注入支持单例Bean循环依赖,但构造器注入需@Lazy或改用字段/Setter注入。

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

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

相关文章

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 框架的灯饰安装维修系统开题报告 一、选题背景与意义 (一)选题背景 随着城市化进程的加速与居民生活品质的显著提升&#xf…

【算法】BFS-解决FloodFill问题

目录 FloodFill问题 图像渲染 岛屿数量 岛屿的最大面积 被围绕的区域 FloodFill问题 FloodFill就是洪水灌溉的意思,假设有下面的一块田地,负数代表是凹地,正数代表是凸地,数字的大小表示凹或者凸的程度。现在下一场大雨&…

GIS开发笔记(10)基于osgearth实现二三维地图的一键指北功能

一、实现效果 二、实现原理 获取视图及地图操作器,通过地图操作器来重新设置视点,以俯仰角 (0.0)和偏航角 (-90.0)来设置。 osgEarth::Util::Viewpoint(…) 这里创建了一个新的 Viewpoint 对象,表示一个特定的视角。构造函数的参数是: 第一个参数:是视角名称。 后面的 6 个…

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…

黑阈免激活版:智能管理后台,优化手机性能

在使用安卓手机的过程中,许多用户会遇到手机卡顿、电池续航不足等问题。这些问题通常是由于后台运行的应用程序过多,占用大量系统资源导致的。今天,我们要介绍的 黑阈免激活版,就是这样一款由南京简域网络科技工作室开发的手机辅助…

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…

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。其在各种行业中得到了广泛应用,包括众多知名企业,例如超过半数的财…

智能体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&多模态】多模…

深入浅出 NVIDIA CUDA 架构与并行计算技术

🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、CUDA为何重要:并行计算的时代 2、NVIDIA在…

FPGA系列之DDS信号发生器设计(DE2-115开发板)

一、IP核 IP(Intellectual Property)原指知识产权、著作权等,在IC设计领域通常被理解为实现某种功能的设计。IP模块则是完成某种比较复杂算法或功能(如FIR滤波器、FFT、SDRAM控制器、PCIe接口、CPU核等)并且参数可修改的电路模块&#xff0c…

【Dv3Admin】从零搭建Git项目安装·配置·初始化

项目采用 Django 与 Vue3 技术栈构建,具备强大的后端扩展能力与现代前端交互体验。完整实现了权限管理、任务队列、WebSocket 通信、系统配置等功能,适用于构建中后台管理系统与多租户平台。 本文章内容涵盖环境搭建、虚拟环境配置、前后端部署、项目结…

P3416-图论-法1.BFS / 法2.Floyd

这道题虽然标签有floyd但是直接bfs也能过 其实事实证明还是bfs快,因为bfs只需要遍历特定的点,但是floyd需要考虑遍历所有可能的中介点 法1.BFS 用字典存储每个点所能普及的范围,然后用对每个点bfs进行拓展 nint(input())temp[]#xmax0;yma…

极狐GitLab 议题和史诗创建的速率限制如何设置?

极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 议题和史诗创建的速率限制 (BASIC SELF) 速率限制是为了控制新史诗和议题的创建速度。例如,如果您将限制设置为 …