当前位置: 首页 > 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,虽然可能会有循环依赖异常,但无论在开发、测试方面,总体而言都是利大于弊,我也一直遵循这个模式。

http://www.lryc.cn/news/40814.html

相关文章:

  • IntelliJ IDEA创建Servlet
  • Spring Boot如何让自己的bean优先加载
  • LeetCode分类刷题----动态规划
  • 今年好像没有金三银四了?
  • 【C++】入门知识之 函数重载
  • 文心一言发布,你怎么看?chatGPT
  • 字符函数和字符串函数【上篇】
  • list的模拟实现(模仿STL)
  • 05-STM32F1 - 串行通信SPI
  • 【Pytorch】Tensor的分块、变形、排序、极值与in-place操作
  • 数组栈的实现
  • *p++,*(p++),*++p,(*p)++区别?
  • 又一个线上偶发问题-系统短暂无法获取到Redis连接
  • [ 系统安全篇 ] 拉黑IP - 火绒安全软件设置IP黑名单 windows使用系统防火墙功能设置IP黑名单
  • MongoDB【部署 01】mongodb最新版本6.0.5安装部署配置使用及mongodb-shell1.8.0安装使用(云盘分享安装文件)
  • 算法竞赛必考算法——动态规划(01背包和完全背包)
  • 基于深度学习的农作物叶片病害检测系统(UI界面+YOLOv5+训练数据集)
  • QT入门Item Views之QListView
  • GEE:计算1990-2021年的指数最大值和最小值,并根据最大最小值对每一副影像归一化
  • LeetCode KMP 算法
  • 全面剖析OpenAI发布的GPT-4比其他GPT模型强在哪里
  • leetcode——26. 删除有序数组中的重复项
  • 基于springboot垃圾分类网站设计实现【毕业论文、源码】
  • 计算机组成原理实验一(完整)
  • 【SSM】MyBatis(一.基础)
  • LInux指令之文件目录类
  • 【c++】:STL中vector的模拟使用及模拟实现
  • C++ STL:vector的使用方法及模拟实现
  • naive UI 的upload组件自定义手动上传图片的base64位
  • 信创办公–基于WPS的PPT最佳实践系列(表格和图标常用动画)