分析 Spring 的依赖注入模式

2023-10-26

一、依赖注入

依赖注入 (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 {

    @Mock
    private AnotherBean anotherBean;
    
    @Mock
    private AnotherBean2 anotherBean2;
    
    ...
    
    @Mock
    private AnotherBean10 anotherBean10;
    
    @InjectMocks
    private HelloBean helloBean;
    
    @Before
    public 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;
   // ...
   
   @Autowired
   public 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;
    
    @Before
    public 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,虽然可能会有循环依赖异常,但无论在开发、测试方面,总体而言都是利大于弊,我也一直遵循这个模式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分析 Spring 的依赖注入模式 的相关文章

  • JVisualVM/JConsole 中的 System.gc() 与 GC 按钮

    我目前正在测试处理 XML 模式的概念验证原型 并围绕一个非常消耗内存的树自动机外部库 我已经获得了源代码 构建 我想绘制 真实峰值 堆 随着模式大小的增加 不同运行的内存消耗 使用的指标符合我的目的并且不会影响问题 或者至少是它的合理近似
  • 在 Mysql 上使用 EntityManager JPA 运行脚本

    我正在尝试运行脚本 sql 文件 但由于我尝试了多种方法 因此出现多个错误 这是我的主要 sql 脚本 INSERT INTO Unity VALUES 11 paq 0 2013 04 15 11 41 37 Admin Paquete
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • Spring RestTemplate 使用 cookie 遵循重定向

    最近我遇到了一个问题 我需要做一个GET请求远程服务 我假设使用一个简单的 servlet 并且 RestTemplate 返回Too many redirects 经过一番调查 似乎对指定远程服务发出的第一个请求实际上只是一个 302 重
  • 身份验证在 Spring Boot 1.5.2 和 Oauth2 中不起作用

    我正在使用带有 spring boot 1 5 2 RELEASE 的 Oauth2 当我尝试重写 ResourceServerConfigurerAdapter 类的配置方法时 它给了我一个编译错误 但这在 Spring boot 1 2
  • 通往楼梯顶部的可能路径

    这是一个非常经典的问题 我听说谷歌在他们的面试中使用过这个问题 问题 制定一个递归方法 打印从楼梯底部到楼梯顶部的所有可能的独特路径 有 n 个楼梯 您一次只能走 1 步或 2 步 示例输出 如果它是一个有 3 级楼梯的楼梯 1 1 1 2
  • org.hibernate.QueryException:无法解析属性:文件名

    我正在使用休眠Criteria从列中获取值filename在我的桌子上contaque recording log 但是当我得到结果时 它抛出异常 org hibernate QueryException 无法解析属性 文件名 com co
  • 如何将 Mat (opencv) 转换为 INDArray (DL4J)?

    我希望任何人都可以帮助我解决这个任务 我正在处理一些图像分类并尝试将 OpenCv 3 2 0 和 DL4J 结合起来 我知道DL4J也包含Opencv 但我认为它没什么用 谁能帮我 如何转换成 INDArray 我尝试阅读一些问题here
  • 来自十六进制代码的 Apache POI XSSFColor

    我想将单元格的前景色设置为十六进制代码中的给定颜色 例如 当我尝试将其设置为红色时 style setFillForegroundColor new XSSFColor Color decode FF0000 getIndexed 无论我在
  • 如何避免 ArrayIndexOutOfBoundsException 或 IndexOutOfBoundsException? [复制]

    这个问题在这里已经有答案了 如果你的问题是我得到了java lang ArrayIndexOutOfBoundsException在我的代码中 我不明白为什么会发生这种情况 这意味着什么以及如何避免它 这应该是最全面的典范 https me
  • 使用 Mockito 模拟某些方法,但不模拟其他方法

    有没有办法使用 Mockito 模拟类中的某些方法 而不模拟其他方法 例如 在这个 诚然是人为的 Stock我想嘲笑的班级getPrice and getQuantity 返回值 如下面的测试片段所示 但我想要getValue 执行乘法 如
  • Freemarker 和 Struts 2,有时它计算为序列+扩展哈希

    首先我要说的是 使用 Struts2 Freemarker 真是太棒了 然而有些事情让我发疯 因为我不明白为什么会发生这种情况 我在这里问是因为也许其他人有一个想法可以分享 我有一个动作 有一个属性 说 private String myT
  • 使用架构注册表对 avro 消息进行 Spring 云合约测试

    我正在查看 spring 文档和 spring github 我可以看到一些非常基本的内容examples https github com spring cloud samples spring cloud contract sample
  • JMenu 中的文本居中

    好吧 我一直在网上寻找有关此问题的帮助 但我尝试的任何方法似乎都不起作用 我想让所有菜单文本都集中在菜单按钮上 当我使用setHorizontalTextPosition JMenu CENTER 没有变化 事实上 无论我使用什么常量 菜单
  • 如何重新启动死线程? [复制]

    这个问题在这里已经有答案了 有哪些不同的可能性可以带来死线程回到可运行状态 如果您查看线程生命周期图像 就会发现一旦线程终止 您就无法返回到新位置 So 没有办法将死线程恢复到可运行状态 相反 您应该创建一个新的 Thread 实例
  • Java中HashMap和ArrayList的区别?

    在爪哇 ArrayList and HashMap被用作集合 但我不明白我们应该在哪些情况下使用ArrayList以及使用时间HashMap 他们两者之间的主要区别是什么 您具体询问的是 ArrayList 和 HashMap 但我认为要完
  • org.apache.commons.net.io.CopyStreamException:复制时捕获 IOException

    我正在尝试使用以下方法中的代码将在我的服务器中创建的一些文件复制到 FTP 但奇怪的是我随机地低于错误 我无法弄清楚发生了什么 Exception org apache commons net io CopyStreamException
  • 配置“DataSource”以使用 SSL/TLS 加密连接到 Digital Ocean 上的托管 Postgres 服务器

    我正在尝试托管数据库服务 https www digitalocean com products managed databases on 数字海洋网 https en wikipedia org wiki DigitalOcean 创建了
  • 在浏览器刷新中刷新检票面板

    我正在开发一个付费角色系统 一旦用户刷新浏览器 我就需要刷新该页面中可用的统计信息 统计信息应该从数据库中获取并显示 但现在它不能正常工作 因为在页面刷新中 java代码不会被调用 而是使用以前的数据加载缓存的页面 我尝试添加以下代码来修复
  • 洪水填充优化:尝试使用队列

    我正在尝试创建一种填充方法 该方法采用用户指定的初始坐标 检查字符 然后根据需要更改它 这样做之后 它会检查相邻的方块并重复该过程 经过一番研究 我遇到了洪水填充算法并尝试了该算法 它可以工作 但无法满足我对 250 x 250 个字符的数

随机推荐

  • Pcap文件详解

    Pcap文件详解 一 简介 pcap文件是常用的数据报存储格式 可以理解为就是一种文件格式 只不过里面的数据是按照特定格式存储的 所以我们想要解析里面的数据 也必须按照一定的格式 普通的记事本打开pcap文件显示的是乱码 用安装了HEX E
  • Java-数组的定义和使用

    一 数组的基本用法 1 什么是数组 数组本质上就是让我们能 批量 创建相同类型的变量 2 创建数组 动态初始化 数据类型 数组名称 new 数据类型 初始化数据 静态初始化 数据类型 数组名称 初始化数据 代码示例 int arr new
  • css样式表属性

    文章目录 css样式表属性 color background color font size font weight font family font style text decoration text indent line heigh
  • JDBC连接多个SQLServer实例

    机器上同时装有SQLServer2000和2005 并存在多个数据库实例时 在写dbURL时需要在服务器地址后加实例名称 否则将连接到机器默认的数据库实例 在注册表的HKEY LOCAL MACHINE SOFTWARE Microsoft
  • 几行代码,搞定 SpringBoot 接口恶意刷新和暴力请求

    在实际项目使用中 必须要考虑服务的安全性 当服务部署到互联网以后 就要考虑服务被恶意请求和暴力攻击的情况 下面的教程 通过intercept和redis针对url ip在一定时间内访问的次数来将ip禁用 可以根据自己的需求进行相应的修改 来
  • 跨域问题解决

    在网关中添加配置类 import org springframework context annotation Bean import org springframework context annotation Configuration
  • 程序员如何做好职业规划?

    1 了解自己的职业目标 首先 要明确自己的职业目标 包括想成为什么样的程序员 是否想做管理工作等 2 了解市场需求 了解市场对技能的需求 通过对人才市场的认识 了解市场对不同类型程序员的需求和薪资水平 3 学习技能 根据自己的职业目标和市场
  • 软件使用:如何彻底把VMware卸载干净

    1 禁用VM虚拟机服务 首先 需要停止虚拟机VMware相关服务 按下快捷键WIN R 打开windows运行对话框 输入services msc 点击确定 在服务管理中 找到VM开头的所有服务类别 将所有VM开头的服务禁用掉 右击选择属性
  • 直接插入排序(深入讲解哨兵的作用)---------通俗易懂,直击重点!!!

    直接插入排序详细讲解 直接插入排序 Straight Insertion Sort 哨兵 代码区实例 你好 欢迎来和我一起学习 直接插入排序算法内容 如果大佬们发现问题希望指出 我会尽全力来更改 希望我们共同进步 直接插入排序 Straig
  • Python:PyCharm 永久破解方法,真的超超超超超超超超超级简单!!!

    准备工作 1 破解包 gt gt gt 下载链接 gt gt gt 提取码 jjbf 2 注册码 gt gt gt 获取地址 gt gt gt 第一步 进入PyCharm 的安装目录的bin文件夹下 把破解包放到该目录 第二步 把bin 目
  • 正确理解stem教育

    成人对STEM教育的理解和态度将深刻地影响孩子们对STEM学习的信念和他们的能力发展 无论孩子天赋如何 STEM对所有的孩子非常有利 也对所有学科领域都很重要 格物斯坦小坦克告诉大家如何正确理解stem教育 当我们学习新技能时 大脑将整合之
  • Tensorflow中tf.nn.bias_add()以及tf.add()和tf.add_n的实现

    tf nn bias add 通俗解释 一个叫bias的向量加到一个叫value的矩阵上 是向量与矩阵的每一行进行相加 得到的结果和value矩阵大小相同 import tensorflow as tf a tf constant 1 1
  • Qt 内存回收机制

    关于 Qt 内存回收机制的 刚接触 Qt 不久 第一次看到这方面的问题 这篇文章需要在对Qt有很多了解后才会理解的更透彻 在你不断补充知识时 温故而知新 看到更全面和更官方的内容时随时更新 C GUI Programming with Qt
  • 多线程+隧道代理:提升爬虫速度

    在进行大规模数据爬取时 爬虫速度往往是一个关键问题 本文将介绍一个提升爬虫速度的秘密武器 多线程 隧道代理 通过合理地利用多线程技术和使用隧道代理 我们可以显著提高爬虫的效率和稳定性 本文将为你提供详细的解决方案和实际操作价值 同时附上Py
  • H5页面实现跳转微信公众号主页面

    由于考虑到微信商城内用户分享给未关注用户 所以进行openid判断 并且进行 跳转 谨此记录一下 1 登录公众号管理后台 2 F12 进入element 模块 3 ctrl f 搜索uin base64字段 4 在下面代码中填入uin ba
  • 戴尔计算机网卡驱动程序,dell戴尔网卡驱动如何安装

    网卡是电脑连接外部网络的重要设备 可以实现局域网传输介质之间的物理连接和电信号匹配 不过想要正常使用网卡设备还需要安装网卡驱动 下面 我就以Dell网卡为例 教大家安装驱动的方法 Dell 戴尔 以设计 生产 销售类型电脑而闻名 戴尔电脑质
  • vue-element-admin项目关闭eslint校验

    事情是这样的 今天在启动项目的时候报错了 报错差不多是这样的 我们都知道 ESLint 是一个代码规范和错误检查工具 对于代码的语法格式的规范相当的严格 我忘记自己在哪里的代码写的出现问题了 但现在直接启动不了就很麻烦了 只有先关闭一下es
  • CentOS7.6服务器搭建SFTP服务及JAVA工具类

    一 SFTP的简介 sftp Secure File Transfer Protocol 是一种安全的文件传送协议 是ssh内含协议 也就是说只要sshd服务器启动了 sftp就可使用 不需要额外安装 它的默认端口和SSH一样为22 1 s
  • thinkphp6 入门(4)--数据库操作 增删改查

    一 设计数据库表 比如我新建了一个数据库表 名为test 二 配置数据库连接信息 本地测试 直接在 env中修改 不用去config database php中修改 正式环境 三 增删改查 引入Db库 use think facade Db
  • 分析 Spring 的依赖注入模式

    一 依赖注入 二 Field Injection 优点 缺点 三 Constructor Injection 优点1 容易发现 code smell 优点2 容易厘清依赖关系 优点3 容易写单元测试 优点4 Immutable Object