一、简单说明

@Autowire、@Qualifier、@Resource和@Inject这四个标签都可以在Spring中通过字段(Field injection)或setter完成依赖对象的注入(DI)工作。只不过四个标签在所属阵营和对注入内容所起的作用各有不同。

首先从标签所属阵营来看:

@Resource和@Injection属于Java的注解序列,位于扩展包javax.annotation。而@Autowire和@Qualifier这两个标签是隶属于Spring的。

接下来具体看一下几个标签的用法

二 @Resource注解

@Resource基于JSR-250标准,属于J2EE扩展包。

该注解对于依赖内容注入的判别步骤是:

  1. by Name 匹配
  2. by Type 匹配
  3. by Qualifier 匹配

无论@Resource是添加在filed还是setter,判定注入内容时均采用相同的判别步骤。

@Resource(name="namedFile")
private File defaultFile;

这是一个添加在Field上的@Resource注解,@Resource的name属性"nameFile"。

当@Resource显式指定name属性后,只能按照名称(byName)装配。换句话说注入defaultFile的Bean只能是配置类中@Bean(name="namedFile")或者Bean配置文件中id为namedFile的:

@Configuration
public class ApplicationContextTestResourceNameType {

    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

如果在上下文中无法通过名字匹配到相应的Bean,会抛出org.springframework.beans.factory.NoSuchBeanDefinitionException异常。

public class MethodResourceInjectionIntegrationTest {

    private File defaultFile;

    @Resource(name="namedFile")
    protected void setDefaultFile(File defaultFile) {
        this.defaultFile = defaultFile;
    }

}

 这是一个添加在setter的@Resource注解。@Resource的name属性被赋值为nameFile,而@Resource显式指定name属性后,只能按照名称(byName)装配。如果无法在上下文中通过名字匹配到相应的Bean,同样会抛出org.springframework.beans.factory.NoSuchBeanDefinitionException异常。

如果未显示指定@Resource注解的name属性,则name属性会使用默认值,默认值规则是:

1. 当注解使用在字段(Field)上时,默认值是字段的名称。

@Resource //未指定name,默认name取将要被注入的字段名,即student 
private Student student;

2. 当注解使用在setter方法上时,默认值是属性的名称。

@Resource //setStudent说明属性的名称是student,也就是此时@Resource的默认name值
public void setStudent(Student student) {
    this.student = student;
}

这时依然优先按照名称(byName)的默认值进行装配。但如果没有匹配内容时,会改为按照类型(byType)装配。

例如,现在的bean配置文件是

<bean id="student1" class="com.piglite.mydomain.Student">
        <property name="name" value="TOM"></property>
        <property name="age" value="23"></property>
</bean>

byName的话,没有bean的id是student,但是byType可以匹配,所以属性student依然可以被注入成功。


虽然@Resource和@Qualifier注解属于不同的阵营,但是在Spring中这两个标签也可以配合使用。@Qualifier标签可以通过添加“条件”,从众多可以匹配的内容中遴选出最合适的进行装配。

假如现在的配置类是:

@Configuration
public class ApplicationContextTestResourceQualifier {

    @Bean(name="defaultFile")
    public File defaultFile() {
        File defaultFile = new File("defaultFile.txt");
        return defaultFile;
    }

    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

现在需要注入的内容是:

@Resource
private File dependency1;
	
@Resource
private File dependency2;

此时第一个@Resource的默认name是dependency1,但是没有符合要求的Bean,于是开始按类型匹配,然而配置类中defaultFile和namedFile均是可注入的File类型。这下Spring不知道该选择谁作为dependency1的注入内容,于是只能抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常。

这时就需要使用@Qualifier标签来添加额外的说明,以便从多个可匹配的内容中遴选出最合适的。

@Resource
@Qualifier("defaultFile")
private File dependency1;

@Resource
@Qualifier("namedFile")
private File dependency2;

@Qualifier注解清除了所有注入时可能引起模糊的障碍。

同样,@Resource与@Qualifier也可以应用于setter:

@Resource
@Qualifier("namedFile")
public void setArbDependency(File arbDependency) {
    this.arbDependency = arbDependency;
}

@Resource
@Qualifier("defaultFile")
public void setAnotherArbDependency(File anotherArbDependency) {
    this.anotherArbDependency = anotherArbDependency;
}

再结合配置类中的定义,可以很清晰的判定出依赖项需要注入的内容:

arbDependency注入File("namedFile.txt"),而anotherArbDependency注入File("defaultFile.txt")。

三、@Inject注解

@Inject 注释属于 JSR-330 注释集合。为了正常访问@Inject 注释,必须将 javax.inject 库声明为 Gradle 或 Maven 依赖项。

此注解的匹配路径,按优先级列出:

  1. by Type 匹配
  2. by Qualifier 匹配
  3. by Name 匹配

无论是将@Inject注解应用于Field还是setter,都是遵循一致的匹配路径。

假设现在有一个可以被装配的组件

@Component
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

然后是应用于Filed的@Inject注解:

@Inject
private ArbitraryDependency fieldInjectDependency;

@Inject 注释的默认行为是按类型解析依赖项,这就意味着即使依赖项的名称与组件名称不同,只要在应用程序上下文中类型匹配,依赖项就可以被注入。

使用配置类作为注入内容也是可以的:

@Bean
public ArbitraryDependency injectDependency() {
    ArbitraryDependency injectDependency = new ArbitraryDependency();
    return injectDependency;
}

在@Inject时可能遇到的难题是“如果某个依赖项的类型有多个实现,该选取谁作为依赖项的注入内容呢?”

假设ArbitraryDependency现在有一个子类:

@Component
public class AnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

而现在的依赖项是:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

显然现在的窘境是@Inject标签无法解决的,只能再次抛出NoUniqueBeanDefinitionException异常。我们必须提供更多的线索才能解决注入适当内容的问题。

此时可以再次使用@Qualifier注解,解决从多个可匹配内容中遴选出最合适的问题。将@Qualifer与 @Inject 注释一起使用,这就是代码块现在的样子:

@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;

@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;

接下来我们再为ArbitraryDependency提供一个子类:

public class YetAnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Yet Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

@Inject还可以与@Name配合,声明需要依赖的bean的名字:

@Inject
@Named("yetAnotherFieldInjectDependency")
private ArbitraryDependency yetAnotherFieldInjectDependency;

很明显,现在依赖项yetAnotherFieldInjectDependency需要类型是ArbitraryDependency而名字是yetAnotherFieldInjectDependency的bean。

配置类:

@Configuration
public class ApplicationContextTestInjectName {

    @Bean
    public ArbitraryDependency yetAnotherFieldInjectDependency() {
        ArbitraryDependency yetAnotherFieldInjectDependency =
          new YetAnotherArbitraryDependency();
        return yetAnotherFieldInjectDependency;
    }
}

显然YetAnotherArbitraryDependency对象将成为yetAnotherFieldInjectDependency依赖项的注入内容。

@Autowired

@Autowired注解的行为类似于@Inject 注解。唯一的区别是@Autowired注解是Spring框架的一部分。@Autowired注解与@Inject注解具有相同的判定路径,按优先顺序列出:

  1. by Type匹配
  2. by Qualifier匹配
  3. by Name匹配

无论是将@Autowired注解应用于Field还是setter,都遵循一致的匹配路径。

@Configuration
public class ApplicationContextTestAutowiredType {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }
}
@Autowired
private ArbitraryDependency fieldDependency;

@Autowired注解作用于依赖项fieldDependency,注入的内容只要类型是ArbitraryDependency的bean就可以。恰好配置类里面,虽然bena的名称是autowiredFieldDependency与fieldDependency风马牛不相及,但是autowiredFieldDependency的类型是ArbitraryDependency,秉着byType优先的匹配原则,一个autowiredFieldDependency返回值的对象顺利注入fieldDependency依赖项中。

基于类型匹配优先的特点,困扰@Autowired注解的情况依然会发生在“如果有多个类型的内容都满足依赖项的类型注入需求,哪一个才是适当的注入项?”

更改的配置类如下:

@Configuration
public class ApplicationContextTestAutowiredQualifier {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }

    @Bean
    public ArbitraryDependency anotherAutowiredFieldDependency() {
        ArbitraryDependency anotherAutowiredFieldDependency =
          new AnotherArbitraryDependency();
        return anotherAutowiredFieldDependency;
    }
}

修改注入依赖项:

@Autowired
private FieldDependency fieldDependency1;

@Autowired
private FieldDependency fieldDependency2;

@Autowired就不知道如何在配置类的autowiredFieldDependency与anotherAutowiredFieldDependency之间做出抉择了。此时必须有新的条件介入,才能完成注入的甄选,否则只能抛出NoUniqueBeanDefinitionException异常。

解决的方式与前面@Inject注解类似,可以引入@Qualifier增加注入内容名字作为甄选条件:

@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;

@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;

有了@Qualifier,就可以甄别适当的注入内容了。

fieldDependency1被注入autowiredFieldDependency方法的返回值,而fieldDependency2被注入anotherAutowiredFieldDependency方法的返回值。

最后看一下@Autowired如何按照名称进行匹配。@Autowired不能像@Inject注解那样与@Name注解互相配合,@Autowired注解要完成按名称匹配要与@ComponentScan注解在ApplicationContext中一起使用。

更改配置类:

@Configuration
@ComponentScan(basePackages={"com.piglite.dependency"})
public class ApplicationContextTestAutowiredName {
   
}

通过使用@ComponentScan注解,Spring框架必须在应用程序上下文中检测 com.piglite.dependency包中被@Component注解的组件类。

然后有一个组件类:

@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

@Component的value属性值 autowiredFieldDependency告诉Spring,ArbitraryDependency 类是一个名为 autowiredFieldDependency 的组件,可以用来注入。

依赖项依然是:

@Autowired
private ArbitraryDependency autowiredFieldDependency;

为了让@Autowired注解通过名称解析依赖,@Component组件的名称必须与@Autowired注解的Field的名字一致。

关于@Autowired还有一个小知识点是它的required属性。

按类型装配时,默认情况下必须要求依赖对象必须存在(不存在会报错),但是可以通过required=false属性设置非必须。

@Autowired(required = false)
private Date date ;
@Autowired
@Qualifier("birth")
private Date birthday ;

 


再来看一些关于@Qualifierr的额外“甄别”用法:

@Qualifier可以与@Component合作,充当组件的名称。

@Component
@Qualifer("autowiredFieldDependency")
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

@Qualifier可以与@Bean配合,筛选“注入”集合中的内容

@Qualifier
@Bean
public Date d1() {
  return new Date() ;
}
@Bean
public Date d2() {
  return new Date() ;
}
@Resource
private List<Date> dates = Collections.emptyList() ;

ApplicationContext启动后,dates里面会有两个条目,分别就是d1和d2。

但是现在修改一下字段dates的注解:

@Qualifier
@Bean
public Date d1() {
  return new Date() ;
}
@Bean
public Date d2() {
  return new Date() ;
}
@Resource
@Qualifier
private List<Date> dates = Collections.emptyList() ;

再次运行后,dates中只注入了一个条目,就是d1。@Qualifier起到了一个让集合只筛选加有@Qualifier注解的Bean才会被收集注入。


现在的问题是,如何选择使用哪个注释以及在什么情况下使用谁的问题。以下是一些建议:

1 通过多态性在应用程序范围内使用单例



如果设计是基于接口或抽象类实现,并且这个行为贯穿整个应用程序中,那么我们可以使用@Inject或@Autowired。

这种方法的好处是,当我们升级应用程序或应用补丁修复错误时,可以换出类,而对整体应用程序影响最小。因为此时主要的默认执行路径是按类型匹配。



2 细粒度的程序行为配置

如果设计使应用程序具有复杂的行为,每个行为都基于不同的接口/抽象类,并且每个实现的用法在应用程序中各不相同,那么我们使用@Resource。在这种情况下,主要默认执行路径是按名称匹配。


3 DI由JEE平台还是Spring平台处理



如果使用JEE平台而不是 Spring 注入所有的依赖项,则选择是在 @Resource和@Inject之间。如果要求所有依赖项都由Spring Framework 处理,那么唯一的选择是@Autowired。这些是由标签所属阵营决定的。