【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的单例实例注入到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 {@Autowiredprivate BeanB beanB;
      }@Component
      public class BeanB {@Autowiredprivate 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;@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();}
      }
      
  2. Setter注入(次选)

    @Controller
    public class MyController {private MyService myService;@Autowiredpublic 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;@Autowiredpublic 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.mzph.cn/pingmian/77854.shtml

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

相关文章

【数学建模】随机森林算法详解:原理、优缺点及应用

随机森林算法详解:原理、优缺点及应用 文章目录 随机森林算法详解:原理、优缺点及应用引言随机森林的基本原理随机森林算法步骤随机森林的优点随机森林的缺点随机森林的应用场景Python实现示例超参数调优结论参考文献 引言 随机森林是机器学习领域中一种…

HttpSessionListener 的用法笔记250417

HttpSessionListener 的用法笔记250417 以下是关于 HttpSessionListener 的用法详解,涵盖核心方法、实现步骤、典型应用场景及注意事项,帮助您全面掌握会话(Session)生命周期的监听与管理: 1. 核心功能 HttpSessionLi…

【Python爬虫基础篇】--2.模块解析

目录 1.urllib库 1.1.request模块 1.1.1、urllib.request.urlopen() 函数 1.1.2.urllib.request.urlretrieve() 函数 1.2. error模块 1.3. parse 模块 2. BeautifulSoup4库 2.1.对象种类 2.2.对象属性 2.2.1.子节点 2.2.2.父节点 2.2.3.兄弟节点 2.2.4.回退和前进 …

Ubuntu-Linux从桌面到显示的全流程:技术分析总结

引言 Ubuntu作为主流的Linux发行版,其显示系统经历了从传统X11到现代Wayland的演进。本文将详细分析从应用程序到屏幕显示的完整技术流程,包括桌面环境、显示服务器、图形栈和硬件交互等核心环节。 1. 系统架构概览 Ubuntu的显示系统架构可分为四个主要…

在PyCharm中部署AI模型的完整指南

引言 随着人工智能技术的快速发展,越来越多的开发者开始将AI模型集成到他们的应用程序中。PyCharm作为一款强大的Python IDE,为AI开发提供了出色的支持。本文将详细介绍如何在PyCharm中部署AI模型,从环境配置到最终部署的完整流程。 第一部分:准备工作 1. 安装PyCharm …

WHAT - 静态资源缓存穿透

文章目录 1. 动态哈希命名的基本思路2. 具体实现2.1 Vite/Webpack 配置动态哈希2.2 HTML 文件中动态引用手动引用使用 index.html 模板动态插入 2.3 结合 Cache-Control 避免缓存穿透2.4 适用于多环境的动态策略 总结 在多环境部署中,静态资源缓存穿透是一个常见问题…

PoCL环境搭建

PoCL环境搭建 **一.关键功能与优势****二.设计目的****三.测试步骤**1.创建容器2.安装依赖3.编译安装pocl4.运行OpenCL测试程序 Portable Computing Language (PoCL) 简介 Portable Computing Language (PoCL) 是一个开源的、符合标准的异构计算框架,旨在为 OpenCL…

【区块链技术解析】从原理到实践的全链路指南

目录 前言:技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现(10个案例)案例1:创建简单区块链案例2:工作…

在Windows上安装Git

一、安装 Git 下载 Git地址:Git - Downloads (git-scm.com) 1、在页面中找到适用于 Windows 系统的最新版本安装包(通常为.exe 格式文件),点击下载链接。 出于访问Git官网需要科学上网,不会的可以私信我要软件包&…

Golang interface总结(其一)

本篇是对golang 中的interface做一些浅层的、实用的总结 多态 常用场景 interface内仅包含函数类型,然后定义结构体去实现,如下 package mainimport "fmt"type Animal interface {Sound()Act() }type Cat struct{}func (c *Cat) Sound() {…

TVM计算图分割--Collage

1 背景 为满足高效部署的需要,整合大量优化的tensor代数库和运行时做为后端成为必要之举。现在的深度学习后端可以分为两类:1)算子库(operator kernel libraries),为每个DL算子单独提供高效地低阶kernel实现。这些库一般也支持算…

Redis——内存策略

目录 前言 1.过期策略 1.1过期策略——DB结构 1.2过期策略——惰性删除 1.3过期策略——定期删除 2.淘汰策略 2.1最少最近使用和使用频率原理 2.2内存淘汰策略执行流程 总结: 前言 Redis之所以性能强,主要的原因就是基于内存存储。然而单节点的R…

原型模式详解及在自动驾驶场景代码示例(c++代码实现)

模式定义 原型模式(Prototype Pattern)是一种创建型设计模式,通过克隆已有对象来创建新对象,避免重复执行昂贵的初始化操作。该模式特别适用于需要高效创建相似对象的场景,是自动驾驶感知系统中处理大量重复数据结构的…

在kali中安装AntSword(蚁剑)

步骤一、下载压缩包 源码:https://github.com/AntSwordProject/antSword,下载压缩包。 加载器:https://github.com/AntSwordProject/AntSword-Loader,根据系统选择压缩包(kali选择AntSword-Loader-v4.0.3-linux-x64&…

华为仓颉编程语言基础概述

第一章:技术演进与诞生背景 1.1 万物智联时代的编程挑战 在5G、物联网、边缘计算等技术推动下,全球智能设备数量呈指数级增长。据IDC预测,2025年全球IoT设备将突破550亿台,这对系统级编程语言提出新要求: 异构硬件兼…

【Linux篇】探索进程间通信:如何使用匿名管道构建高效的进程池

从零开始:通过匿名管道实现进程池的基本原理 一. 进程间通信1.1 基本概念1.2 通信目的1.3 通信种类1.3.1 同步通信1.3.2 异步通信 1.4 如何通信 二. 管道2.1 什么是管道2.2 匿名管道2.2.1 pipe()2.2.2 示例代码:使用 pipe() 进行父子进程通信2.2.3 管道容…

【LeetCode】嚼烂热题100【持续更新】

2、字母异位词分组 方法一&#xff1a;排序哈希表 思路&#xff1a;对每个字符串排序&#xff0c;排序后的字符串作为键插入到哈希表中&#xff0c;值为List<String>形式存储单词原型&#xff0c;键为排序后的字符串。 Map<String, List<String>> m new Ha…

2025年最新版 Git和Github的绑定方法,以及通过Git提交文件至Github的具体流程(详细版)

文章目录 Git和Github的绑定方法与如何上传至代码仓库一. 注册 GitHub 账号二.如何创建自己的代码仓库&#xff1a;1.登入Github账号&#xff0c;完成登入后会进入如下界面&#xff1a;2.点击下图中红色框选的按钮中的下拉列表3.选择New repostitory4.进入创建界面后&#xff0…

FPGA开发板这样做?(一)-像 Arduino 一样玩 FPGA

这也是一个系列文章&#xff0c;来源之前和粉丝们在评论区讨论的国外对于FPGA的开发或者入门所做的努力。 基本一篇文章会介绍一个FPGA开发板&#xff0c;重点在于为开发板准备的开发方式&#xff08;和国内大不相同&#xff09;。 今天的主角-PulseRain M10&#xff1a;像 Ard…

【C++游戏引擎开发】第21篇:基于物理渲染(PBR)——统计学解构材质与光影

引言 宏观现象:人眼观察到的材质表面特性(如金属的高光锐利、石膏的漫反射柔和),本质上是微观结构对光线的统计平均结果。 微观真相:任何看似平整的表面在放大后都呈现崎岖的微观几何。每个微表面(Microfacet)均为完美镜面,但大量微表面以不同朝向分布时,宏观上会表…