使用限定符微调基于注解的自动装配

当可以确定一个主(或非回退)候选者时,@Primary@Fallback 是对多个实例按类型使用自动装配的有效方法。

当您需要对选择过程进行更多控制时,可以使用 Infra @Qualifier 注解。 您可以将限定符值与特定参数相关联,缩小类型匹配的集合,以便为每个参数选择特定的 bean。 在最简单的情况下,这可以是一个普通的描述性值,如下例所示:

  • Java

public class MovieRecommender {

  @Autowired
  @Qualifier("main")
  private MovieCatalog movieCatalog;

  // ...
}

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

  • Java

public class MovieRecommender {

  private final MovieCatalog movieCatalog;

  private final CustomerPreferenceDao customerPreferenceDao;

  @Autowired
  public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
      CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
  }

  // ...
}

以下示例显示了相应的 bean 定义。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
    <qualifier value="main"/> (1)

    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
    <qualifier value="action"/> (2)

    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
1 具有 main 限定符值的 bean 与使用相同值限定的构造函数参数连接。
2 具有 action 限定符值的 bean 与使用相同值限定的构造函数参数连接。

对于回退匹配,bean 名称被视为默认限定符值。 因此,您可以使用 idmain 定义 bean,而不是嵌套的限定符元素,从而导致相同的匹配结果。 但是,虽然您可以使用此约定按名称引用特定 bean,但 @Autowired 根本上是关于具有可选语义限定符的类型驱动注入。 这意味着限定符值,即使有 bean 名称回退,在类型匹配集合中也始终具有缩小语义。 它们在语义上不表示对唯一 bean id 的引用。 好的限定符值是 mainEMEApersistent,表示特定组件的特征,这些特征独立于 bean id,在匿名 bean 定义(如上例中的定义)的情况下,bean id 可能是自动生成的。

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

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

从 6.1 版本开始,这要求存在 -parameters Java 编译器标志。

话虽如此,如果您打算表达按名称的注解驱动注入,请不要主要使用 @Autowired,即使它能够要在类型匹配候选者中按 bean 名称进行选择。 相反,请使用 JSR-250 @Resource 注解,该注解在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。 @Autowired 具有相当不同的语义:在按类型选择候选 bean 后,指定的 String 限定符值仅在那些类型选定的候选者中考虑(例如,将 account 限定符与标记有相同限定符标签的 bean 进行匹配)。

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

从 4.3 开始,@Autowired 还考虑注入的自引用(即,引用回当前注入的 bean)。 请注意,自注入是一种回退。 对其他组件的常规依赖项始终具有优先权。 从这个意义上说,自引用不参与常规候选者选择,因此特别是不是主候选者。 相反,它们总是以最低优先级结束。 实际上,您应该仅将自引用作为最后手段(例如,用于通过 bean 的事务代理调用同一实例上的其他方法)。 在这种情况下,考虑将受影响的方法重构到单独的委托 bean 中。 或者,您可以使用 @Resource,它可以通过其唯一名称获取当前 bean 的代理。

尝试在同一个配置类上注入 @Bean 方法的结果实际上也是一种自引用场景。 要么在实际需要它的方法签名中延迟解析此类引用(而不是配置类中的自动装配字段),要么将受影响的 @Bean 方法声明为 static,将其与包含的配置类实例及其生命周期解耦。 否则,此类 bean 仅在回退阶段考虑,而其他配置类上的匹配 bean 被选为主候选者(如果可用)。

@Autowired 适用于字段、构造函数和多参数方法,允许通过参数级别的限定符注解进行缩小。 相比之下,@Resource 仅支持字段和具有单个参数的 bean 属性 setter 方法。 因此,如果您的注入目标是构造函数或多参数方法,则应坚持使用限定符。

您可以创建自己的自定义限定符注解。为此,定义一个注解并在定义中提供 @Qualifier 注解,如下例所示:

  • Java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

  String value();
}

然后,您可以在自动装配的字段和参数上提供自定义限定符,如下例所示:

  • Java

public class MovieRecommender {

  @Autowired
  @Genre("Action")
  private MovieCatalog actionCatalog;

  private MovieCatalog comedyCatalog;

  @Autowired
  public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
    this.comedyCatalog = comedyCatalog;
  }

  // ...
}

接下来,您可以为候选 bean 定义提供信息。 您可以将 <qualifier/> 标记添加为 <bean/> 标记的子元素,然后指定 typevalue 以匹配您的自定义限定符注解。 类型与注解的全限定类名匹配。 或者,为了方便起见,如果不存在名称冲突的风险,可以使用短类名。 以下示例演示了这两种方法:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
    <qualifier type="Genre" value="Action"/>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
    <qualifier type="example.Genre" value="Comedy"/>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

类路径扫描和托管组件 中,您可以看到在 XML 中提供限定符元数据的基于注解的替代方案。 具体来说,请参阅 使用注解提供限定符元数据

在某些情况下,使用没有值的注解可能就足够了。 当注解用于更通用的目的并且可以跨多种不同类型的依赖项应用时,这很有用。 例如,您可以提供一个在没有 Internet 连接时可以搜索的离线目录。 首先,定义简单的注解,如下例所示:

  • Java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}

然后将注解添加到要自动装配的字段或属性中,如下例所示:

  • Java

public class MovieRecommender {

  @Autowired
  @Offline (1)
  private MovieCatalog offlineCatalog;

  // ...
}
1 这行添加了 @Offline 注解。

现在,bean 定义只需要一个限定符 type,如下例所示:

<bean class="example.SimpleMovieCatalog">
  <qualifier type="Offline"/> (1)
  <!-- 注入此 bean 所需的任何依赖项 -->
</bean>
1 此元素指定限定符。

您还可以定义自定义限定符注解,该注解接受命名属性,以补充或替代简单的 value 属性。 如果在要自动装配的字段或参数上指定了多个属性值,则 bean 定义必须匹配所有这些属性值才能被视为自动装配候选者。 作为示例,考虑以下注解定义:

  • Java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

  String genre();

  Format format();
}

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

  • Java

public enum Format {
  VHS, DVD, BLURAY
}

要自动装配的字段使用自定义限定符进行注解,并包含两个属性的值:genreformat,如下例所示:

  • Java

public class MovieRecommender {

  @Autowired
  @MovieQualifier(format=Format.VHS, genre="Action")
  private MovieCatalog actionVhsCatalog;

  @Autowired
  @MovieQualifier(format=Format.VHS, genre="Comedy")
  private MovieCatalog comedyVhsCatalog;

  @Autowired
  @MovieQualifier(format=Format.DVD, genre="Action")
  private MovieCatalog actionDvdCatalog;

  @Autowired
  @MovieQualifier(format=Format.BLURAY, genre="Comedy")
  private MovieCatalog comedyBluRayCatalog;

  // ...
}

最后,bean 定义应包含匹配的限定符值。 此示例还演示了您可以使用 bean 元属性而不是 <qualifier/> 元素。 如果可用,<qualifier/> 元素及其属性优先,但如果不存在此类限定符,则自动装配机制回退到 <meta/> 标记中提供的值,如下例中的最后两个 bean 定义所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
    <qualifier type="MovieQualifier">
      <attribute key="format" value="VHS"/>
      <attribute key="genre" value="Action"/>
    </qualifier>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
    <qualifier type="MovieQualifier">
      <attribute key="format" value="VHS"/>
      <attribute key="genre" value="Comedy"/>
    </qualifier>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
    <meta key="format" value="DVD"/>
    <meta key="genre" value="Action"/>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
    <meta key="format" value="BLURAY"/>
    <meta key="genre" value="Comedy"/>
    <!-- 注入此 bean 所需的任何依赖项 -->
  </bean>

</beans>