当前位置: 首页 > news >正文

分析 Spring 的依赖注入模式

    • 一、依赖注入
    • 二、Field Injection
      • 优点
      • 缺点
    • 三、Constructor Injection
      • 优点1. 容易发现 code smell
      • 优点2. 容易厘清依赖关系
      • 优点3. 容易写单元测试
      • 优点4. Immutable Object
      • 缺点:循环依赖
    • 四、总结

一、依赖注入

依赖注入 (Dependency Injection, DI) 是 Spring 实现控制反转概念的重要手段。 Spring 提供了多种依赖注入方式,其中最方便、最常用的是 field injection,它应该是许多人第一次写 Spring 项目时所使用的模式,虽然这方式简单易用,却有不少缺点。

例如你会发现, IntelliJ IDEA 会很贴心地告诉我们:

Field Injection is not recommended.
Spring Team recommends: “Always use constructor based dependency
injection in tour beans. Always use assertions for mandatory
dependencies”.

在这里插入图片描述

为何 constructor injection 优于 field injection 呢? 接下来我会解析这两种模式。 (虽然 Spring 还有其他种注入方式,但我比较不常用,所以就不在此介绍了)

二、Field Injection

这种注入方式顾名思义,就是直接在 field 加上 @Autowired

@Component
public class HelloBean {@Autowired private AnotherBean anotherBean;@Autowired private AnotherBean2 anotherBean2;// ...

优点

  • 简单方便易用,只要短短一行即可完成。
  • 代码最少,读起来真舒服

缺点

  • 不易维护,因为简单方便,更容易产生code smell而不自知,例如God Object
  • 不好写单元测试,测试环境需要通过DI container并加上许多@Annotation来初始化,看起来更像整合测试了。 而且编译、执行时会多一些 overhead。
  • 不好理解测试,以下程序为例
@RunWith(MockitoJUnitRunner.class)
public class HelloBeanTest {@Mockprivate AnotherBean anotherBean;@Mockprivate AnotherBean2 anotherBean2;...@Mockprivate AnotherBean10 anotherBean10;@InjectMocksprivate HelloBean helloBean;@Beforepublic void setup() {...}// Test cases...
}

这是相当常见的 Mockito+Junit 单元测试写法,但容易造成疑问:

  • @RunWith(MockitoJUnitRunner.class) 是什么意思 ?
  • @InjectMocks 做了什么 ?
  • 是否需要将待测对象 实体化呢 ?HelloBean
  • 如果有两个 类型的依赖怎么办 ?AnotherBean

只有短短几行就让人产生诸多疑问,因此理解成本较高。 虽然这种注入方式很简单方便,但写单元测试时就得还债了。 若使用 constructor injection 则不易产生此问题,我们接着看下去:

三、Constructor Injection

此方式最大的特点是: Bean 的建立与依赖的注入是同时发生的

@Component
public class HelloBean {private final AnotherBean anotherBean;private final AnotherBean2 anotherBean2;// ...@Autowiredpublic HelloBean(AnotherBean anotherBean, AnotherBean2 anotherBean2, ...) {this.anotherBean = anotherBean;this.anotherBean2 = anotherBean2;// ...}// ...
}

优点1. 容易发现 code smell

假设我们需要注入十几个 dependecies,对比 field injection 的方式,这种方式暴露了 constructor 中含有过多的参数 (Long Parameter List),这是个很好的臭味侦测器,正常的开发者看到这么多参数肯定是会头痛的,这就表示我们需要想办法重构它,尽可能使它符合单一职责原则 ( Single Responsibility Principle)。

优点2. 容易厘清依赖关系

一看到 constructor 就可以让开发者厘清这个物件所需要的 dependency,且缺一不可,进而缩小该物件在项目中的使用范围,事物的范围越窄,就越容易理解与维护。 另外,我们也可以透过 constructor 注入假的依赖,进而容易写单元测试。

优点3. 容易写单元测试

一个简单的范例:

public class HelloBeanTest {private HelloBean helloBean;@Beforepublic void setup() {AnotherBean anotherBean = mock(AnotherBean.class);AnotherBean2 anotherBean2 = mock(AnotherBean2.class);// ...helloBean = new HelloBean(anotherBean, anotherBean2, ...);}// Test cases...
}

相较前面的例子,这种注入方式不需要太多 @Annotation,让测试程式码看起来更干净了,我们也能轻松的用 来实体化待测对象、注入假依赖,整体而言看起来更 清楚、好理解,就算是不熟 Java 或 Mockito 的开发人员应该也能看得懂七八成,对于新人也比较好上手,而且也比较不会有误用 @Annotation 所产生额外成本 ,优秀的单元测试就应该如此。new

优点4. Immutable Object

意思是 Bean 在被创造之后,它的内部 state, field 就无法被改变了。 不可变意味着只读,因而具备线程安全(Thread-safety)的特性。 此外,相较于可变对象,不可变对象在一些场合下也较合理、易于了解,而且提供较高的安全性,是个良好的设计。 因此,透过 constructor injection,再把依赖宣都告成 final,就可以轻松建立 Immutable Object。

缺点:循环依赖

只有在使用 constructor injection 时才会造成此问题。

举个简单的例子,若依赖关系图: Bean C → Bean B → Bean A → Bean C ,则会造成造成此问题,程序在 Runtime 会抛出,更白话来说,这就是鸡生蛋 / 蛋生鸡的问题,而 Spring 容器初始化时无法解决这样的窘境,因此抛出例外并中断程序。BeanCurrentlyInCreationException

在这里插入图片描述
但是,Circular dependency 其实算是一种 Anti-Pattern,所以如果能够实时发现它,提早让开发人员意识到该问题重新设计此 bean,我个人认为这点反而蛮好的。

四、总结

本文介绍了两种依赖注入模式,它们各有好坏,也都能达到同样的目的,而比较常见的是 field injection,但不幸的这种方式较可能会写出 code smell。 另外,Spring 官方团队建议开发者使用 constructor injection,虽然可能会有循环依赖异常,但无论在开发、测试方面,总体而言都是利大于弊,我也一直遵循这个模式。

相关文章:

分析 Spring 的依赖注入模式

一、依赖注入二、Field Injection优点缺点三、Constructor Injection优点1. 容易发现 code smell优点2. 容易厘清依赖关系优点3. 容易写单元测试优点4. Immutable Object缺点:循环依赖四、总结一、依赖注入 依赖注入 (Dependency Injection,…...

IntelliJ IDEA创建Servlet

目录 ——————————————————————————————— 一、创建Java项目 1、创建java项目 2、选择java 3、next 4、给项目命名 5、新创建完java项目的目录结构 二、变java为servlet项目 1、变servlet项目 2、选择Web Application 3、更新完成后的目录…...

Spring Boot如何让自己的bean优先加载

背景介绍 在一些需求中,可能存在某些场景,比如先加载自己的bean,然后自己的bean做一些DB操作,初始化配置问题,然后后面的bean基于这个配置文件,继续做其他的业务逻辑。因此有了本文的这个题目。 实现方法…...

LeetCode分类刷题----动态规划

动态规划509.斐波那契数列70.爬楼梯746.使用最小花费怕楼梯62.不同路径63.不同路径||343.整数拆分96.不同的二叉搜索树01背包问题416.分割等和子集1049.最后一块石头的重量||494.目标和474.一和零完全背包问题518.零钱兑换||377.组合总和IV322.零钱兑换279.完全平方数139.单词拆…...

今年好像没有金三银四了?

大家好,我是记得诚。 金三银四,是换工作的高峰期,新的一年结束了,在年前拿完年终奖,在年后3月和4月换个满意的工作。 单从我公司来看,目前还没有一个人离职,往年离职率是要高一些的。 还有我…...

【C++】入门知识之 函数重载

前言提到重载这个词,我们会想到什么呢?重载有一种一词多义的意思,中华文化博大精深,之前有一个笑话,中国的乒乓球谁都打不过,男足谁都打不过,哈哈哈这也是非常有意思的,但是今天我们…...

文心一言发布,你怎么看?chatGPT

百度全新一代知识增强大语言模型“文心一言”于2021年3月16日正式发布,作为一款自然语言处理技术,它引起了广泛的关注和讨论。 首先,文心一言是一款具有重大意义的自然语言处理技术。在人工智能领域,自然语言处理技术一直是一个难…...

字符函数和字符串函数【上篇】

文章目录🎖️1.函数介绍📬1.1. strlen📬1.2. strcpy📬1.3. strcat📬1.4. strcmp📬1.5. strncpy📬1.6. strncat📬1.7. strncmp🎖️1.函数介绍 📬1.1. strlen …...

list的模拟实现(模仿STL)

目录 一、模拟实现前的准备 1.list结构认识 2.迭代器的实现不同 3.如何实现需要的功能 二.结点类实现 三.迭代器实现 1.实现前的问题 2._list_iterator类的成员变量和构造函数 3.*和->运算符重载 4.前置和后置的实现 5.前置--和后置-- 6.和!运算符重载 四.list类的实现 1.li…...

05-STM32F1 - 串行通信SPI

SPI STM-SPI作为主机,从机 SPI的时钟,最高为Pclk/2,SPI1最高为36Mhz,SPI2最高为18Mhz。 SPI的四种模式 CPOL CPHA,数据帧8~16位,LSB,MSB 全双工,双向单线,单线 物理层 接口标准…...

【Pytorch】Tensor的分块、变形、排序、极值与in-place操作

本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 这是目录Tensor的分块Tensor的变形Tensor的排序Tensor的极值Tensor的in-place操作Tensor是PyTorch中用于存储和处理多维数据的基本数据结构,它类似于NumPy中的ndarray&…...

数组栈的实现

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【数据结构初阶(C实现)】 目录所有接口函数栈的初始化在栈顶放数据释放数据删除数据取栈顶的数据判断栈取区是否为…...

*p++,*(p++),*++p,(*p)++区别?

*p++:等同于:*p; p += 1; 解析:由于和++的运算优先级一样,且是右>结合。故p++相当于*(p++),p先与++结合,>然后p++整体再与结合。前面陈述是一种最 常见的错误,很多初学者也是这么理解的。 但是,因为++后置的时候,本身含义就是先 运算后增加1(运算指的是p++作为…...

又一个线上偶发问题-系统短暂无法获取到Redis连接

概述 最近不知道咋回事,老是在线上遇到偶发的故障,它突然出现,又很快消失了。 在2023年3月11下午差不多六点左右,我正在工位上喝着香味扑鼻的金骏眉红茶,突然接到了一个电话,拿起手机一看,是阿里…...

[ 系统安全篇 ] 拉黑IP - 火绒安全软件设置IP黑名单 windows使用系统防火墙功能设置IP黑名单

🍬 博主介绍 👨‍🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...

MongoDB【部署 01】mongodb最新版本6.0.5安装部署配置使用及mongodb-shell1.8.0安装使用(云盘分享安装文件)

云盘分享文件: 链接:https://pan.baidu.com/s/11sbj1QgogYHPM4udwoB1rA 提取码:l2wz 1.mongodb简单介绍 MongoDB的 官网 内容还是挺丰富的。 是由 C语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下&…...

算法竞赛必考算法——动态规划(01背包和完全背包)

动态规划(一) 目录动态规划(一)1.01背包问题1.1题目介绍1.2思路一介绍(二维数组)1.3思路二介绍(一维数组) 空间优化1.4思路三介绍(输入数据优化)2.完全背包问题2.1题目描述:2.2思路一(朴素算法)2.3思路二(将k优化处理掉)2.4思路三(优化j的初始条件)总结1.01背包问题…...

基于深度学习的农作物叶片病害检测系统(UI界面+YOLOv5+训练数据集)

摘要:农作物叶片病害检测系统用于智能检测常见农作物叶片病害情况,自动化标注、记录和保存病害位置和类型,辅助作物病害防治以增加产值。本文详细介绍基于YOLOv5深度学习模型的农作物叶片病害检测系统,在介绍算法原理的同时&#…...

QT入门Item Views之QListView

目录 一、QListView界面相关 1、布局介绍 二、代码展示 1、创建模型,导入模型 2、 设置隔行背景色 3、删除选中行 三、源码下载 此文为作者原创,创作不易,转载请标明出处! 一、QListView界面相关 1、布局介绍 先看下界面…...

GEE:计算1990-2021年的指数最大值和最小值,并根据最大最小值对每一副影像归一化

本文记录了在GEE平台上计算影像集合中所有像素的最大值和最小值。并且根据该最大最小值对所有影像进行最大最小值归一化。以SAVI为例,记录了主要函数的使用方法和代码。 结果如图所示, 文章目录 一、计算每一副影像的最大值或者最小值,并将最值保存在 List 中二、计算 Lis…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...