Field Injection 究竟是什么以及如何避免它?

What exactly is Field Injection and how to avoid it?

我在一些关于 Spring MVC 和 Portlet 的帖子中读到,不推荐 字段注入 。据我了解,field injection 是当你用 @Autowired 注入 Bean 时,如下所示:

@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: 因为这个问题被标记为 this question 的重复问题,所以我检查了它。因为问题和答案中都没有任何代码示例,所以我不清楚我的猜测是否正确,我正在使用哪种注入类型。

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

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

  1. 我不想为所有 @Service@Repository@Controller bean 实现构造函数。我的意思是,大约有 40-50 个豆子或更多。每次如果我添加一个新字段,我都必须扩展构造函数。不,我不想要,也没有必要。

  2. 如果您的 Bean(服务或控制器)需要注入大量其他 bean 怎么办?具有 4 个以上参数的构造函数非常丑陋。

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


编辑#1: Vojtech Ruzicka 说:

class has too many dependencies and is probably violating single responsibility principle and should be refactored

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

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

编辑#2: 因为每个人都关注构造函数中的 8 个参数......这是一个真实世界的例子 - 客户遗留代码。我已经改变了。对于 4 个以上的参数,同样的论证适用于我。

这都是关于代码注入,而不是实例构造。

注入类型

对于如何将依赖项注入到 bean 中,存在三个选项:

  1. 通过构造函数
  2. 通过setter或其他方法
  3. 通过反射,直接进入字段

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


注射指南

一般准则,which is recommended by Spring (see the sections on Constructor-based DI or Setter-based DI) 如下:

  • 对于强制依赖项或以不变性为目标,使用构造函数注入
  • 对于可选的或可更改的依赖项,使用 setter 注入
  • 在大多数情况下避免字段注入

字段注入缺点

字段注入不受欢迎的原因如下:

  • 您不能像使用构造函数注入那样创建不可变对象
  • 你的 classes 与你的 DI 容器紧密耦合,不能在它之外使用
  • 您的 classes 不能在没有反射的情况下实例化(例如在单元测试中)。您需要 DI 容器来实例化它们,这使您的测试更像集成测试
  • 您真正的依赖项对外部是隐藏的,并且不会反映在您的界面中(无论是构造函数还是方法)
  • 拥有十个依赖项真的很容易。如果您正在使用构造函数注入,您将拥有一个带有十个参数的构造函数,这表明有些东西是可疑的。但是您可以无限期地使用字段注入添加注入字段。依赖太多是一个危险信号,表明 class 通常做不止一件事,并且可能违反单一职责原则。

结论

根据您的需要,您应该主要使用构造函数注入或构造函数和 setter 注入的某种组合。字段注入有很多缺点,应该避免。字段注入唯一的好处就是写起来比较方便,这并不能抵消所有的缺点。


进一步阅读

我写了一篇关于为什么通常不推荐使用字段注入的博客文章:Field Dependency Injection Considered Harmful

这是软件开发中永无止境的讨论之一,但行业中的主要影响者对这个话题越来越有意见,并开始建议将构造函数注入作为更好的选择。

构造函数注入

优点:

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

缺点:

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

基本上场注入是相反的

还有一条评论 - Vojtech Ruzicka 表示 Spring 以三种方式注入 bean(得分最多的答案):

  1. 通过构造函数
  2. 通过setter或其他方法
  3. 通过反射,直接进入字段

这个答案是错误的 - 因为对于每一种注入 SPRING 都使用反射! 使用 IDE,在 setter / constructor 上设置断点,然后检查。

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