如何正确进行依赖注入(在 Spring 中)? [英] How to properly do dependency injection (in Spring)?

查看:27
本文介绍了如何正确进行依赖注入(在 Spring 中)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

@Resource//或 @Autowired 甚至 @Inject私有 PersonRepository personRepository;

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

personRepository.save(p);

否则我在 Spring 示例中发现,注入构造函数:

private final PersonRepository personRepository;@自动连线公共人控制器(PersonRepository personRepository){this.personRepository = personRepository;}

所以两者都正确?或者每个都有它的属性和用法?

解决方案

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

后者是正确的,这与其说是因为 Spring 或任何依赖注入容器,不如说是因为面向对象的类设计原则.

详情

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

使用场注入所有这一切都是不可能的.您没有从外部看到依赖项,您需要一些黑魔法来注入依赖项,并且您永远无法确定它们不是 null 除非您盲目相信容器.

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

容器

人们经常争辩说这只是学术上的废话,因为无论如何您都可以依赖容器.这是我的看法:

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

  • 即使是设计用于容器的类型,手动使用:在单元测试中.如果您不编写单元测试……那么那就是另一个话题了.

  • 所谓的额外构造函数的冗长(我可以通过一行字段注入实现同样的事情!!" - 不,你不能.你实际上得到东西多写一行代码.")可以通过诸如 Lombok 之类的东西来缓解.带有 Spring 和 Lombok 的构造函数注入组件如下所示:

    @Component@RequiredArgsConstructor类 MyComponent 实现了 MyComponentInterface {私有最终 @NonNull MyDependency 依赖项;…}

Lombok 将负责为每个最终字段生成一个带参数的构造函数,并在分配之前检查给定参数的 null.这样你就可以有效地获得字段注入的简洁性和构造函数注入的设计优势.

结语

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

也许我们应该为这种风格创造一个不同的术语?依赖喂养,也许?

资源

I have a doubt about injecting objects to a class using Spring. I used in my projects this kind of code:

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

then used it normally on methods as :

personRepository.save(p);

Otherwise I found on Spring examples, injecting the constructor:

private final PersonRepository personRepository;

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

So both are correct? Or each on has its properties and usages?

解决方案

tl;dr - Constructor injection is the best way of doing DI

The latter is correct and this is not so much because of Spring or any dependency injection container, but object-oriented class design principles.

Details

A type should be designed, so that you can only create instances from it, that are in a valid state. To achieve this, all mandatory dependencies of that type need to be constructor arguments. This means, these dependencies can be null-checked, assigned to final fields to promote immutability. Beyond that, when working with the code, for the caller (or creator) of that instance it's immediately obvious which dependencies it has to provide (through either skimming through the API docs or using code completion in the IDE).

All of this is impossible with field injection. You don't see the dependencies from the outside, you require some black magic to inject the dependencies and you can never be sure they're not null except you blindly trust the container.

The last but not least important aspect actually is, that with field injections, it's less painful to add a lot of dependencies to your class, which is a design problem in itself. With constructors that becomes much more painful much earlier which is a good thing as it tells you something about your class design: the class has too many responsibilities. No need to calculate metrics on it in the first place, you feel it when you try to extend it.

Containers

People often argue that that's just academic bullshit as you can rely on the container anyway. Here's my take on this:

  • Just because a container exists, it doesn't mean you have to throw all basic object-oriented design principles over board, does it? You still take a shower, even if antiperspirants exist, right?

  • Even types designed for usage with a container, will be used manually: in unit tests. If you don't write unit tests… well that's another topic then.

  • The alleged verbosity of an additional constructor ("I can achieve the same thing with a single line of field injection!!" - "No you can't. You actually get stuff from writing a line of code more.") can be mitigated by things like Lombok. A constructor injected component with Spring and Lombok looks like this:

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

Lombok will take care of generating a constructor taking a parameter for each final field and check the given parameter for null before assigning it. So you effectively get the brevity of field injection and the design advantages of constructor injection.

Epilogue

I been part of a discussion recently with some non-Java folks that I terribly puzzled by using the term "injection" for constructor DI. Effectively, they argued - and there's a lot of truth to that - that passing dependencies through a constructor is not injection at all, as it's the most natural way to hand objects into others (in stark contrast to injections of any sorts).

Maybe we should coin a different term for that kind of style? Dependency feeding, maybe?

Resources

这篇关于如何正确进行依赖注入(在 Spring 中)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆