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

@Primary 是一种有效的方式,当可以确定一个主要候选项时,可以使用按类型进行自动装配。当您需要更多控制选择过程时,可以使用Spring的 @Qualifier 注解。您可以将限定符值与特定参数关联起来,缩小类型匹配集,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值,如下面的示例所示:

  • Java

  • Kotlin

public class MovieRecommender {

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

	// ...
}
class MovieRecommender {

	@Autowired
	@Qualifier("main")
	private lateinit var movieCatalog: MovieCatalog

	// ...
}

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

  • Java

  • Kotlin

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;
	}

	// ...
}
class MovieRecommender {

	private lateinit var movieCatalog: MovieCatalog

	private lateinit var customerPreferenceDao: CustomerPreferenceDao

	@Autowired
	fun 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)

		<!-- inject any dependencies required by this bean -->
	</bean>

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

		<!-- inject any dependencies required by this 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定义的情况下可能会自动生成,就像前面的示例中一样。

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

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

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

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

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

从4.3开始,@Autowired 还考虑自引用进行注入(即引用回当前正在注入的bean)。请注意,自引用是一种后备。对其他组件的常规依赖关系始终具有优先级。在这种意义上,自引用不参与常规候选项选择,因此特别不会成为主要候选项。在实践中,您应该仅在最后一种情况下使用自引用(例如,通过bean的事务代理调用同一实例上的其他方法)。在这种情况下,考虑将受影响的方法提取到单独的委托bean中。或者,您可以使用 @Resource,通过其唯一名称获取回当前bean的代理。

尝试注入同一配置类上的 @Bean 方法的结果实际上也是一种自引用场景。在实际需要的方法签名中延迟解析这样的引用(与配置类中的自动装配字段相反),或者将受影响的 @Bean 方法声明为 static,将其与包含配置类实例及其生命周期解耦。否则,这些bean仅在后备阶段考虑,而选择其他配置类上的匹配bean作为主要候选项(如果有的话)。

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

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

  • Java

  • Kotlin

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

	String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)

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

  • Java

  • Kotlin

public class MovieRecommender {

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

	private MovieCatalog comedyCatalog;

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

	// ...
}
class MovieRecommender {

	@Autowired
	@Genre("Action")
	private lateinit var actionCatalog: MovieCatalog

	private lateinit var comedyCatalog: MovieCatalog

	@Autowired
	fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
		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"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="example.Genre" value="Comedy"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

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

</beans>

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

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

  • Java

  • Kotlin

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline

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

  • Java

  • Kotlin

public class MovieRecommender {

	@Autowired
	@Offline (1)
	private MovieCatalog offlineCatalog;

	// ...
}
1 此行添加了@Offline注解。
class MovieRecommender {

	@Autowired
	@Offline (1)
	private lateinit var offlineCatalog: MovieCatalog

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

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

<bean class="example.SimpleMovieCatalog">
	<qualifier type="Offline"/> (1)
	<!-- inject any dependencies required by this bean -->
</bean>
1 此元素指定了限定符。

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

  • Java

  • Kotlin

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

	String genre();

	Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)

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

  • Java

  • Kotlin

public enum Format {
	VHS, DVD, BLURAY
}
enum class Format {
	VHS, DVD, BLURAY
}

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

  • Java

  • Kotlin

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;

	// ...
}
class MovieRecommender {

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

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

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

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

	// ...
}

最后,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>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="MovieQualifier">
			<attribute key="format" value="VHS"/>
			<attribute key="genre" value="Comedy"/>
		</qualifier>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<meta key="format" value="DVD"/>
		<meta key="genre" value="Action"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<meta key="format" value="BLURAY"/>
		<meta key="genre" value="Comedy"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

</beans>