【Spring】浅谈spring为什么推荐使用构造器注入
目录
一、前言
二、常见的三种注入方式
2.1 field注入
2.2 构造器注入
2.3 setter注入
三、构造器注入的好处
四、答疑
五、总结
一、前言
Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合。前几天的时候,笔者的同事问我为什么要使用构造器的注入方式,我回答说因为Spring文档推荐这种,而说不出为什么 T^T,后面抽时间了解了一下,下面就是笔者要讨论的就是其注入方式。
二、常见的三种注入方式
笔者为了方便起见就只是用注解的方式注入(现在也很少使用xml了吧,(~ ̄▽ ̄)~)
2.1 field注入
@Controller
public class FooController {@Autowired//@Injectprivate FooService fooService;//简单的使用例子,下同public List<Foo> listFoo() {return fooService.list();}
}
这种注入方式应该是笔者目前为止开发中见到的最常见的注入方式。原因很简单:
- 注入方式非常简单:加入要注入的字段,附上
@Autowired,即可完成。 - 使得整体代码简洁明了,看起来美观大方。
2.2 构造器注入
@Controller
public class FooController {private final FooService fooService;@Autowiredpublic FooController(FooService fooService) {this.fooService = fooService;}//使用方式上同,略
}
在Spring4.x版本中推荐的注入方式就是这种,相较于上面的field注入方式而言,就显得有点难看,特别是当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。对于从field注入转过来+有强迫症的园友 来说,简直可以说是石乐志 (`Д´*)9。对于这一点我们后面再来讨论,别急。
2.3 setter注入
@Controller
public class FooController {private FooService fooService;//使用方式上同,略@Autowiredpublic void setFooService(FooService fooService) {this.fooService = fooService;}
}
在Spring3.x刚推出的时候,推荐使用注入的就是这种,笔者现在也基本没看到过这种注解方式,写起来麻烦,当初推荐Spring自然也有他的道理,这里我们引用一下Spring当时的原话:
The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.
Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.
咳咳,简单的翻译一下就是:构造器注入参数太多了,显得很笨重,另外setter的方式能用让类在之后重新配置或者重新注入。
那么后面为什么又换成构造器注入了呢?(喂喂喂,Spring你前一大版本还贬低构造器注入,后面就立刻捧人家了不好吧,不过能用于承认自己的错误,才是真正令人称赞的地方吧 (๑•̀ㅂ•́)و✧)
三、构造器注入的好处
先来看看Spring在文档里怎么说:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not
null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
咳咳,再来简单的翻译一下:这个构造器注入的方式啊,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
下面来简单的解释一下:
- 依赖不可变:其实说的就是final关键字,这里不再多解释了。不明白的园友可以回去看看Java语法。
- 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。所以保证不会为空,Spring总不至于传一个null进去吧 😦
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态。
等等,比较完了setter注入与构造器注入的优缺点,你还没用说使用field注入与构造器的比较呢!那么我们再回头看一看使用最多的field注入方式:
//承接上面field注入的代码,假如客户端代码使用下面的调用(或者再Junit测试中使用)
//这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。
FooController fooController = new FooController();
fooController.listFoo(); // -> NullPointerException
如果使用field注入,缺点显而易见,对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。
还值得一提另外一点是:使用field注入可能会导致循环依赖,即A里面注入B,B里面又注入A:
public class A {@Autowiredprivate B b;
}public class B {@Autowiredprivate A a;
}
如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。
四、答疑
好了,相信已经园友们知道了构造器注入的好处,那么回到了在前面提到的问题:
Q1:跟3.x里说的一样,我要是有大量的依赖要注入,构造方法不会显得很臃肿吗?
对于这个问题,说明你的类当中有太多的责任,那么你要好好想一想是不是自己违反了类的单一性职责原则,从而导致有这么多的依赖要注入。
Q2:是不是其他的注入方式都不适合用了呢?
当然不是,存在即是合理!setter的方式既然一开始被Spring推荐肯定是有它的道理,像之前提到的setter的方式能用让类在之后重新配置或者重新注入,就是其优点之一。除此之外,如果一个依赖有多种实现方式,我们可以使用@Qualifier ,在构造方法里选择对应的名字注入,也可以使用field或者setter的方式来手动配置要注入的实现。
五、总结
使用构造器注入的好处:
- 保证依赖不可变(final关键字)
- 保证依赖不为空(省去了我们对其检查)
- 保证返回客户端(调用)的代码的时候是完全初始化的状态
- 避免了循环依赖
- 提升了代码的可复用性
另外,当有一个依赖有多个实现的使用,推荐使用field注入或者setter注入的方式来指定注入的类型。这是spring官方博客对setter注入方式和构造器注入的比较。谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!
参考链接:
Setter injection versus constructor injection and the use of @Required
Field Dependency Injection Considered Harmful | Vojtech Ruzicka's Programming Blog
作者:joemsu
出处:joemsu - 博客园
相关文章:
【Spring】浅谈spring为什么推荐使用构造器注入
目录 一、前言 二、常见的三种注入方式 2.1 field注入 2.2 构造器注入 2.3 setter注入 三、构造器注入的好处 四、答疑 五、总结 一、前言 Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转&…...
在阿里云Linux服务器上部署MySQL数据库流程
阿里云百科分享在阿里云Linux服务器上部署MySQL数据库流程,MySQL是一个关系型数据库管理系统,常用于LAMP和LNMP等网站场景中。本教程介绍如何在Linux系统ECS实例上安装、配置以及远程访问MySQL数据库。 目录 背景信息 Alibaba Cloud Linux 2/3、CentO…...
实战——OPenPose讲解及代码实现
一些前提 先思考下面几个问题; 1、什么是姿态估计? 参考:Point Detect任务,识别人体指定部分的关键点; 2、姿态估计中的难点是什么? 从干扰的角度,人体被遮挡对检测的影响很大;…...
专注于创意设计,为您的小程序和网站建设带来更多的可能性
随着移动互联网的快速发展,越来越多的企业开始关注小程序和网站建设,以此来拓展业务和提升品牌形象。 在这个领域中,创意设计扮演着关键的角色。它不仅可以帮助企业打造独特的形象和品牌,还能够提高用户体验和购买决策的效率。 因…...
ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, CVE-2017-5754)
ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, CVE-2017-5754) 二、Variant 1 (CVE-2017-5753) 三、Variant 2 (CVE-2017-5715) 四、Variant 3 (CVE-2017-5754) 一、ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, C…...
vue3 基础语法 02
你好,今天过的怎么样呀,嘿嘿,加油夏 💕 文章目录 一、模板语法 一、模板语法 React的开发模式: React 使用的 jsx,对应的代码编写的类似于js的一种语法;通过 Babel 将 jsx , 编译成…...
版本控制工具——git
版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。 版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变,文件的…...
超详细,自动化测试实战-获取配置文件信息(实例源码)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 配置文件的类型 …...
spring 2.7.14 cors 设置 allowedOrigins(“*“)通配符 失效怎么解决
失效代码: package com.yukuanyan.searcher_web.config;import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebM…...
一、Go的前景与优势、基础语法
Go简介 Go的公司、场景应用 公司占比: 字节跳动领先,公司内部有上万个微服务使用go来编写,不久前也开源了GO RPC框架KiteX。 同时,腾讯、百度、美团、滴滴、深信服、平安、OPPO、知乎、去哪儿、360、金山、微博、哩哔哩、七牛、…...
shell脚本循环语句
shell脚本循环语句 一.echo命令二.查看当前系统的时间--date命令三.循环语句for四.while循环语句结构五.while循环语句结构(迭代)六.continue和break 一.echo命令 echo -n 表示不换行输出 echo -e输出转义符,将转义后的内容输出到屏幕上 常…...
二叉树题目:二叉树的直径
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题:二叉树的直径 出处:543. 二叉树的直径 难度 3 级 题目描述 要求 给定二叉树的根结点 root \texttt{root} root,返回其直径…...
嵌入式:C高级 Day4
一、整理思维导图 二、写一个函数,获取用户的uid和gid并使用变量接收 三、整理冒泡排序、简单选择排序和快速排序的代码 冒泡排序 #include <myhead.h>void output(int arr[], int len); void bubble_sort(int arr[], int len);int main(int argc, const ch…...
cmake常用命令(1)——函数相关
一、function/endfunction cmake中的函数与其他语言相似,表示一个命令集,可以被重复调用。形式如下: function(<name> [<arg1> ...])<commands> endfunction() function:表示函数开始 <name>…...
阿里三年功能测试的一些感悟
一、前言 功能测试是测试工程师的基础功,很多人功能测试还做不好,就想去做性能测试、自动化测试。很多人对功能测试的理解就是点点点,如何自己不用心去悟,去研究,那么你的职业生涯也就停留在点点点上了。在这里&#…...
React源码解析18(4)------ completeWork的工作流程【mount】
摘要 经过上一章,我们得到的FilberNode已经具有了child和return属性。一颗Filber树的结构已经展现出来了。 那我们最终是想在页面渲染真实的DOM。所以我们现在要在completeWork里,构建出一颗离屏的DOM树。 之前在说FilberNode的属性时,我们…...
Kafka: 详解、使用教程和示例
Kafka: 详细介绍、使用教程和示例 什么是 Kafka? Kafka 是一个分布式的流处理平台,最初由 LinkedIn 开发,现已成为 Apache 基金会的顶级项目。它以高吞吐量、可靠性和可扩展性而闻名,被广泛应用于实时数据传输、日志收集、事件处…...
【LeetCode周赛】LeetCode第358场周赛
LeetCode第358场周赛 数组中的最大数对和翻倍以链表形式表示的数字限制条件下元素之间的最小绝对差 数组中的最大数对和 给你一个下标从0开始的整数数组nums。请你从nums中找出和最大的一对数,且这两个数数位上最大的数字相等。 返回最大和,如果不存在满…...
Node.js学习笔记-04
这第九章也是个大重点 九、玩转进程 Node在选型时决定在V8引擎之上构建,也就意味着它的模型与浏览器类似。 本章关于进程的介绍和讨论将会解决如下两个问题: 单进程单线程并非完美,如今CPU基本均是多核的,真正的服务器…...
基于dbn+svr的交通流量预测,dbn详细原理
目录 背影 DBN神经网络的原理 DBN神经网络的定义 受限玻尔兹曼机(RBM) DBN+SVR的交通流量预测 基本结构 主要参数 数据 MATALB代码 结果图 展望 背影 DBN是一种深度学习神经网络,拥有提取特征,非监督学习的能力,是一种非常好的分类算法,本文将DBN+SVR用于交通流量预测…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...
spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
PLC入门【4】基本指令2(SET RST)
04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C),从 文件 - 主画面,“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...
JVM——对象模型:JVM对象的内部机制和存在方式是怎样的?
引入 在Java的编程宇宙中,“Everything is object”是最核心的哲学纲领。当我们写下new Book()这样简单的代码时,JVM正在幕后构建一个复杂而精妙的“数据实体”——对象。这个看似普通的对象,实则是JVM内存管理、类型系统和多态机制的基石。…...
DROPP算法详解:专为时间序列和空间数据优化的PCA降维方案
DROPP (Dimensionality Reduction for Ordered Points via PCA) 是一种专门针对有序数据的降维方法。本文将详细介绍该算法的理论基础、实现步骤以及在降维任务中的具体应用。 在现代数据分析中,高维数据集普遍存在特征数量庞大的问题。这种高维特性不仅增加了计算…...
