场注入到底是什么?如何避免?


130

我在有关Spring MVC和Portlet的一些帖子中读到,不建议使用字段注入。据我了解,字段注入是当您使用以下方式注入Bean时@Autowired

@Component
public class MyComponent {
    @Autowired
    private Cart cart;
}

在研究期间,我还阅读了有关构造函数注入的信息

@Component
public class MyComponent {
    private final Cart cart;

    @Autowired
    public MyComponent(Cart cart){
       this.cart = cart;
    }
}

这两种类型的注射都有哪些优缺点?


编辑1:由于此问题被标记为该问题的重复,我检查了它。因为在问题和答案中都没有任何代码示例,所以我不确定我所使用的注入类型是否正确。


3
如果场注入与您描述的一样糟糕,为什么Spring允许它?字段注入有其自身的优势,可以使代码更具可读性,而又不那么冗长。如果您在编码方面有足够的纪律性,则可以确保即使使用字段注入,事情也不会中断。
骨灰

@ashes,因为当时它是一个简洁的功能,而且还没有完全考虑到其含义。Date(int,int,int)存在相同的原因。
chrylis -cautiouslyoptimistic-

Answers:


224

注射类型

关于如何将依赖项注入到bean中,有三个选项:

  1. 通过构造函数
  2. 通过二传手或其他方法
  3. 通过反射,直接进入田野

您正在使用选项3。这就是@Autowired直接在字段上使用时发生的情况。


注射指南

Spring建议的一般准则如下(请参阅有关基于构造函数的DI基于Setter的DI的部分):

  • 对于强制性依赖性或针对不变性,请使用构造函数注入
  • 对于可选或可变的依赖项,请使用setter注入
  • 大多数情况下避免现场注入

场注入的缺点

禁止使用场注入的原因如下:

  • 您不能像构造函数注入那样创建不可变的对象
  • 您的课程与您的DI容器紧密耦合,不能在其外部使用
  • 没有反射,就无法实例化您的类(例如,在单元测试中)。您需要DI容器来实例化它们,这使您的测试更像集成测试
  • 您真正的依赖项从外部隐藏,并且没有反映在您的界面中(构造函数或方法)
  • 拥有十个依赖关系确实很容易。如果正在使用构造函数注入,则将有一个带有十个参数的构造函数,这将表明某些东西很混乱。但是您可以无限期地使用字段注入来添加注入的字段。依赖关系过多是一个危险信号,即该类通常不仅仅做一件事,而且还可能违反单一职责原则。

结论

根据您的需求,您应该主要使用构造函数注入或构造函数和setter注入的某种混合。场注入具有许多缺点,应该避免。场注入的唯一优点是写起来更方便,但没有克服所有缺点。


进一步阅读

我写了一篇博客文章,介绍为什么通常不建议使用字段注入:字段依赖注入被认为有害


12
告诉世界“应避免现场注入”是一个普遍的好主意,也不是好事。展示自己的利与弊,让别人自己决定;)许多人都有其他的经历和自己看待事物的方式。
节食者

6
在这里可能就是这种情况,但是在其他情况下,社区已达成普遍共识以阻止某些事情。以匈牙利表示法为例。
Jannik

您在可测试性和依赖项可见性方面给出了一些优点,但是我并不完全同意。构造函数注入有没有弊端?具有5个或6个字段以插入类中以执行真正的呼叫组合可能是理想的。我也坚决反对你。具有final字段对于使类不可变不是强制性的。最好。这是非常不同的。
davidxxx

我认为您的意思是“对于强制性依赖项或针对不变性
Alex Terreaux

1
我指的是答案的开头,它指向春季文档的链接
Vojtech Ruzicka,

47

这是软件开发中永无止境的讨论之一,但是行业中的主要影响者对此话题越来越持怀疑态度,并开始建议使用构造函数注入作为更好的选择。

构造器注入

优点:

  • 更好的可测试性。在单元测试中,您不需要任何模拟库或Spring上下文。您可以使用new关键字创建要测试的对象。这样的测试总是更快,因为它们不依赖于反射机制。(此问题在30分钟后被提出。如果作者使用了构造函数注入,就不会出现)。
  • 不变性。一旦设置了依赖性,就无法更改它们。
  • 更安全的代码。执行完构造函数后,您的对象就可以使用了,因为您可以验证作为参数传递的任何内容。对象可以是就绪的,也可以是不准备的,两者之间没有任何状态。使用场注入,您可以在对象易碎时引入中间步骤。
  • 强制依赖的清晰表达。在这件事上,场注入是模棱两可的。
  • 使开发人员考虑设计。dit讲述了一个带有8个参数的构造函数,它实际上是设计不良和God对象反模式的标志。一个类在其构造函数或字段中是否具有8个依赖关系都没有关系,这总是错误的。人们比通过字段更不愿意向构造函数添加更多依赖项。它向您的大脑发出信号,您应该停一会儿并考虑一下代码结构。

缺点:

  • 更多代码(但现代IDE减轻了痛苦)。

基本上,场注入是相反的。


1
可测试性,是的,这是我梦mock以求地注入大豆的噩梦。一次,我使用了constructor注射,我不需要做任何不必要的嘲笑
kenobiwan

25

味道的问题。这是你的决定。

但是我可以解释一下,为什么我从不使用构造函数注入

  1. 我不希望实现我所有的构造@Service@Repository@Controller豆类。我的意思是,大约有40至50颗豆或更多。每次添加新字段时,都必须扩展构造函数。不,我不需要,也不必。

  2. 如果您的Bean(服务或控制器)需要注入许多其他Bean,该怎么办?具有4个以上参数的构造函数非常难看。

  3. 如果我使用的是CDI,则构造函数与我无关。


编辑#1:Vojtech Ruzicka说:

类具有过多的依赖关系,可能违反了单一责任原则,应进行重构

是。理论与现实。这是en示例:DashboardController映射到单路径*:8080/dashboard

DashboardController的网站从其他服务收集了大量信息,以将其显示在仪表板/系统概述页面中。我需要这个控制器。因此,我只需要保护这一路径(基本身份验证或用户角色过滤器)。

编辑#2:由于每个人都专注于构造函数中的8个参数...这是一个真实的示例-客户的遗留代码。我已经改变了。同样的论点适用于4个以上的参数。

全部与代码注入有关,而不是实例构造。


34
具有8个依赖项的非常丑陋的构造函数实际上是很棒的,因为它是一个警告,表明出了点问题,类具有过多的依赖项,并且可能违反了单一职责原则,应该对其进行重构。这实际上是一件好事。
Vojtech Ruzicka

6
@VojtechRuzicka肯定不是很好,但有时您无法避免。
节食者

4
我要说的经验法则是3(更不用说40-50),任何类的依赖关系都应表明您需要重构。拥有40个依赖关系的类绝不可能坚持单一责任主体或开放/关闭主体。
Amin J

4
@AminJ规则很棒,但现实却不同。我工作的公司已有20多年的历史,我们有许多旧代码。重构是一个好主意,但要花钱。我也不知道为什么这么说,但我不是说40-50依赖,我是说40-50豆类,组件,模块……
Dieter

7
@dit,您的情况显然是技术债务使您做出次优选择的情况。用您自己的话说,您处于决策受20年以上旧代码严重影响的情况。在开始新项目时,您仍然会建议使用字段注入,而不是构造函数注入吗?也许您应该在答案中做出警告,以表明在哪种情况下您将选择现场注入。
Umar Farooq Khawaja

0

另一则评论-Vojtech Ruzicka指出,Spring通过以下三种方式注入豆类(得分最高的答案):

  1. 通过构造函数
  2. 通过二传手或其他方法
  3. 通过反射,直接进入田野

这个答案是错误的-因为每一种注射弹簧都需要反射!使用IDE,在setter /构造函数上设置断点,然后检查。

这可能是一个口味问题,但也可能是一个案例问题。当场注入更好时,@ dieter提供了一个很好的例子。如果您在设置Spring上下文的集成测试中使用字段注入-带有类可测试性的参数也是无效的-除非您想稍后在测试中编写集成测试;)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.