《Spring 依赖注入方式全解析》
一、Spring 依赖注入概述

Spring 依赖注入(Dependency Injection,DI)是一种重要的设计模式,它在 Spring 框架中扮演着关键角色。依赖注入的核心概念是将对象所需的依赖关系由外部容器(通常是 Spring 容器)进行管理和注入,而不是让对象自己去创建和管理依赖。
这种方式具有极大的重要性。首先,它实现了解耦。在传统的编程方式中,对象之间的依赖关系通常是在对象内部通过直接实例化来建立的,这会导致对象之间高度耦合,难以维护和扩展。而依赖注入使得对象只关注自身的核心业务逻辑,不需要关心依赖对象的创建和获取方式,从而降低了对象之间的耦合度。
例如,在一个企业级应用中,一个业务服务类可能依赖于数据访问层的某个 DAO(Data Access Object)。如果采用传统方式,业务服务类需要自己实例化 DAO 对象,这样一旦 DAO 的实现发生变化,业务服务类也需要相应修改。但通过依赖注入,业务服务类只需要声明对 DAO 的依赖,由 Spring 容器在运行时将合适的 DAO 实例注入到业务服务类中,大大提高了代码的可维护性。
此外,依赖注入还提高了代码的可测试性。在单元测试中,可以轻松地替换依赖对象,模拟不同的场景,而不需要实际创建复杂的依赖关系。
总之,Spring 依赖注入通过将对象的依赖关系外部化,实现了解耦和可维护性,是 Spring 框架中不可或缺的一部分。
二、常见的依赖注入方式

(一)属性注入
属性注入是一种常见的依赖注入方式,它通过 set 方法注入 Bean 的属性值或依赖对象。这种方式具有很高的灵活性,因为可以在对象实例化后根据需要动态地设置属性值。
例如,在一个 Java 项目中,有一个名为UserService的服务类,它依赖于一个UserRepository接口的实现类来进行用户数据的操作。如果使用属性注入,可以在UserService类中定义一个UserRepository类型的属性,并提供对应的 set 方法。在 Spring 配置文件中,可以通过<property>标签将具体的UserRepository实现类注入到UserService中。
属性注入的优点在于灵活性高,可以根据不同的情况在运行时动态地设置属性值。同时,对于一些可选的依赖关系,也可以在需要的时候进行注入,而不是在对象实例化时强制注入。
(二)构造函数注入
构造函数注入是在对象实例化时,通过构造函数设置必要的属性。这种方式确保了对象实例化后即可使用,因为所有必要的依赖都在对象创建时被注入。
以一个学生管理系统为例,有一个Student类,它有name、age和grade等属性。如果使用构造函数注入,可以在Student类的构造函数中接收这些属性的值,并在对象创建时进行初始化。在 Spring 配置文件中,可以使用<constructor-arg>标签来指定构造函数的参数值。
构造函数注入的优点在于可以确保对象在创建时就处于一个完整的状态,避免了在使用对象之前可能出现的未初始化状态。同时,它也使得对象的依赖关系更加明确,因为在构造函数中可以清楚地看到对象所依赖的所有资源。
(三)Setter 方法注入
Setter 方法注入是在对象实例化后,通过调用 setter 方法实现依赖注入。这种方式使得依赖关系成为可选的,因为可以在需要的时候才进行注入。
比如在一个电商系统中,有一个Order类,它依赖于一个PaymentService类来处理支付操作。如果使用 Setter 方法注入,可以在Order类中定义一个PaymentService类型的属性,并提供对应的 setter 方法。在需要进行支付操作时,可以通过 Spring 容器调用 setter 方法将PaymentService实例注入到Order对象中。
Setter 方法注入的优点在于灵活性高,可以根据不同的业务场景在运行时动态地注入依赖关系。同时,对于一些可选的依赖,也可以在需要的时候进行注入,而不会在对象实例化时强制注入不必要的依赖。
三、其他依赖注入方式

(一)基于注解的自动装配
@Autowired注解是 Spring 框架中用于自动装配的重要注解之一。它可以应用于构造器、字段和方法注入。
在构造器注入中,当@Autowired注解用于构造器时,Spring 会在创建 Bean 实例时自动调用该构造器,并为其参数注入对应类型的实例。例如:
@Servicepublic class DriverServiceImpl implements DriverService {private DriverDao driverDao;@Autowiredpublic DriverServiceImpl(DriverDao driverDao) {this.driverDao = driverDao;}}
在接口注入中,@Autowired可以用于接口的实现类,自动注入实现接口的具体对象。
在方法注入中,如果方法有参数,会使用@Autowired的方式在容器中查找是否有该参数,并执行该方法。比如:
@Autowiredpublic void commonMethod(Bean04 bean04){System.out.println("普通方法的执行");}
@Autowired默认按类型注入。这意味着 Spring 容器会自动查找与所需类型匹配的 Bean 进行注入。此时,要求 Spring 容器中有且仅有一个合适的 Bean 为其赋值。但如果项目中有多个 Bean 可以赋值,则会发生错误。可以通过结合@Qualifier注解来指定具体的 Bean 名称进行注入,避免这种错误。
(二)Resource 注入
@Resource注解是 Java 标准(JSR-250)提供的注解,Spring 也支持该注解。它主要有name和type两个重要属性。
如果同时指定了name和type,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。例如:
@Resource(name = "myBean", type = MyBean.class)private MyBean myBean;
如果指定了name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或者找到多个,都会抛出异常。
如果既没有指定name,又没有指定type,则自动按照 byName 方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
(三)接口注入
接口注入是一种通过接口来实现依赖注入的方式。在接口中定义要注入的信息,然后通过实现该接口的类来完成注入。
例如:
public class ClassA {private InterfaceB clzB;public init() {Object obj = Class.forName(Config.BImplementation).newInstance();clzB = (InterfaceB)obj;}}
在这种方式中,通过接口将调用者与实现者分离,提高了代码的可维护性和可扩展性。但接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
四、不同注入方式对比

(一)可靠性
- 属性注入:不可靠。属性注入是在对象实例化后通过 set 方法进行注入,这意味着在对象使用过程中,属性可能会被意外修改,导致对象状态不可预测。
- 构造函数注入:可靠。构造函数注入在对象实例化时就将所有必要的依赖注入,一旦对象创建完成,其依赖关系就不会再发生变化,保证了对象的稳定性和可靠性。
- Setter 方法注入:不可靠。虽然 Setter 方法注入可以在对象实例化后进行依赖注入,但这也使得对象的依赖关系可以在运行时被随意修改,增加了对象状态的不确定性。
(二)可维护性
- 属性注入:差。属性注入的依赖关系不明显,难以直接从代码中看出对象的依赖关系,不利于代码的维护和理解。
- 构造函数注入:好。构造函数中明确列出了对象所依赖的资源,使得依赖关系一目了然,方便开发者进行代码维护和分析。
- Setter 方法注入:差。Setter 方法注入的依赖关系也不够直观,需要通过查看 setter 方法才能确定对象的依赖关系,增加了维护的难度。
(三)可测试性
- 属性注入:差。在进行单元测试时,由于属性注入的对象可能会受到外部环境的影响,难以进行有效的模拟和控制,导致测试难度较大。
- 构造函数注入:好。构造函数注入使得对象的依赖关系在创建时就确定,在单元测试中可以方便地通过构造函数传入模拟的依赖对象,进行测试。
- Setter 方法注入:好。Setter 方法注入可以在测试时根据需要设置不同的依赖对象,方便进行各种场景的测试。
(四)灵活性
- 属性注入:很灵活。属性注入可以在对象实例化后根据需要动态地设置属性值,对于一些可选的依赖关系非常方便。但这种灵活性也可能导致代码的混乱和不可控。
- 构造函数注入:不灵活。构造函数注入在对象创建时就确定了依赖关系,不能在运行时进行修改,缺乏一定的灵活性。
- Setter 方法注入:很灵活。Setter 方法注入可以在对象实例化后根据业务需求动态地注入依赖关系,具有较高的灵活性。但也可能导致依赖关系的不明确和代码的复杂性。
(五)循环关系检测
- 属性注入:不检测。属性注入方式不会自动检测 Bean 之间的循环依赖关系,可能会导致应用程序出现问题而难以排查。
- 构造函数注入:自动检测。构造函数注入在对象创建时会自动检测循环依赖关系,如果存在循环依赖,会抛出异常,便于开发者及时发现和解决问题。
- Setter 方法注入:不检测。Setter 方法注入也不会自动检测循环依赖关系,可能会导致应用程序出现死锁等问题。
(六)性能表现
- 属性注入:启动快。属性注入在启动时不需要进行复杂的依赖关系处理,启动速度相对较快。
- 构造函数注入:启动慢。构造函数注入需要在对象创建时处理所有的依赖关系,这可能会导致启动时间延长。
- Setter 方法注入:启动快。Setter 方法注入在对象实例化时不需要处理依赖关系,可以在需要时进行注入,启动速度相对较快。
五、Spring 官方推荐及原因

Spring 官方推荐构造器注入,这一推荐有多个重要原因。
首先,IDEA 警告提示 “Field injection is not recommended”,即不建议使用属性注入(字段注入)。这是因为属性注入存在一些弊端。属性注入是通过在类的变量上使用注解进行依赖注入,本质上是通过反射的方式直接注入到字段。虽然这种方式非常简洁,代码看起来简单易懂,类可以专注于业务而不被依赖注入所污染,只需要把注解扔到变量之上就好,不需要特殊的构造器或者 set 方法,依赖注入容器会提供所需的依赖。但是,成也萧何败也萧何,属性注入也会引发很多问题。
一方面,容易违背单一职责原则。使用属性注入方式,添加依赖很简单,普通开发者很可能会无意识地给一个类添加很多依赖,而当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现 something is wrong。拥有太多的依赖通常意味着类要承担更多的责任,明显违背了单一职责原则。
另一方面,属性注入会导致依赖注入与容器本身耦合。具体表现为类和依赖容器强耦合,不能在容器外使用;不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试;不能使用属性注入的方式构建不可变对象(final 修饰的变量)。
其次,Spring 开发团队建议 “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”,即在 beans 中永远使用基于构造器的依赖注入,对于必须的依赖,永远使用断言来确认。构造器注入有以下几个好处:
- 易于测试:构造器注入使得对类的单元测试变得更加容易。通过将依赖项作为构造函数的参数传递,可以轻松地在测试中传递模拟对象或存根对象,从而控制和验证类的行为。例如,对于一个OrderService类,其依赖于OrderRepository,在单元测试中,可以轻松传入模拟的OrderRepository对象,以验证OrderService的行为。
- 易于理解和维护:构造器注入提供了清晰的依赖关系,使代码更易于理解和维护。构造函数参数直观地表示了类所需的依赖项,降低了代码的复杂性。比如PaymentProcessor依赖于PaymentGateway,通过构造器注入一目了然。
- 依赖注入的一致性:构造器注入鼓励将所有依赖项都放在构造函数中,从而确保类的实例在被创建时处于一致的状态。这有助于避免在使用对象时遇到空指针异常或未初始化的依赖项。
- 不可变性:通过使用final关键字,构造器注入可以实现不可变性,这意味着一旦依赖项被设置,它们不能再被修改。这可以提高代码的安全性和稳定性。例如ShoppingCart类通过构造器注入一个不可变的List<Item>。
- 依赖项解析:构造器注入使依赖项的解析变得更加明确。当容器创建 Bean 实例时,容器只需查找所需的构造函数参数,而不需要进行复杂的解析或猜测。
- 避免循环依赖:构造器注入有助于避免循环依赖问题,因为在创建 Bean 实例时,构造函数参数必须已经可用。这有助于减少潜在的运行时错误。
综上所述,Spring 官方推荐构造器注入是出于提高代码质量、可测试性和可维护性的考虑。
相关文章:
《Spring 依赖注入方式全解析》
一、Spring 依赖注入概述 Spring 依赖注入(Dependency Injection,DI)是一种重要的设计模式,它在 Spring 框架中扮演着关键角色。依赖注入的核心概念是将对象所需的依赖关系由外部容器(通常是 Spring 容器)进…...
【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844
本文涉及知识点 C动态规划 LeetCode1411. 给 N x 3 网格图涂色的方案数 提示 你有一个 n x 3 的网格图 grid ,你需要用 红,黄,绿 三种颜色之一给每一个格子上色,且确保相邻格子颜色不同(也就是有相同水平边或者垂直…...
外包干了3年,技术退步明显...
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能…...
SpringBoot 2.x 整合 Redis
整合 1)添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 如果没有使用下面给出的工具类,那么就不需要引入 -…...
React的API✅
createContext createContext要和useContext配合使用,可以理解为 “React自带的redux或mobx” ,事实上redux就是用context来实现的。但是一番操作下来我还是感觉,简单的context对视图的更新的细粒度把控比不上mobx,除非配合memo等…...
什么是全渠道客服中心?都包括哪些电商平台?
什么是全渠道客服中心?都包括哪些电商平台? 作者:开源呼叫中心系统 FreeIPCC,Github地址:https://github.com/lihaiya/freeipcc 全渠道客服中心是一种能够同时接入并处理来自多个渠道客户咨询和请求的综合服务平台。以…...
Jtti:如何知晓服务器的压力上限?具体的步骤和方法
了解服务器的压力上限(也称为性能极限或容量)是确保系统在高负载下仍能稳定运行的重要步骤。这通常通过压力测试(也称为负载测试或性能测试)来实现。以下是详细的步骤和方法来确定服务器的压力上限: 1. 定义测试目标和指标 在进行压力测试前,明确测试目标…...
贪心算法(1)
目录 柠檬水找零 题解: 代码: 将数组和减半的最少操作次数(大根堆) 题解: 代码: 最大数(注意 sort 中 cmp 的写法) 题解: 代码: 摆动序列࿰…...
SpringBoot,IOC,DI,分层解耦,统一响应
目录 详细参考day05 web请求 1、BS架构流程 2、RequestParam注解 完成参数名和形参的映射 3、controller接收json对象,使用RequestBody注解 4、PathVariable注解传递路径参数 5、ResponseBody(return 响应数据) RestController源码 6、统一响…...
目标驱动学习python动力
文章目录 迟迟未开始的原因打破思维里的围墙抛砖引玉爬虫 结束词 迟迟未开始的原因 其实我也是很早就知道有python,当时听说这个用于做测试不错,也就一直没有提起兴趣,后来人工智能火了之后,再次接触python,安装好pyth…...
力扣-Hot100-回溯【算法学习day.39】
前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴&am…...
小熊派Nano接入华为云
一、华为云IoTDA创建产品 创建如下服务,并添加对应的属性和命令。 二、小熊派接入 根据小熊派官方示例代码D6完成了小熊派接入华为云并实现属性上传命令下发。源码:小熊派开源社区/BearPi-HM_Nano 1. MQTT连接代码分析 这部分代码在oc_mqtt.c和oc_mq…...
【linux硬件操作系统】计算机硬件常见硬件故障处理
这里写目录标题 一、故障排错的基本原则二、硬件维护注意事项三、关于最小化和还原出厂配置四、常见故障处理及调试五、硬盘相关故障六、硬盘相关故障:硬盘检测问题七、硬盘相关故障:自检硬盘报错八、硬盘相关故障:硬盘亮红灯九、硬盘相关故障…...
谈学生公寓安全用电系统的涉及方案
学生公寓安全 学生公寓安全用电系统的设计方案主要包括以下几个方面: 电气线路设计: 合理布线:确保所有电气线路按照国家或地区的电气安全标准进行设计,避免线路过载和短路。使用阻燃材料:选用阻燃或低…...
自动语音识别(ASR)与文本转语音(TTS)技术的应用与发展
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
Go 语言数组
Go 语言数组 引言 Go 语言是一种静态类型、编译型语言,由 Google 开发,旨在提高多核处理器下的编程效率。数组作为 Go 语言中的一种基本数据结构,提供了存储一系列具有相同类型元素的能力。本文将深入探讨 Go 语言中数组的使用方法、特性以…...
13. 【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--完善TODO标记的代码
这篇文章特别短,短到可以作为一篇文章的一个章节,那让我们开始吧 一、编写代码 我们在代码中标记了大量的TODO标记,并且注明了这里暂时写死,等权限和授权完成后再改为动态获取这句话。那么到目前为止和权限有关的代码已经完成了…...
深入剖析Java内存管理:机制、优化与最佳实践
🚀 作者 :“码上有前” 🚀 文章简介 :Java 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 深入剖析Java内存管理:机制、优化与最佳实践 一、Java内存模型概述 1. Java内存模型的定义与作…...
【Amazon】亚马逊云科技Amazon DynamoDB 实践Amazon DynamoDB
Amazon DynamoDB 是一种完全托管的 NoSQL 数据库服务,专为高性能和可扩展性设计,特别适合需要快速响应和高吞吐量的应用场景,如移动应用、游戏、物联网和实时分析等。 工作原理 Amazon DynamoDB 在任何规模下响应时间一律达毫秒级ÿ…...
Qt-常用的显示类控件
QLabel QLabel有如下核心属性: 关于文本格式的验证: 其中<b>xxx<b>,就是加粗的意思。 效果: 或者再把它改为markdown形式的: 在markd中,#就是表示一级标题,我们在加上##后&#x…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
