SpringBoot中为什么不推荐字段依赖自动注入?


当我们在IDE中运行代码分析工具时,它可能会针对带有@Autowired注解的字段发出“不推荐字段注入”的警告。
在本教程中,我们将探讨为什么不推荐使用字段注入以及我们可以使用哪些替代方法。

对象使用它们的依赖对象而不需要定义或创建它们的过程称为依赖注入。它是 Spring 框架的核心功能之一。

我们可以通过三种方式注入依赖对象,使用:

  • 构造函数注入
  • Setter方法注入
  • 字段注入

这里的第三种方法涉及使用@Autowired注释将依赖项直接注入到类中。尽管这可能是最简单的方法,但我们必须了解它可能会导致潜在的问题。

更重要的是,即使是官方的 Spring 文档也不再提供字段注入作为 DI 选项之一。


1、如果依赖关系没有被正确初始化,字段注入会产生NullPointerException的风险。

让我们定义EmailService类,并使用字段注入添加EmailValidator的依赖关系:

@Service
public class EmailService {

    @Autowired
    private EmailValidator emailValidator;
}

public void process(String email) {
    if(!emailValidator.isValid(email)){
        throw new IllegalArgumentException(INVALID_EMAIL);
    }
    // ...
}

只有当我们提供EmailValidator的依赖关系时,EmailService才能正常工作。然而,使用字段注入,我们没有提供一个直接的方式来实例化具有所需依赖关系的EmailService。

此外,我们可以使用默认的构造函数来创建EmailService实例:

EmailService emailService = new EmailService();
emailService.process("test@baeldung.com");

执行上面的代码会导致NullPointerException,因为我们没有提供其必须的依赖,即EmailValidator。

现在,我们可以使用构造函数注入来减少NullPointerException的风险:

private final EmailValidator emailValidator;

public EmailService(final EmailValidator emailValidator) {
   this.emailValidator = emailValidator;
}

通过这种方法,我们公开了所需的依赖性。此外,我们现在要求客户提供必须的依赖关系。换句话说,如果不提供EmailValidator实例,就不可能创建一个新的EmailService实例。

2、使用字段注入,我们无法创建不可变的类。

我们需要在声明最终字段时或通过构造函数将其实例化。此外,一旦构造函数被调用,Spring就会执行自动布线。因此,使用字段注入不可能自动连接最终字段。

由于依赖关系是可变的,我们没有办法确保它们在被初始化后保持不变。此外,在运行应用程序时,重新分配非最终字段会引起意想不到的副作用。

另外,我们可以对强制性依赖使用构造函数注入,对可选依赖使用设置函数注入。这样,我们可以确保所需的依赖关系保持不变。

3、违反单一责任
作为SOLID原则的一部分,单一责任原则指出每个类应该只有一个责任。换句话说,一个类应该只对一个行动负责,因此,只有一个理由可以改变。

当我们使用字段注入时,我们可能最终违反了单一责任原则。我们可以很容易地添加更多的依赖关系,并创建一个做了不止一个工作的类。

另一方面,如果我们使用构造函数注入,我们会注意到如果一个构造函数有超过几个依赖关系,我们可能会有一个设计问题。此外,如果构造函数中有七个以上的参数,甚至IDE也会发出警告。

4.循环依赖
简单地说,当两个或多个类相互依赖时,就会出现循环依赖。由于这些依赖关系,不可能构建对象,执行过程中可能会出现运行时错误或无限循环。

使用字段注入可以导致循环依赖不被注意:

@Component
public class DependencyA {

   @Autowired
   private DependencyB dependencyB;
}

@Component
public class DependencyB {

   @Autowired
   private DependencyA dependencyA;
}

由于依赖关系是在需要时注入的,而不是在上下文加载时注入的,所以Spring不会抛出BeanCurrentlyInCreationException。

通过构造函数注入,可以在编译时检测到循环依赖,因为它们会产生无法解决的错误。

此外,如果我们的代码中有循环依赖,这可能是我们的设计出了问题的信号。因此,如果可能的话,我们应该考虑重新设计我们的应用程序。

不过,自从Spring Boot 2.6.版本以来,默认情况下不再允许循环依赖。