如何正确地进行dependency injection(在Spring中)?

我怀疑使用Spring将对象注入到类中。 我在我的项目中使用了这种代码:

@Resource // or @Autowired even @Inject private PersonRepository personRepository; 

然后在方法上正常使用它:

 personRepository.save(p); 

否则我在Spring示例中找到了注入构造函数:

 private final PersonRepository personRepository; @Autowired public PersonController(PersonRepository personRepository) { this.personRepository = personRepository; } 

那么两个都是正确的? 或者每个都有它的属性和用法?

tl; dr – 构造函数注入是进行DI的最佳方式

后者是正确的,这不是因为Spring或任何dependency injection容器,而是面向对象的类设计原则。

细节

应该设计一个类型,以便您只能从中创建处于有效状态的实例。 为实现此目的,该类型的所有必需依赖项都需要是构造函数参数。 这意味着,这些依赖项可以为null检查,分配给最终字段以提升不变性。 除此之外,在使用代码时,对于该实例的调用者(或创建者),它立即显而易见它必须提供哪些依赖关系(通过浏览API文档或使用IDE中的代码完成)。

所有这些都不可能通过现场注入。 你没有从外部看到依赖关系,你需要一些黑魔法来注入依赖关系,你永远不能确定它们不是null除非你盲目地信任容器。

实际上,最后但并非最不重要的方面是,通过字段注入,向类中添加大量依赖项不那么痛苦,这本身就是一个设计问题。 随着构造函数变得更加痛苦,这是一件好事,因为它告诉你关于你的类设计的一些事情:这个类有太多的责任。 首先,无需计算其上的指标,当您尝试扩展它时,您会感觉到它。

集装箱

人们常常认为这只是学术废话,因为你可以依赖容器。 这是我对此的看法:

  • 仅仅因为存在一个容器,这并不意味着你必须抛弃所有基本的面向对象设计原则,是吗? 你仍然洗澡,即使存在止汗剂,对吧?

  • 甚至设计用于容器的类型也将手动使用:在unit testing中。 如果你不编写unit testing……那么那是另一个主题。

  • 所谓的额外构造函数的冗长(“我可以通过单行注入实现同样的事情!” – “不,你不能。你实际上可以通过编写一行代码获得更多东西。”)可以减轻像龙目岛这样的东西。 Spring和Lombok的构造函数注入组件如下所示:

     @Component @RequiredArgsConstructor class MyComponent implements MyComponentInterface { private final @NonNull MyDependency dependency; … } 

Lombok将负责生成一个构造函数,为每个final字段获取一个参数,并在分配之前检查给定参数是否为null 。 因此,您可以有效地获得现场注入的简洁性和构造器注入的设计优势。

结语

我最近与一些非Java人员进行了讨论,我对构造函数DI使用术语“注入”感到非常困惑。 实际上,他们认为 – 并且有很多事实 – 通过构造函数传递依赖关系根本不是注入,因为它是将对象传递给其他人的最自然的方式(与任何种类的注入形成鲜明对比)。

也许我们应该为这种风格注入不同的术语? 依赖性喂养,也许?

资源

  • Oliver Gierke – 为什么野外注射是邪恶的
  • Jens Schauder – 一种正确的dependency injection方式

从学术上讲,我同意构造函数只是创建对象的更好方法。 然而,java bean规范建立在mutators的前提下,以便于反思。 围绕这种易于访问的范例构建了太多的工具和框架。 对于服务,DAO和其他单例场景,我认为只应使用构造函数注入作为mutators打破古老的规则“只有朋友可以看到你的私有部分”。