究竟啥是现场注入以及如何避免它?

Posted

技术标签:

【中文标题】究竟啥是现场注入以及如何避免它?【英文标题】:What exactly is Field Injection and how to avoid it?究竟什么是现场注入以及如何避免它? 【发布时间】:2017-02-14 21:28:58 【问题描述】:

我在一些关于 Spring MVC 和 Portlets 的帖子中读到,不推荐字段注入。据我了解,字段注入是当你像这样用@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 的重复,所以我检查了它。因为在问题和答案中都没有任何代码示例,我不清楚我的猜测是否正确,我正在使用哪种注入类型。

【问题讨论】:

如果字段注入如您描述的那么糟糕,为什么 Spring 允许它?字段注入有其自身的好处,使代码更具可读性和更少冗长。如果您在编码方面有足够的纪律,那么即使您使用字段注入,也可以确保事情不会中断。 @ashes 因为它在当时是一个简洁的功能,其含义并没有被完全考虑清楚。与Date(int,int,int) 存在的原因相同。 【参考方案1】:

注入类型

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

    通过构造函数 通过设置器或其他方法 通过反射,直接进入字段

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


注射指南

一般准则 which is recommended by Spring(参见 Constructor-based DI 或 Setter-based DI 的部分)如下:

对于强制依赖或以不变性为目标时,请使用构造函数注入 对于可选或可变依赖项,使用 setter 注入 在大多数情况下避免现场注入

字段注入的缺点

字段注入不被认可的原因如下:

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

结论

根据您的需要,您应该主要使用构造函数注入或构造函数和设置器注入的某种组合。现场注入有很多缺点,应该避免。字段注入唯一的好处就是写起来更方便,也不算坏处。


进一步阅读

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

【讨论】:

告诉世界“应该避免现场注入”一般不是好主意,也不好。展示利弊,让其他人自己决定;)许多人有其他的经历和自己的看待事物的方式。 这里可能就是这种情况,但在其他情况下,社区已经达成普遍共识来阻止某些事情。以匈牙利表示法为例。 您在可测试性和依赖可见性方面给出了一些优点,但我并不完全同意。构造函数注入没有缺点?可能需要在类中注入 5 或 6 个字段来执行真正的调用组合。我也不同意你的不变性。拥有 final 字段并不是必须拥有一个不可变的类。这是优选的。这是非常不同的。 我认为您的意思是“对于强制依赖或针对不变性 我指的是答案开头的链接,它链接到 spring 文档【参考方案2】:

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

构造函数注入

优点:

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

缺点:

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

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

【讨论】:

可测试性,是的,模拟现场注入的 bean 对我来说是一场噩梦。曾经,我使用了构造函数注入,我不需要做任何不必要的模拟【参考方案3】:

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

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

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

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

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


编辑 #1: Vojtech Ruzicka 说:

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

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

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

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

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

【讨论】:

非常丑陋的带有 8 个依赖项的构造函数实际上很棒,因为它是一个危险信号,表明有问题,类有太多的依赖项,可能违反了单一职责原则,应该重构。这其实是件好事。 @VojtechRuzicka 这肯定不好,但有时你无法避免。 我会说经验法则 3,更不用说 40-50,任何类的依赖关系都应该表明您需要重构。具有 40 个依赖项的类不可能坚持单一职责主体或打开/关闭主体。 @AminJ 规则很好,但现实不同。我工作的公司已有 20 多年的历史,我们有很多遗留代码。重构是个好主意,但要花钱。另外我不知道为什么这么说,但我的意思不是 40-50 个依赖项,我的意思是 40-50 个 bean、组件、模块...... @dit,您的情况显然是技术债务导致您做出次优选择的情况。用你自己的话来说,你的决策会受到 20 多年前的遗留代码的显着影响。在开始一个新项目时,您仍然会推荐字段注入而不是构造函数注入吗?也许你应该在你的答案中加上一个警告,以表明在哪些情况下你会选择现场注入。【参考方案4】:

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

    通过构造函数 通过setter或其他方法 通过反射,直接进入场

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

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

【讨论】:

您能否澄清一下所有三种注入方法都使用反射?我为构造函数注入设置了一个断点,但没有发现任何类似反射的东西。 :13, Bleh (com.wujq.cameldemo) newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect) newInstance:62, NativeConstructorAccessorImpl (sun.reflect) newInstance:45, DelegatingConstructorAccessorImpl (sun .reflect) newInstance:423, Constructor (java.lang.reflect) 这是来自构造函数类型注入 bean 的调用堆栈 - topn 条目。

以上是关于究竟啥是现场注入以及如何避免它?的主要内容,如果未能解决你的问题,请参考以下文章

究竟啥是 getattr() 以及如何使用它?

春季现场注入的内部工作以及为啥不建议使用

究竟啥是“WPF 服务”?

C#:啥是虚拟事件以及如何使用它们?

究竟啥是核心文件,它为啥有用?

究竟啥是 JavaBean?