Spring注解依赖注入详解

依赖注入可以使用 @Autowired, @Resource, @Inject 三个注解,那么这3中注解有何异同呢?

同时在Spring框架中, 当我们在使用依赖注入的时候,通常有三种方式:

  1. 通过filed变量来注入

  2. 通过setter方法来注入

  3. 通过constructor构造器来注入

那么他们有什么区别吗?应该选择哪种方式更好?

当你在使用@Autowired时,是否有出现过Field injection is not recommended的警告?你知道这是为什么吗?

Spring 依赖注入有哪几种方式?官方是怎么建议使用的呢?下面我给你详细道来!

@Autowired

@Autowired 为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired

例如: 想要注入类型为MyService的Bean

Field变量

所谓基于 field 注入,就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。

@Autowired
// @Qualifier("serviceA")
private MyService service;

这是我平常开发中看的最多也是最熟悉的一种方式,同时,也正是 Spring 团队所不推荐的方式。

好处:

这种方式非常的简洁,代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。

坏处:

基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。

  • 容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。
  • 依赖注入与容器本身耦合 依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。这个问题具体可以表现在:你的类和依赖容器强耦合,不能在容器外使用你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试
  • 不能使用属性注入的方式构建不可变对象(final 修饰的变量)

Setter方法

通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。

private MyService service;
   
@Autowired
// @Qualifier("serviceA")
public void setService(MyService service) {
    this.service = service;
}

提示: 在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。

Constructor构造器

将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。

private final MyService service;

@Autowired
public MyController(@Qualifier("serviceA") MyService service) {
  this.service = service;
}  

提示: 在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。

装配顺序:

  1. 按照类型(type)在上下文中查找匹配的bean查找type为MyService的bean
  2. 如果有多个bean,则按照name进行匹配如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配查找name为 serviceA 的bean,如果没有,则按照变量名进行匹配查找name为 service 的bean
  3. 匹配不到,则报错。(@Autowired(required=false),如果设置required为false(默认为true),则注入失败时不会抛出异常)

三种方式的区别:

  1. 基于constructor的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;

  2. 基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于constructor注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或无参数构造函数。

  3. 相信很多同学都选择使用直接在成员变量上写上注解来注入,正如我们所见,这种方式看起来非常好,精短,可读性高,不需要多余的代码,也方便维护;

三种方式各自缺点:

  1. 当我们利用constructor来注入的时候,假如我们需要注入的对象特别多的时候,我们的构造器就会显得非常的冗余、不好看;

  2. 当我们选择setter方法来注入的时候,我们不能将对象设为final的;

  3. 当我们在field变量上来实现注入的时候

    • 容易出现循环依赖
    • 这样不符合JavaBean的规范,如果bean没有autowired成功,运行时直接调用该bean的方法,会报空指针
    • final或者static对象无法注入,同时也不能将对象标为final的
    • 类与IoC容器高度耦合,我们不能在外部使用它;高度依赖Spring容器,如果换了其他容器,比如:Jfinal等框架,则运行不了
    • 类不通过反射不能被实例化(例如单元测试中),你需要用IoC容器去实例化它;

Field injection is not recommended

在使用IDE开发工具进行Spring开发,当你在字段上面使用@Autowired注解的时候,你会发现会有警告提示:

Field injection is not recommended
不建议使用基于 field 的注入方式。

Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".
Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。

提示: Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着代码出现了垃圾,这个类可能承担了过多的责任,需要重构

基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。

@Resource

@Resource是JSR-250定义的注解。Spring 在CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource。

@Resource 有两个重要的属性:nametype,而Spring 将 @Resource 注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

装配顺序:

  1. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

  2. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

  3. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。

  4. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

@Inject

在Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用 AutowiredAnnotationBeanPostProcessor来处理的。

@Inject是 JSR-330 定义的规范,如果使用这种方式,切换到Guice(Guice 是 google 开源的轻量级 IoC 框架)也是可以的

如果要说两个的区别,首先 @Inject 是Java EE包里的,在SE环境需要单独引入。另一个区别在于 @Autowired 可以设置required=false@Inject 并没有这个属性。

你可能感兴趣的:(Spring,spring,java,单元测试)