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

时间:2016-10-06 08:12:05

标签: java spring-mvc dependency-injection portlet autowired

我在一些关于PortletsBean的帖子中读到了不建议进行现场注射的内容。因为我试图得到一个所以我问自己我是否正在使用现场注射而我无法回答它。据我了解,字段注入是指您将@Autowired注入... @Autowired private Cart cart; ... 的属性,如下所示:

CartController.java:

@Configuration
public class BookShopConfiguration {

@Bean
public Cart cart(){
    return new Cart();
}
//more configuration

BookshopConfiguartion.java:

Cart.java

我的... public class MyComponent{ private Cart cart; @Autowired public MyComponent(Cart cart){ this.cart = cart; } ... 用于存储和提供购物车中书籍的相关信息。

在我的研究中,我读到了构造函数注入

MyComponent.java:

con.commit()

这两种注射剂有哪些优点和缺点?

编辑1:由于此问题被标记为this question的重复,我检查了它。因为在问题和答案中都没有任何代码示例,我不清楚我是否正确猜测我正在使用哪种注射类型。

5 个答案:

答案 0 :(得分:130)

注射类型

如何将依赖项注入bean中有三个选项:

  1. 通过构造函数
  2. 通过制定者或其他方法
  3. 通过反思,直接进入字段
  4. 您正在使用选项3.这就是您在字段上直接使用@Autowired时发生的情况。

    注射指南

    一般准则which is recommended by Spring(请参阅Constructor-based DISetter-based DI部分)如下:

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

    现场注入缺陷

    现场注射不赞成的原因如下:

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

    <强>结论

    根据您的需要,您应该主要使用构造函数注入或构造函数和setter注入的混合。现场注入有许多缺点,应该避免。现场注入的唯一优势是写入更方便,这不会超过所有缺点。

    进一步阅读

    我写了一篇博客文章,说明为什么通常不建议进行现场注射:Field Dependency Injection Considered Harmful

答案 1 :(得分:31)

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

构造函数注入

优点:

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

缺点:

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

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

答案 2 :(得分:19)

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

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

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

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

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

  4. 修改: Vojtech Ruzicka说:

      

    类有太多依赖项,可能违反了单个   责任原则,应该重构

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

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

答案 3 :(得分:3)

Field
@Autowired
private DependencyA dependencyA;

@Autowired
private DependencyB dependencyB;

@Autowired
private DependencyC dependencyC;

怎么了? 如您所见,Field变体看起来非常漂亮。它非常简短,简洁,没有样板代码。该代码易于阅读和浏览。您的班级只专注于重要的课程,不会受到DI样板的污染。您只需将@Autowired注释放在字段上方即可。没有专门为DI容器提供依赖项的特殊构造函数或设置方法。 Java本身非常冗长,因此欢迎您提供所有缩短代码的机会,对吧?

违反单一责任原则

添加新的依赖项非常容易。也许太容易了。添加六个,十个甚至十个依赖关系没有问题。当您将构造函数用于DI时,在特定点之后,构造函数参数的数量变得过多,并且很明显,出了点问题。依赖关系过多通常意味着该类承担太多职责。这可能违反了“单一责任原则”和关注点分离,并且是一个很好的指示,表明该类需要进一步检查和可能的重构。直接注入字段时没有这种危险信号,因为这种方法可以无限期扩展。

依赖隐藏

使用DI容器意味着该类不再负责管理其自己的依赖项。从类中提取获取依赖项的责任。现在,其他人负责提供依赖性-DI容器或在测试中手动分配它们。当类不再负责获取其依赖项时,应使用公共接口(方法或构造函数)明确地与它们进行通信。这样,可以清楚地知道类的要求,以及它是可选的(setter)还是强制的(构造函数)。

DI容器联轴器

DI框架的核心思想之一是,托管类不应依赖于所使用的DI容器。换句话说,它应该只是一个普通的POJO,只要您将其传递给所有必需的依赖项,就可以独立地实例化。这样,您可以在单元测试中实例化它,而无需启动DI容器并单独进行测试(使用一个可以进行集成测试的容器)。如果没有容器耦合,则可以将该类用作托管或非托管类,甚至可以切换到新的DI框架。

但是,当直接注入字段时,您没有提供直接实例化具有所有必需依赖关系的类的方法。这意味着:

有一种方法(通过调用默认构造函数)在缺少某些强制协作者且使用状态会导致NullPointerException的状态下使用new创建对象。 此类不能在DI容器(测试,其他模块)之外重用,因为除了反射以外,没有其他方法可以为其提供所需的依赖关系。 不变性 与构造函数不同,字段注入不能用于将依赖项分配给最终字段,从而有效地使您的对象可变。

答案 4 :(得分:3)

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

  1. 通过构造函数
  2. 通过设置器或其他方法
  3. 通过反射,直接进入视野

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

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