对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

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

@Primary 是在存在多个实例但可确定一个主要候选者时,通过类型进行自动装配的有效方式。当你需要对选择过程进行更精细的控制时,可以使用 Spring 的 @Qualifier 注解。你可以将限定符值与特定参数关联起来,从而缩小类型匹配的范围,使得为每个参数选择特定的 Bean。在最简单的情况下,这可以是一个简单的描述性值,如下例所示:spring-doc.cadn.net.cn

public class MovieRecommender {

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

	// ...
}
class MovieRecommender {

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

	// ...
}

您也可以在单个构造函数参数或方法参数上指定 @Qualifier 注解,如下例所示:spring-doc.cadn.net.cn

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 定义。spring-doc.cadn.net.cn

<?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 的名称被视为默认的限定符值。因此,你可以将 Bean 定义为 idmain,而不使用嵌套的限定符元素,从而得到相同的匹配结果。然而,尽管你可以使用此约定通过名称引用特定的 Bean,@Autowired 本质上仍是基于类型的注入,并可选择性地使用语义限定符。这意味着,即使有 Bean 名称作为后备,限定符值在类型匹配的集合中始终具有缩小范围的语义,而不会在语义上表示对某个唯一 Bean id 的引用。良好的限定符值应为 mainEMEApersistent,它们表达了特定组件的某些特征,这些特征独立于 Bean 的 id;而在匿名 Bean 定义(如前面示例中的情况)下,Bean 的 8 可能是自动生成的。spring-doc.cadn.net.cn

限定符(Qualifiers)也适用于类型化的集合,如前所述——例如,适用于Set<MovieCatalog>。在这种情况下,所有符合所声明限定符的匹配 Bean 都会被注入为一个集合。这意味着限定符不必是唯一的,而是构成了一种过滤条件。例如,你可以定义多个具有相同限定符值“action”的MovieCatalog Bean,它们都会被注入到一个使用Set<MovieCatalog>注解的@Qualifier("action")中。spring-doc.cadn.net.cn

在类型匹配的候选 Bean 中,让限定符(qualifier)值根据目标 Bean 的名称进行选择时,在注入点并不需要使用 @Qualifier 注解。 如果在非唯一依赖的情况下没有其他解析指示符(例如限定符或主标记 primary marker), Spring 会将注入点名称(即字段名或参数名)与目标 Bean 名称进行匹配,并选择名称相同的候选 Bean(无论是通过 Bean 名称还是其关联的别名)。spring-doc.cadn.net.cn

这需要 Java 编译器启用 -parameters 标志。作为后备方案,6.0 版本仍然支持通过 -debug 从调试符号中获取参数名称。spring-doc.cadn.net.cn

作为按名称注入的替代方案,请考虑使用 JSR-250 的 @Resource 注解, 该注解在语义上被定义为通过其唯一名称来标识特定的目标组件, 而声明的类型在匹配过程中无关紧要。@Autowired 则具有相当不同的语义: 它首先按类型选择候选 Bean,然后仅在这些已按类型筛选出的候选者中, 考虑所指定的 String 限定符值(例如,将 account 限定符与带有相同限定符标签的 Bean 进行匹配)。spring-doc.cadn.net.cn

对于本身被定义为集合、Map 或数组类型的 Bean,@Resource 是一个很好的解决方案,可以通过唯一的名称引用特定的集合或数组 Bean。 然而,从 Spring 4.3 版本开始,只要元素类型信息在 Map 方法的返回类型签名或集合的继承层次结构中得以保留,你也可以通过 Spring 的 @Autowired 类型匹配算法来匹配集合、@Bean 和数组类型。 在这种情况下,你可以使用限定符(qualifier)值在相同类型的集合之间进行选择,如前一段所述。spring-doc.cadn.net.cn

从 4.3 版本开始,@Autowired 也会考虑自引用(self references)进行注入(即指向当前正在被注入的 bean 的引用)。请注意,自注入仅作为一种后备方案。对其他组件的常规依赖始终具有更高优先级。从这个意义上说,自引用不会参与常规的候选 bean 选择过程,因此尤其不会被视为主候选(primary)。相反,它们的优先级始终是最低的。在实践中,您应仅将自引用作为最后的手段(例如,通过 bean 的事务代理调用同一实例上的其他方法)。在这种场景下,建议将受影响的方法提取到一个单独的委托 bean 中。或者,您也可以使用 @Resource,它可以通过 bean 的唯一名称获取指向当前 bean 的代理。spring-doc.cadn.net.cn

尝试注入来自同一配置类中 @Bean 方法的结果,实际上也属于一种自引用场景。要么在实际需要该引用的方法签名中以延迟方式解析这些引用(而不是在配置类的自动装配字段中),要么将受影响的 @Bean 方法声明为 static,从而将其与包含它的配置类实例及其生命周期解耦。 否则,此类 Bean 仅会在回退阶段被考虑,而其他配置类中匹配的 Bean 将被优先选为主要候选者(如果存在的话)。spring-doc.cadn.net.cn

@Autowired 可用于字段、构造函数和多参数方法,允许在参数级别通过限定符注解进行缩小匹配范围。相比之下,@Resource 仅支持字段和带单个参数的 bean 属性 setter 方法。 因此,如果你的注入目标是构造函数或多参数方法,应坚持使用限定符。spring-doc.cadn.net.cn

您可以创建自己的自定义限定符注解。为此,请定义一个注解,并在您的定义中包含 @Qualifier 注解,如下例所示:spring-doc.cadn.net.cn

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

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

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 以匹配您自定义的限定符注解。其中,4 会与注解的完整限定类名进行匹配。或者,为方便起见,如果不存在名称冲突的风险,您也可以使用简短的类名。以下示例展示了这两种方式:spring-doc.cadn.net.cn

<?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 中提供限定符元数据。具体请参见使用注解提供限定符元数据spring-doc.cadn.net.cn

在某些情况下,使用不带值的注解就足够了。当注解用于更通用的目的,并可应用于多种不同类型的依赖项时,这种方式非常有用。例如,您可能提供一个离线目录,在没有互联网连接时可以进行搜索。首先,定义一个简单的注解,如下例所示:spring-doc.cadn.net.cn

@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

然后将注解添加到需要自动装配的字段或属性上,如下例所示:spring-doc.cadn.net.cn

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,如下例所示:spring-doc.cadn.net.cn

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

你还可以定义自定义的限定符注解,这些注解除了或代替简单的 value 属性外,还可以接受命名的属性。如果在需要自动装配的字段或参数上指定了多个属性值,则 Bean 定义必须匹配所有这些属性值,才能被视为自动装配的候选者。例如,请考虑以下注解定义:spring-doc.cadn.net.cn

@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 是一个枚举,定义如下:spring-doc.cadn.net.cn

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

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

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 的定义应包含匹配的限定符(qualifier)值。本示例还演示了你可以使用 Bean 的元属性(meta attributes),而不必使用 <qualifier/> 元素。如果存在 <qualifier/> 元素及其属性,则它们具有优先权;但如果未提供此类限定符(如以下示例中的最后两个 Bean 定义所示),自动装配机制将回退到 <meta/> 标签中提供的值。spring-doc.cadn.net.cn

<?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>