为什么Spring和IDEA都不推荐使用 @Autowired 注解

533 阅读3分钟

为什么Spring和IDEA都不推荐使用 @Autowired 注解

一、前言

  • Spring 为什么不推荐使用@Autowired注解?
  • 为什么推荐使用@Resource代替@Autowired注解?
  • 如何快速使用构造注入代替@Autowired注解?
  • @Autowired@Resource@Qualifier有何区别?

二、背景

idea中经常使用@Autowired注解,但会有一个警告Field injection is not recommended:不推荐使用属性注入。

三、原因

为什么 Spring 建议我们在Bean中使用构造注入呢? 想要回答这个问题, 我们需要了解 Spring的依赖注入(DI)方式

Spring常用的注入方式有: 简单类型注入, 集合类型注入, 域属性自动注入, 自动注入的类别, 空值注入, 构造注入 可以简化为: 属性注入, 构造方法注入, set 方法注入

1. 属性注入

@RestController
public class AppointmentNumberConfigurationController {

    @Autowired
    private AppointmentNumberConfigurationService numberConfigurationService;
}

2. set方法注入

@RestController
public class AppointmentNumberConfigurationController {


    private AppointmentNumberConfigurationService numberConfigurationService;

    @Autowired
    public void setNumberConfigurationService(AppointmentNumberConfigurationService numberConfigurationService) {
        this.numberConfigurationService = numberConfigurationService;
}

3. 构造方法注入

Constructor Injection是构造器注入,是我们最为推荐的一种使用方式。 但是, 每次注入都按照这样的流程去构造注入的话, 会显得比较麻烦.

@RestController
public class AppointmentNumberConfigurationController {

    final AppointmentNumberConfigurationService numberConfigurationService;
    
      public AppointmentNumberConfigurationController(AppointmentNumberConfigurationService numberConfigurationService) {
          this.numberConfigurationService = numberConfigurationService;
      }
    }

四、方式对比

注入方式可靠性可维护性可测试性灵活性循环关系的检测性能
Field Injection不可靠很灵活不检测启动快
Constructor Injection可靠不灵活自动检测启动慢
Setter Injection不可靠很灵活不检测启动快

使用属性注入可能会出现的问题

基于属性注入的方式, 违背单一职责原则 因为现在的业务一般都会使用很多依赖, 但拥有太多的依赖通常意味着承担更多的责任,而这显然违背了单一职责原则. 并且类和依赖容器强耦合,不能在容器外使用。

基于属性注入的方式, 容易导致Spring 初始化失败 因为现在在Spring特别是Spring Boot使用中, 经常会因为初始化的时候, 由于属性在被注入前就引用而导致npe(空指针错误), 进而导致容器初始化失败(类似下面代码块). Java 在初始化一个类时, 是按照 静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。 所以在执行这个类的构造方法时,person 对象尚未被注入,它的值还是 null。

通过@Autowired 注入, 又因为是 ByType 注入, 因此有可能会出现两个相同的类型bean 如下代码快, 就会产生两个相同的Bean, 进而导致Spring 装配失败

五、解决

如果一定要使用属性注入, 可以使用 @Resource 代替 @Autowired 注解 @Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。 如果我们想使用按照名称byName来装配,可以结合@Qualifier注解一起使用。

如果可能的话, 尽量使用构造注入 Lombok提供了一个注解@RequiredArgsConstructor, 可以方便我们快速进行构造注入, 例如:

@RestController
@RequiredArgsConstructor
public class AppointmentNumberConfigurationController {
    
    final AppointmentNumberConfigurationService numberConfigurationService;
    }

同时需要注意:

使用@RequiredArgsConstructor注解需要导入Lombok 包 或者安装lombok 插件

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.16</version>
  </dependency>

必须声明的变量为final

根据构造器注入的,相当于当容器调用带有一组参数的类构造函数时,基于构造函数的 DI 就完成了, 其中每个参数代表一个对其他类的依赖。基于构造方法为属性赋值,容器通过调用类的构造方法将其进行依赖注入

六、思考

为什么推荐使用@Resource,不推荐使用@Autowired

通过对问题1 的梳理, 我们可以知道. 因为@Autowired 注解在Bean 注入的时候是基于ByType, 因此会由于注入两个相同类型的Bean导致装配失败

@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。 如果我们想使用按照名称byName来装配,可以结合@Qualifier注解一起使用。