问题

对于使用Spring框架的java开发人员对下面的代码应该很熟悉:

  1. @Autowired
  2. private HelloService helloService;

但是对于上面的代码,Sonar会提示:Remove this annotation and use constructor injection instead.
翻译成中文即:移除@Autowired注解使用构造器注入方式替代。
IntelliJ IDEA也会提示Field injection is not recommended
翻译成中文即:不推荐使用字段注入

那么他们为什么这么建议呢?

首先我们先看一下Spring有哪些注入bean的方式

  1. 构造方法注入
  2. set方法注入
  3. 字段注入,即@Autowired注解

    如何使用这些方式

    构造方法注入

    在Spring4.3版本之前,我们必须要在构造方法上加@Autowired注解;在新版本中如果当前类只有一个构造方法@Autowired注解就是可选的。
    只有一个构造方法示例:
    1. @Controller
    2. public class ValidationController {
    3. private final HelloService helloService;
    4. public ValidationController(HelloService helloService) {
    5. this.helloService = helloService;
    6. }
    7. }
    多个构造方法示例:
    1. @Controller
    2. public class ValidationController {
    3. private HelloService helloService;
    4. @Autowired
    5. public ValidationController(HelloService helloService) {
    6. this.helloService = helloService;
    7. }
    8. public ValidationController() {
    9. }
    10. }

    set方法注入

    这种方式Spring会找到 @Autowired 注解并且调用set方法来注入所需的依赖。
    1. @Controller
    2. public class ValidationController {
    3. private HelloService helloService;
    4. public HelloService getHelloService() {
    5. return helloService;
    6. }
    7. @Autowired
    8. public void setHelloService(HelloService helloService) {
    9. this.helloService = helloService;
    10. }
    11. }

    字段注入

    通过基于字段的注入,Spring在使用@Autowired注释进行注释时,直接将所需的依赖项分配给字段。
    1. @Controller
    2. public class ValidationController {
    3. @Autowired
    4. private HelloService helloService;
    5. }

    这些方式有什么优缺点

    既然要移除@Autowired注解使用构造器注入方式替代,那么我们主要讨一下这些方式的优缺点。

    字段注入方式的优点

    相比较另外两种方式,字段注入方式的代码量更少、更整齐、更简洁

    构造方法注入的优点

    容易发现代码的坏味道

    set方法注入和字段注入会间接违反单一职责原则
    因为在一个类依赖很多其他类的时候,如果使用构造方法注入就会发现构造方法的参数太多,这会让开发人员反思这个类真的需要这么多依赖吗?当前类是不是职责过多?
    而使用字段注入时,就会把一些例如sonar的提示屏蔽掉,让开发人员误以为这样做没有问题

    可以创建不可变类

    在使用构造方法注入时因为构造方法是创建依赖对象的唯一方式,这非常有助于让我们创建不可变的对象。
    想象一下创建一个bean之后你可以通过set方法随意修改此类的依赖,在出现问题时是很难定位的。
    @Autowired的源码有一段注释如下:Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public.
    大意是使用@Autowired注解时,bean是在构造当前的bean之后,并且在任何的其他方法调用之前注入,因此无法设置成final类型的字段。

    更明显的声明所有的依赖

    使用构造方法注入,在使用这个类时就会暴露给使用者说我要依赖构造方法中的类。
    但是使用字段注入时,使用者其实并不知道这个类依赖了哪些类,除非我到此类中查看这个类有多少个字段是有@Autowired注解。

    不方便迁移

    spring实现了DI(控制反转),但并非是DI本身;
    使用构造方法注入时,除了在类上面有@Service@Component等的注解,没有其他的Spring相关的更多的注解。
    使用字段注入时,除了在类上面有@Service@Component等的注解之外又使用了Spring的@Autowired注解,如果把此类迁移到其他没有spring的环境时是完成不了注入的。

    不方便测试

    在使用构造方法注入时,单元测试时开发人员可以直接传入一个mock的类或者其他的任何被测试类依赖的子类;
    当然我们也可以使用set方式注入一个mock的类,但是如果代码修改了新增了一个依赖,那么我们很容易忘掉在测试代码中set新增的依赖,直到运行的时候我们才会看到可能有NPE异常爆出;但是构造方法就不必有这种烦恼,因为如果新增了一个依赖,测试方法会马上编译不通过。
    使用字段注入,必须依赖Spring去帮助注入依赖的类

    总结

    通过构造方法注入bean是我们更容易创建不可变类,代码更健壮、更具有可测试性、更容易避免NPE。