Spring 构造器注入和setter注入的比较
一、比较说明
在 Spring 框架中,构造器注入(Constructor Injection)和 Setter 注入(Setter Injection)是实现依赖注入(DI)的两种主要方式。它们的核心区别在于依赖注入的时机、代码设计理念以及适用场景。以下是两者的详细比较:
1. 核心区别
| 特性 | 构造器注入 | Setter 注入 |
|---|---|---|
| 注入方式 | 通过类的构造方法注入依赖。 | 通过 Setter 方法注入依赖。 |
| 依赖不可变性 | 依赖通常声明为 final,确保对象创建后不可变。 | 依赖可变,可在对象生命周期中修改。 |
| 依赖必要性 | 强制要求依赖,适用于必需依赖。 | 可选依赖,允许部分依赖为 null。 |
| 初始化完整性 | 对象创建时即完成依赖注入,保证完全初始化。 | 对象可能处于“部分初始化”状态(依赖未完全注入)。 |
| 循环依赖处理 | 无法解决构造器级别的循环依赖(Spring 会抛出异常)。 | 可通过延迟注入解决循环依赖。 |
2. 优缺点对比
构造器注入
-
优点:
-
不可变性:依赖字段可声明为
final,确保线程安全和对象状态一致性。 -
明确性:强制要求所有必需依赖,避免
NullPointerException。 -
代码简洁性:结合 Lombok 的
@RequiredArgsConstructor,可自动生成构造方法。 -
兼容测试:易于在单元测试中手动注入依赖。
-
-
缺点:
-
灵活性不足:对可选依赖支持较弱,需通过重载构造方法实现。
-
循环依赖限制:无法处理构造器级别的循环依赖。
-
Setter 注入
-
优点:
-
灵活性高:支持可选依赖,允许动态重新配置依赖。
-
解决循环依赖:Spring 容器可处理 Setter 注入的循环依赖。
-
向后兼容:适合逐步迁移旧代码到依赖注入模式。
-
-
缺点:
-
状态不稳定:对象可能在未完全初始化时被使用(如缺少必需依赖)。
-
线程安全风险:依赖可变性可能导致多线程环境下的问题。
-
3. 适用场景
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 必需依赖 | 构造器注入 | 强制依赖不可为空,确保对象完全初始化。 |
| 可选依赖 | Setter 注入 | 允许依赖为 null,或通过默认值处理。 |
| 不可变对象 | 构造器注入 | 结合 final 字段,保证线程安全。 |
| 循环依赖 | Setter 注入 | Spring 容器支持通过 Setter 解决循环依赖。 |
| 遗留代码迁移 | Setter 注入 | 逐步引入依赖注入,无需修改现有构造方法。 |
4. 代码示例
构造器注入
java
复制
@Component
public class OrderService {private final PaymentService paymentService; // final 字段@Autowired // Spring 4.3+ 可省略单构造器的 @Autowiredpublic OrderService(PaymentService paymentService) {this.paymentService = paymentService;}
}
Setter 注入
java
复制
@Component
public class UserService {private EmailService emailService;@Autowiredpublic void setEmailService(EmailService emailService) {this.emailService = emailService;}
}
5. 现代实践建议
-
优先使用构造器注入:Spring 官方推荐构造器注入作为默认选择,尤其在 Spring 4.x+ 中,因其对不可变性和安全性的支持。
-
结合 Lombok 简化代码:使用
@RequiredArgsConstructor自动生成构造方法,减少样板代码。 -
谨慎处理循环依赖:循环依赖通常是设计问题的信号,应优先通过重构解决,而非依赖 Setter 注入。
总结
构造器注入和 Setter 注入各有优劣,选择取决于具体需求:
-
构造器注入:适用于强制依赖、不可变对象和高安全性场景。
-
Setter 注入:适用于可选依赖、动态配置或遗留代码迁移。
在实际项目中,可以混合使用两者,但应以构造器注入为主,遵循“依赖明确、状态可靠”的设计原则。
二、最佳实践
在 Spring 的构造器注入中,将依赖字段声明为 final 可以增强线程安全和对象状态一致性,这主要归功于 Java 内存模型(JMM)对 final 字段的特殊处理和 不可变性(Immutability) 的设计原则。以下是具体原因:
1. final 字段的内存可见性保证
根据 Java 内存模型(JSR-133)的规范:
-
初始化安全性:当一个对象被正确构造(即构造方法没有发生
this引用逸出)时,所有线程在访问该对象的final字段时,无需同步即可看到构造方法中初始化的值。 -
禁止指令重排序:JVM 会对
final字段的写操作插入内存屏障,确保构造方法中对final字段的赋值操作不会被重排序到对象引用发布之后。
示例
java
复制
public class OrderService {private final PaymentService paymentService; // final 字段public OrderService(PaymentService paymentService) {this.paymentService = paymentService; // 初始化 final 字段}
}
-
当一个线程创建
OrderService对象后,其他线程在访问paymentService时,一定能看到构造方法中初始化的值,不会出现未初始化或部分初始化的状态。
2. 不可变性(Immutability)
-
字段不可变:
final字段一旦被赋值,其引用不能再被修改(即不能通过setter或其他方法重新赋值)。 -
状态一致性:对象的状态(依赖的组件)在构造完成后即固定,不会因后续代码的意外修改而破坏一致性。
对比 Setter 注入
java
复制
public class UserService {private EmailService emailService; // 非 final 字段public void setEmailService(EmailService emailService) {this.emailService = emailService; // 可能被多次调用或并发修改}
}
-
线程安全问题:如果多线程同时调用
setEmailService,可能导致竞态条件(Race Condition),最终emailService的值可能不一致。 -
状态不一致:对象可能在某个时刻处于“部分初始化”状态(例如,依赖未完全注入)。
3. 避免 this 引用逸出
-
构造器注入的天然优势:在构造方法中完成依赖注入,可以避免在对象未完全初始化前暴露
this引用。 -
final字段的强制约束:必须在构造方法中完成final字段的初始化,否则代码无法编译。这强制开发者保证依赖的完整性。
反例(Setter 注入中的风险)
java
复制
public class UserService {private EmailService emailService;public UserService() {// 构造方法中可能提前暴露 this 引用(错误实践)SomeRegistry.register(this); // 此时 emailService 尚未初始化!}public void setEmailService(EmailService emailService) {this.emailService = emailService;}
}
-
如果其他线程通过
SomeRegistry获取到未完全初始化的UserService实例,可能导致NullPointerException。
4. 实际场景中的线程安全
-
无状态服务:Spring 中的 Bean 默认是单例的,如果 Bean 是无状态的(例如仅依赖其他组件),结合
final字段的不可变性,天然支持多线程并发访问。 -
无需额外同步:由于依赖不可变,无需使用
synchronized或volatile等同步机制。
对比 Setter 注入的线程安全成本
java
复制
public class UserService {private volatile EmailService emailService; // 需要 volatile 保证可见性public synchronized void setEmailService(EmailService emailService) {this.emailService = emailService; // 需要同步锁保证原子性}
}
-
为了线程安全,Setter 注入可能需要额外的同步机制,增加了代码复杂性和性能开销。
总结
通过构造器注入将依赖字段声明为 final,可以从以下层面保证线程安全和状态一致性:
-
内存可见性:JMM 确保
final字段的初始化值对所有线程立即可见。 -
不可变性:依赖引用不可修改,消除竞态条件。
-
初始化完整性:强制依赖在对象创建时完成注入,避免部分初始化状态。
因此,构造器注入 + final 字段 是 Spring 中实现线程安全依赖注入的最佳实践。
相关文章:
Spring 构造器注入和setter注入的比较
一、比较说明 在 Spring 框架中,构造器注入(Constructor Injection)和 Setter 注入(Setter Injection)是实现依赖注入(DI)的两种主要方式。它们的核心区别在于依赖注入的时机、代码设计理念以及…...
uploadlabs通关思路
目录 靶场准备 复现 pass-01 代码审计 执行逻辑 文件上传 方法一:直接修改或删除js脚本 方法二:修改文件后缀 pass-02 代码审计 文件上传 1. 思路 2. 实操 pass-03 代码审计 过程: 文件上传 pass-04 代码审计 文件上传 p…...
迷你世界脚本自定义UI接口:Customui
自定义UI接口:Customui 彼得兔 更新时间: 2024-11-07 15:12:42 具体函数名及描述如下:(除前两个,其余的目前只能在UI编辑器内部的脚本使用) 序号 函数名 函数描述 1 openUIView(...) 打开一个UI界面(注意…...
【情境领导者】评估情境——准备度水平
本系列是看了《情境领导者》一书,结合自己工作的实践经验所做的学习笔记。 在文章【情境领导者】评估情境——什么是准备度-CSDN博客我们提到准备度是由能力和意愿两部分组成的。 准备度水平 而我们要怎么去评估准备度呢?准备度水平是指人们在每项工作中…...
2025 ubuntu24.04系统安装docker
1.查看ubuntu版本(Ubuntu 24.04 LTS) rootmaster:~# cat /etc/os-release PRETTY_NAME"Ubuntu 24.04 LTS" NAME"Ubuntu" VERSION_ID"24.04" VERSION"24.04 LTS (Noble Numbat)" VERSION_CODENAMEnoble IDubun…...
Android中AIDL和HIDL的区别
在Android中,AIDL(Android Interface Definition Language) 和 HIDL(HAL Interface Definition Language) 是两种用于定义跨进程通信接口的语言。AIDL 是 Android 系统最早支持的 IPC(进程间通信࿰…...
通过数据库网格架构构建现代分布式数据系统
在当今微服务驱动的世界中,企业在跨分布式系统管理数据方面面临着越来越多的挑战。数据库网格架构已成为应对这些挑战的强大解决方案,它提供了一种与现代应用架构相匹配的分散式数据管理方法。本文将探讨数据库网格架构的工作原理,以及如何使…...
Python基于Django的医用耗材网上申领系统【附源码、文档说明】
博主介绍:✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&…...
Python爬虫实战:一键采集电商数据,掌握市场动态!
电商数据分析是个香饽饽,可市面上的数据采集工具要不贵得吓人,要不就是各种广告弹窗。干脆自己动手写个爬虫,想抓啥抓啥,还能学点技术。今天咱聊聊怎么用Python写个简单的电商数据爬虫。 打好基础:搞定请求头 别看爬虫…...
STM32之I2C硬件外设
注意:硬件I2C的引脚是固定的 SDA和SCL都是复用到外部引脚。 SDA发送时数据寄存器的数据在数据移位寄存器空闲的状态下进入数据移位寄存器,此时会置状态寄存器的TXE为1,表示发送寄存器为空,然后往数据控制寄存器中一位一位的移送数…...
【C++】中的赋值初始化和直接初始化的区别
在C中,赋值初始化(也称为拷贝初始化)和直接初始化(也称为构造初始化)虽然常常产生相同的结果,但在某些情况下它们有不同的含义和行为。 赋值初始化(Copy Initialization) 使用等号…...
Python ❀ Unix时间戳转日期或日期转时间戳工具分享
设计一款Unix时间戳和日期转换工具,其代码如下: from datetime import datetimeclass Change_Date_Time(object):def __init__(self, date_strNone, date_numNone):self.date_str date_strself.date_num date_num# 转时间戳def datetime2timestamp(s…...
本地部署Dify及避坑指南
Dify作为开源的大模型应用开发平台,支持本地私有化部署,既能保障数据安全,又能实现灵活定制。但对于新手而言,从环境配置到服务启动可能面临诸多挑战。本文结合实战经验,手把手教你从零部署Dify,并总结高频…...
大白话CSS 优先级计算规则的详细推导与示例
大白话CSS 优先级计算规则的详细推导与示例 答题思路 引入概念:先通俗地解释什么是 CSS 优先级,让读者明白为什么要有优先级规则,即当多个 CSS 样式规则作用于同一个元素时,需要确定哪个规则起作用。介绍优先级的分类࿱…...
OpenCV计算摄影学(19)非真实感渲染(Non-Photorealistic Rendering, NPR)
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 非真实感渲染(Non-Photorealistic Rendering, NPR)是一种计算机图形学技术,旨在生成具有艺术风格或其他非现实…...
深度学习(斋藤)学习笔记(五)-反向传播2
上一篇关于反向传播的代码仅支持单变量的梯度计算,下面我们将扩展代码使其支持多个输入/输出。增加了对多输入函数(如 Add),以实现的计算。 1.关于前向传播可变长参数的改进-修改Function类 修改方法: Function用于对…...
数据库基础练习1
目录 1.创建数据库和表 2.插入数据 创建一个数据库,在数据库种创建一张叫heros的表,在表中插入几个四大名著的角色: 1.创建数据库和表 #创建表 CREATE DATABASE db_test;#查看创建的数据库 show databases; #使用db_test数据库 USE db_te…...
TypeError: Cannot create property ‘xxx‘ on string ‘xxx‘
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
极狐GitLab 17.9 正式发布,40+ DevSecOps 重点功能解读【三】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
lsblk命令linux查询设备信息
lsblk命令是Linux中用于列出所有可用块设备信息的工具,它能够显示设备之间的依赖关系,但不会列出RAM盘的信息。块设备包括硬盘、闪存盘、CD-ROM等。lsblk命令包含在util-linux包中,该命令的常用参数包括: -d:仅列出磁盘…...
LyricsX:macOS平台的多源歌词同步与显示技术方案
LyricsX:macOS平台的多源歌词同步与显示技术方案 【免费下载链接】LyricsX 🎶 Ultimate lyrics app for macOS. 项目地址: https://gitcode.com/gh_mirrors/ly/LyricsX LyricsX是一款专为macOS设计的开源歌词应用,通过集成多个歌词源和…...
三步突破抖音音乐批量下载难题:douyin-downloader全功能技术指南
三步突破抖音音乐批量下载难题:douyin-downloader全功能技术指南 【免费下载链接】douyin-downloader 项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader 在数字内容创作领域,背景音乐是提升作品感染力的关键元素。然而&…...
微信支付商家券:从创建到核销的全链路开发实战
1. 微信支付商家券的核心价值与应用场景 商家券是微信支付为商户提供的数字化营销工具,本质上是一种电子优惠凭证。与传统的纸质优惠券相比,商家券最大的优势在于能够实现全链路数字化管理。我在帮一家连锁咖啡品牌接入商家券时发现,他们的线…...
跨境电商多语种支持:SenseVoice-Small ONNX语音识别模型部署与本地化适配
跨境电商多语种支持:SenseVoice-Small ONNX语音识别模型部署与本地化适配 1. 环境准备与快速部署 SenseVoice-Small ONNX模型是一个经过量化处理的高效语音识别解决方案,特别适合跨境电商场景中的多语言语音处理需求。这个模型支持超过50种语言&#x…...
探索Tabler Icons 3.40.0:新增6000+高质量SVG图标的终极指南
探索Tabler Icons 3.40.0:新增6000高质量SVG图标的终极指南 【免费下载链接】tabler-icons A set of over 4800 free MIT-licensed high-quality SVG icons for you to use in your web projects. 项目地址: https://gitcode.com/GitHub_Trending/ta/tabler-icons…...
Qwen-Turbo-BF16惊艳案例:霓虹雨街中不同材质(金属/玻璃/布料)反射率差异还原
Qwen-Turbo-BF16惊艳案例:霓虹雨街中不同材质(金属/玻璃/布料)反射率差异还原 你有没有想过,为什么一张好的夜景图片,尤其是那种霓虹闪烁的雨夜街景,看起来那么真实、那么有“感觉”? 关键往往…...
数据库课程设计案例:基于深度感知的智能仓储管理系统
数据库课程设计案例:基于深度感知的智能仓储管理系统 每次路过大型物流仓库,看到那些高耸的货架和穿梭的叉车,我总会想,他们是怎么知道哪个货位是满的,哪个是空的?靠人工盘点?那得累死。靠传统…...
SpeedyStepper Forked:嵌入式步进电机硬实时控制库解析
1. SpeedyStepper Forked:面向嵌入式实时控制的高性能步进电机驱动库深度解析1.1 库定位与工程价值SpeedyStepper Forked 是一个专为嵌入式平台(尤其是基于Arduino生态的MCU)设计的轻量级、高精度步进电机运动控制库。其核心目标并非提供图形…...
OpenClaw终端整合:QwQ-32B命令行操作增强方案
OpenClaw终端整合:QwQ-32B命令行操作增强方案 1. 为什么需要终端智能助手 作为开发者,我们每天要处理大量命令行操作。从简单的目录跳转、文件操作,到复杂的管道命令组合,再到调试报错信息,这些重复性工作消耗了大量…...
LeagueAkari终极指南:智能游戏辅助工具快速上手与深度配置
LeagueAkari终极指南:智能游戏辅助工具快速上手与深度配置 【免费下载链接】League-Toolkit 兴趣使然的、简单易用的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 你是否曾在…...
