当可以确定一个主要的候选者时,@Primary 是按类型使用自动布线的一种有效方式,有几个实例。当你需要对选择过程进行更多控制时,你可以使用 Spring 的 @Qualifier注解。你可以将限定符的值与特定的参数联系起来,缩小类型匹配的范围,从而为每个参数选择一个特定的 bean。在最简单的情况下,这可以是一个普通的描述性值,如下面的例子所示:

  1. public class MovieRecommender {
  2. @Autowired
  3. @Qualifier("main")
  4. private MovieCatalog movieCatalog;
  5. // ...
  6. }

您还可以在单个构造函数参数或方法参数上指定 @Qualifier 注释,如下面的示例所示:

  1. public class MovieRecommender {
  2. private MovieCatalog movieCatalog;
  3. private CustomerPreferenceDao customerPreferenceDao;
  4. @Autowired
  5. public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
  6. CustomerPreferenceDao customerPreferenceDao) {
  7. this.movieCatalog = movieCatalog;
  8. this.customerPreferenceDao = customerPreferenceDao;
  9. }
  10. // ...
  11. }

下面的示例显示相应的 bean 定义:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. https://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:annotation-config/>
  10. <bean class="example.SimpleMovieCatalog">
  11. <qualifier value="main"/>
  12. <!-- inject any dependencies required by this bean -->
  13. </bean>
  14. <bean class="example.SimpleMovieCatalog">
  15. <qualifier value="action"/>
  16. <!-- inject any dependencies required by this bean -->
  17. </bean>
  18. <bean id="movieRecommender" class="example.MovieRecommender"/>
  19. </beans>

对于回退匹配,Bean 的名字被认为是默认的限定符值。因此,你可以用 main 的 id 来定义 Bean,而不是嵌套的限定符元素,导致同样的匹配结果。然而,尽管你可以使用这个约定来引用特定的 Bean 的名字,但 @Autowired从根本上说是关于类型驱动的注入,并带有可选的语义限定词。这意味着限定符的值,即使有 Bean 名称的回退,也总是在类型匹配的集合中具有缩小的语义。它们在语义上并不表达对唯一Bean ID 的引用。好的限定符值是 main 或 EMEA 或 persistent,表达了独立于 Bean ID 的特定组件的特征,在匿名 Bean 定义的情况下,如前面的例子中,它可能是自动生成的。

如前所述,限定符也适用于类型化的集合—例如,适用于 Set<MovieCatalog>。在这种情况下,所有匹配的 bean ,根据声明的限定词,被作为一个集合注入。这意味着限定词不一定是唯一的。相反,它们构成过滤标准。例如,你可以用相同的限定词值 「action 」来定义多个 MovieCatalog Bean,所有这些都被注入到一个用 @Qualifier("action")注解的 Set<MovieCatalog>中。

:::info 在类型匹配候选者中,让限定符值针对目标 Bean 名称进行选择,不需要在注入点上使用 @Qualifier 注释。如果没有其他解决指标(如限定符或主要标记),对于非唯一的依赖情况,Spring 会将注入点名称(即字段名或参数名)与目标 Bean 名称进行匹配,并选择同名的候选者(如果有)。 :::

也就是说,如果你打算通过名字来表达注解驱动的注入,请不要主要使用 @Autowired,即使它能够在类型匹配的候选者中通过 bean 的名字来选择。相反,使用 JSR-250@Resource注解,它在语义上被定义为通过其唯一的名称来识别特定的目标组件,而声明的类型与匹配过程无关。@Autowired具有相当不同的语义。在通过类型选择候选 Bean 后,指定的 String 限定符值只在这些类型选择的候选中被考虑(例如,将账户限定符与标有相同限定符标签的 Bean 匹配)。

对于那些本身被定义为集合、Map 或数组类型的 Bean,@Resource是一个很好的解决方案,它通过唯一的名称来引用特定的集合或数组 Bean。也就是说,从 4.3 版本开始,你也可以通过 Spring 的@Autowired类型匹配算法来匹配集合、Map 和数组类型,只要在 @Bean返回类型签名或集合继承层次中保留元素类型信息。在这种情况下,你可以使用限定值在相同类型的集合中进行选择,如上一段所述。

从 4.3 版开始,@Autowired也考虑到了用于注入的自我引用(也就是对当前注入的 Bean 的引用)。请注意,自我注入是一种回退。对其他组件的常规依赖总是具有优先权。在这个意义上,自我引用不参与常规的候选选择,因此特别是永远不会是主要的。相反,他们总是以最低的优先级结束。在实践中,你应该把自引用作为最后的手段(例如,通过 Bean 的事务性代理调用同一实例上的其他方法)。在这种情况下,可以考虑将受影响的方法分解到一个单独的委托 Bean 中。另外,你也可以使用 @Resource,它可以通过唯一的名称获得一个回到当前 Bean的代理。

:::info 简单说:比如在一个 service 类中有一个方法是 aop 方法 B,然而你在这个 service 方法 A 中直接通过 this.B() 这样不会触发 aop 的作用,可以通过 @Resource注入当前这个 service,再通过这个 注入
的 service 调用方法 B :::

:::tips 试图在同一个配置类上注入 @Bean方法的结果,实际上也是一种自我引用的情况。要么在实际需要的方法签名中懒加载地解决这种引用(而不是配置类中的自动连接字段),要么将受影响的 @Bean方法声明为静态,将它们与包含的配置类实例及其生命周期解耦。否则,这些 Bean 只在后备阶段被考虑,其他配置类上的匹配 Bean 将被选为主要候选者(如果有的话)。 :::

@Autowired 适用于字段、构造函数和多参数方法,允许在参数级别上通过限定符注释来缩小范围。相比之下,@Resource只支持字段和只有一个参数的 bean 属性 setter 方法。因此,如果你的注入目标是构造函数或多参数方法,你应该坚持使用限定符。

你可以创建你自己的自定义限定符注解。要做到这一点,请定义一个注解,并在你的定义中提供@Qualifier注解,如下面的例子所示。

  1. @Target({ElementType.FIELD, ElementType.PARAMETER})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Qualifier
  4. public @interface Genre {
  5. String value();
  6. }

然后你可以在自动连接字段和参数上提供自定义限定符,如下面的例子所示:

  1. public class MovieRecommender {
  2. @Autowired
  3. @Genre("Action")
  4. private MovieCatalog actionCatalog;
  5. private MovieCatalog comedyCatalog;
  6. @Autowired
  7. public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
  8. this.comedyCatalog = comedyCatalog;
  9. }
  10. // ...
  11. }

接下来,你可以提供候选 Bean 定义的信息。你可以添加 <qualifier/>标签作为 <bean/>标签的子元素,然后指定类型和值来匹配你的自定义限定符注释。类型是与注解的全限定类名相匹配的。另外,作为一种方便,如果不存在名称冲突的风险,你可以使用简短的类名。下面的例子演示了这两种方法。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. https://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:annotation-config/>
  10. <bean class="example.SimpleMovieCatalog">
  11. <qualifier type="Genre" value="Action"/>
  12. <!-- inject any dependencies required by this bean -->
  13. </bean>
  14. <bean class="example.SimpleMovieCatalog">
  15. <qualifier type="example.Genre" value="Comedy"/>
  16. <!-- inject any dependencies required by this bean -->
  17. </bean>
  18. <bean id="movieRecommender" class="example.MovieRecommender"/>
  19. </beans>

:::tips 注意上面的 bean 声明:

  1. SimpleMovieCatalog 没有主动声明 ID,他的父类是 MovieCatalog

    不然在 MovieRecommender 类中就无法使用 MovieCatalog 来接收了

  2. SimpleMovieCatalog 中有使用 qualifier 写上信息

  3. MovieRecommender 中的 @Genre 注解指定了各自的值

表现出来的效果是:

  1. SimpleMovieCatalog 会生成 2 个实例,每个实例的 bean 名称像这样 example.SimpleMovieCatalog#0、example.SimpleMovieCatalog#1
  2. 由于上面 bean 的定义,qualifier 和自动注入的地方就能对应上了 :::

Classpath Scanning 和 Managed Components 中,您可以看到一种基于注释的替代方法,可以在 XML 中提供限定符元数据。具体来说,请参见使用注解提供限定符元数据

在某些情况下,使用没有值的注解可能就足够了。当注解服务于一个更通用的目的并且可以应用于几个不同类型的依赖关系时,这可能是有用的。例如,你可以提供一个离线目录,在没有互联网连接的情况下可以进行搜索。首先,定义简单的注解,如下面的例子所示。

  1. @Target({ElementType.FIELD, ElementType.PARAMETER})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Qualifier
  4. public @interface Offline {
  5. }

然后将注释添加到要自动连接的字段或属性,如下面的示例所示:

  1. public class MovieRecommender {
  2. @Autowired
  3. @Offline
  4. private MovieCatalog offlineCatalog;
  5. // ...
  6. }

现在 bean 定义只需要一个限定符类型,如下面的例子所示:

  1. <bean class="example.SimpleMovieCatalog">
  2. <qualifier type="Offline"/>
  3. <!-- inject any dependencies required by this bean -->
  4. </bean>

你也可以定义自定义的限定符注解,除了简单的值属性之外,还接受命名的属性,或者代替简单的值属性。如果在要自动连接的字段或参数上指定了多个属性值,那么 Bean 定义必须与所有这些属性值相匹配才能被认为是自动连接的候选者。作为一个例子,考虑下面的注解定义。

  1. @Target({ElementType.FIELD, ElementType.PARAMETER})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Qualifier
  4. public @interface MovieQualifier {
  5. String genre();
  6. Format format();
  7. }

在这种情况下,Format 是枚举,定义如下:

  1. public enum Format {
  2. VHS, DVD, BLURAY
  3. }

要自动连接的字段用自定义限定符注解,包括属性值:genre 和 format,如下面的示例所示:

  1. public class MovieRecommender {
  2. @Autowired
  3. @MovieQualifier(format=Format.VHS, genre="Action")
  4. private MovieCatalog actionVhsCatalog;
  5. @Autowired
  6. @MovieQualifier(format=Format.VHS, genre="Comedy")
  7. private MovieCatalog comedyVhsCatalog;
  8. @Autowired
  9. @MovieQualifier(format=Format.DVD, genre="Action")
  10. private MovieCatalog actionDvdCatalog;
  11. @Autowired
  12. @MovieQualifier(format=Format.BLURAY, genre="Comedy")
  13. private MovieCatalog comedyBluRayCatalog;
  14. // ...
  15. }

最后,Bean 的定义应该包含匹配的限定符值。这个例子还展示了你可以使用 Bean 元属性来代替<qualifier/>元素。如果有的话,<qualifier/> 元素及其属性优先,但如果没有这样的限定符,自动布线机制就会回到 <meta/> 标签中提供的值,就像下面例子中的最后两个 Bean 定义。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. https://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:annotation-config/>
  10. <bean class="example.SimpleMovieCatalog">
  11. <qualifier type="MovieQualifier">
  12. <attribute key="format" value="VHS"/>
  13. <attribute key="genre" value="Action"/>
  14. </qualifier>
  15. <!-- inject any dependencies required by this bean -->
  16. </bean>
  17. <bean class="example.SimpleMovieCatalog">
  18. <qualifier type="MovieQualifier">
  19. <attribute key="format" value="VHS"/>
  20. <attribute key="genre" value="Comedy"/>
  21. </qualifier>
  22. <!-- inject any dependencies required by this bean -->
  23. </bean>
  24. <bean class="example.SimpleMovieCatalog">
  25. <meta key="format" value="DVD"/>
  26. <meta key="genre" value="Action"/>
  27. <!-- inject any dependencies required by this bean -->
  28. </bean>
  29. <bean class="example.SimpleMovieCatalog">
  30. <meta key="format" value="BLURAY"/>
  31. <meta key="genre" value="Comedy"/>
  32. <!-- inject any dependencies required by this bean -->
  33. </bean>
  34. </beans>

总结

简单说:

  • @Autowired:主要通过类型匹配,名称只是有可能被考虑
  • @Resource:不管类型,只管名称
  • @Qualifier:配合 Autowired 进行微调使用:当作用在自定义注解上时,在对 bean 进行定义的时候,就需要声明 bean 的 qualifier 对应我们自定义注解中的值,spring 才能识别我们自定义注解中的任意参数;再简单一点,使用注解的方式来演示的话,就更清楚了,比如

    1. @Bean
    2. @Genre("Comedy") // 声明的时候使用相同的限定符
    3. public MovieCatalog movieCatalog(){
    4. return new SimpleMovieCatalog2();
    5. }
    6. @Autowired
    7. @Genre("Comedy") // 注入的时候还是使用相同的限定符
    8. private MovieCatalog comedyCatalog;

    另外也可以参考后面的章节,里面也有这个使用注解限定符的用法