6.9.4 使用@Qualifier微调基于注解的自动装配
@Primary是一种有效的方法,可以在确定一个主要候选者时按类型使用自动装配。当需要更多地控制选择过程时,可以使用Spring的@Qualifier注解。您可以将限定符值与特定参数相关联,缩小类型匹配集,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值:
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... }
@Qualifier注解也可以在各个构造函数参数或方法参数上指定:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main")MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
相应的bean定义如下所示。具有限定符值“main”的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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
对于回调匹配,bean名称被视为默认限定符值。因此,您可以使用id“main”而不是嵌套的限定符元素来定义bean,从而得到相同的匹配结果。但是,虽然您可以使用此约定来按名称引用特定bean,但@Autowired基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回调,限定符值在类型匹配集合中也总是具有缩小范围的语义; 它们在语义上不表示对唯一bean id的引用。好的限定符值是“main”或“EMEA”或“persistent”,表示独立于bean id的特定组件的特征,在匿名bean定义的情况下可以自动生成,如上例中的那个。
限定符也适用于类型化集合,如上所述,例如,Set <MovieCatalog>。在这种情况下,根据声明的限定符的所有匹配bean都作为集合注入。这意味着限定符不必是唯一的; 它们只是简单地组成过滤条件。例如,您可以使用相同的限定符值“action”定义多个MovieCatalog bean,所有这些bean都将注入到使用@Qualifier(“action”)注解的Set <MovieCatalog>中。
如果您打算按名称来表达注解驱动的注入,尽管技术上能够通过@Qualifier值引用bean名称;也请不要大量使用@Autowired。反而,可以使用JSR-250 @Resource注解,在语义上,该注解定义为通过其唯一名称标识一个指定的目标组件,并且声明的类型与匹配过程无关。
作为该语义差异的明确结果,本身定义为集合或映射类型的bean是无法通过@Autowired注入,因为类型匹配不适用于它们。 对这样的bean使用@Resource来通过唯一名称引用指定的集合或映射bean。
@Autowired适用于字段,构造函数和多参数方法,允许限定符注解在参数级别缩小范围。相比之下,@ Resource仅支持具有单个参数的字段和bean属性的setter方法。因此,如果您的注入目标是构造函数或多参数方法,请坚持使用限定符。
您可以创建自己的自定义限定符注解。只需定义一个注解并且在定义中提供@Qualifier注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre { String value(); }
然后,您可以在自动装配的字段和参数上提供自定义限定符:
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 />标记的子元素,然后指定与自定义限定符注解匹配的type和value。类型与注解的完全限定类名匹配。或者,为方便起见,如果不存在名称冲突的风险,您可以使用短的类名称。以下示例演示了这两种方法。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://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>
在第6.10节“类路径扫描和受管理的组件”中,您将看到基于注解的替代方法,用于在XML中提供限定符元数据。具体来说,请参见第6.10.8节“使用注解提供限定符元数据”。
在某些情况下,使用没有值的注解可能就足够了。当注解用于更通用的目的并且可以跨多种不同类型的依赖项应用时,这可能很有用。例如,在没有Internet连接时,您可以提供将被搜索的offline目录。首先定义简单注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline { }
然后将注解添加到要自动装配的字段或属性中:
public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }
现在bean定义只需要一个限定符类型:
<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!-- inject any dependencies required by this bean --> </bean>
您还可以定义除简单value属性之外或代替简单value属性接受命名属性的自定义限定符注解。 如果随后在要自动装配的字段或参数上指定了多个属性值,那么,bean的定义必须匹配所有这种被视为自动装配候选者的属性值。例如,请考虑以下注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier { String genre(); Format format(); }
在这种情况下,Format是一个枚举:
public enum Format { VHS, DVD, BLURAY }
要自动装配的字段使用自定义限定符进行注解,并包含两个属性的值:genre和format。
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 meta属性来替代<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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://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>