在 Spring 的依赖注入中,使用 @Autowired 与 @Resource 变量注入与构造方法注入、Setter 注入之间有什么区别呢?

一、前言

在 Controller 层注入 Service 层的依赖时,我们通常会使用 @Autowired 自动注入,例如:

@Controller
public class AnimalController {
    @Autowired
    private DogService dogService;
}

这时 IntelliJ IDEA 通常会显示 Field injection is not recommended 的警告:

filedinjectionwarn.png

可以看出,官方不推荐使用 Field 进行注解,而推荐使用构造器或 Setter 的方式进行注解。

另外,当使用 @Resource 进行变量注入时,并不会有警告,例如:

@Resource
private UserService userService;

那么,使用 @Autowired 与 @Resource 变量注入与构造方法注入、Setter 注入之间有什么区别呢?

二、Field 变量注入

注解 @Autowired 与 @Resource 的效果差不多,主要的区别是: @Autowired 注解是 Spring 中的,默认按类型注入;@Resource 注解是 Java EE 中自带的,默认按名称注入的。这里主要谈的是 Spring 中的 @Autowired 注解。

基于 Filed 变量注入的示例:

@Controller
public class AnimalController {
     @Autowired
     private DogService dogService;

     @Autowired
     private CatService catService;
}

(1) 优点:

  • 使用简单,只需要把 @Autowired 放到待注入依赖的变量之上就行了。新增 Field 时也不用修改什么代码,方便维护。
  • 减少了大量冗余代码,使代码看起来简洁明了,可读性高。

(2) 缺点:

  • 当注入的对象依赖其它的对象,而被依赖的对象没被创建的话,就容易引起空指针异常。
  • 类与 DI 容器高度耦合,类不通过反射不能被实例化,需要用 DI 容器去实例化它,所以不能在外部使用或重用它。(另一方面,这对于单元测试也很不友好,更像集成测试)
  • 不能将对象标记为 final 的,该属性实例化后可能被修改。从理论上讲,该类可以在实例化注入的属性后,对其进行修改。
  • 可能会导致循环依赖的问题,即 A 里面注入 B,B 里面又注入 A。

三、Setter 注入

基于 Setter 方法注入的示例:

@Controller
public class AnimalController {
    private DogService dogService;

    private CatService catService;

   @Autowired
    public void setDogService(DogService dogService) {
        this.dogService = dogService;
    }

    @Autowired
    public void setCatService(CatService catService) {
        this.catService = catService;
    }
}

优点:

  • 当待注入的属性过多时,使用构造方法显得很笨重,Setter 方法更轻便。
  • 方便让类在实例化之后重新对该属性进行配置或注入。

缺点: Setter 方法过多,会导致代码冗余,且维护起来很麻烦。

注:在 Spring 3.x 刚推出的时候,官方推荐使用的注入方式就是这种。但在 Spring 4.x 的时候,官方换成推荐使用构造方法注入了。

四、构造方法注入

基于构造方法注入的示例:

@Controller
public class AnimalController {
    private final DogService dogService;

    private final CatService catService;

    @Autowired
    public AnimalController(DogService dogService, CatService catService) {
        this.dogService = dogService;
        this.catService = catService;
    }
}

结合 Spring 官方的推荐理由,使用构造方法注入的优点如下:

  1. 依赖不可变:可以加入 final 来约束 Field,防止属性在实例化后被修改。也叫做保证注入的组件不可变。
  2. 依赖不为空:在实例化的时候会检查构造方法参数是否为空,如果为空(未找到该类型的实例对象)则会抛出异常。
  3. 单一职责原则:当使用构造方法注入时,如果参数过多,你会发现当前类的职责过大,需要进行拆分。而使用 Field 注入时,你并不会意识到此问题。
  4. 更利于单元测试:按照其他两种方式注入,在进行单元测试时需要初始化整个 Spring 的环境,而采用构造方法注入时,只需要初始化需要的类即可,即可以直接实例化需要的类。
  5. 避免循环依赖:若使用构造方法注入,Spring 在项目启动的时候会检测循环依赖,抛出 BeanCurrentlyInCreationException 异常。
  6. 保证返回客户端(调用方)的代码的时候是完全初始化的状态。

五、总结

综上,这里推荐优先使用构造方法进行注入,然后结合 Filed 变量注入和 Setter 方法注入的优点,搭配使用。

参考:


(完)

评论