3 用@Primary微调基于注解的自动装配
因为根据类型的自动装配可能会导致多个候选目标,所以在选择过程中进行更多的控制经常是有必要的。一种方式通过Spring的@Primary注解来完成。当有个多个候选bean要组装到一个单值的依赖时,@Primary表明指定的bean应该具有更高的优先级。如果确定一个’primary’ bean位于候选目标中间,它将是那个自动装配的值。
假设我们具有如下配置,将firstMovieCatalog定义为主要的MovieCatalog。
@Configuration public class MovieConfiguration { @Bean @Primary public MovieCatalog firstMovieCatalog() { ... } @Bean public MovieCatalog secondMovieCatalog() { ... } // ... }
根据这样的配置,下面的MovieRecommender将用firstMovieCatalog进行自动装配。
public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; // ... }
对应的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" primary="true"> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
3.9.4 微调基于注解且带有限定符的自动装配
当有多个实例需要确定一个主要的候选对象时,@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来代替内嵌的限定符元素,会有同样的匹配结果。然而,尽管你可以使用这个约定根据名字引用特定的beans,但是@Autowired从根本上来讲是使用可选的语义限定符来进行类型驱动注入的。这意味着限定符的值,即使回退到bean名称,总是缩小语义类型匹配的集合;它们没有从语义上将一个引用表达为一个唯一的beanid。好的限定符值是”main”或”EMEA”或”persistent”,表达一个特定组件的性质,这个组件是独立于bean id的,即使前面例子中像这个bean一样的匿名bean会自动生成id。
正如前面讨论的那样,限定符也可以应用到类型结合上,例如,Set。在这个例子中,根据声明的限定符匹配的所有beans作为一个集合进行注入。这意味着限定符不必是唯一的;它们只是构成过滤标准。例如,你可以定义多个具有同样限定符值”action”的MovieCatalog,所有的这些都将注入到带有注解@Qualifier("action")的Set中。
如果你想通过名字表达注解驱动的注入,不要主要使用@Autowired,虽然在技术上能通过@Qualifier值引用一个bean名字。作为可替代产品,可以使用JSR-250 @Resource注解,它在语义上被定义为通过组件唯一的名字来识别特定的目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:通过类型选择候选beans,特定的String限定符值被认为只在类型选择的候选目标中,例如,在那些标记为具有相同限定符标签的beans中匹配一个”account”限定符。
对于那些本身定义在集合/映射或数组类型中的beans来说,@Resource是一个很好的解决方案,适用于特定的集合或通过唯一名字区分的数组bean。也就是说,自Spring 4.3起,集合/映射和数组类型中也可以通过Spring的@Autowired类型匹配算法进行匹配,只要元素类型信息在@Bean中保留,返回类型签名或集合继承体系。在这种情况下,限定符值可以用来在相同类型的集合中选择,正如在前一段中概括的那样。
如果你想通过名字表达注解驱动的注入,不要主要使用@Autowired,虽然在技术上能通过@Qualifier值引用一个bean名字。作为可替代产品,可以使用JSR-250 @Resource注解,它在语义上被定义为通过组件唯一的名字来识别特定的目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:通过类型选择候选beans,特定的String限定符值被认为只在类型选择的候选目标中,例如,在那些标记为具有相同限定符标签的beans中匹配一个”account”限定符。
对于那些本身定义在集合/映射或数组类型中的beans来说,@Resource是一个很好的解决方案,适用于特定的集合或通过唯一名字区分的数组bean。也就是说,自Spring 4.3起,集合/映射和数组类型中也可以通过Spring的@Autowired类型匹配算法进行匹配,只要元素类型信息在@Bean中保留,返回类型签名或集合继承体系。在这种情况下,限定符值可以用来在相同类型的集合中选择,正如在前一段中概括的那样。
自Spring 4.3起,@Qualifier也考虑自引用注入,例如,引用返回当前注入的bean。注意自注入是备用;普通对其它组件的依赖关系总是优先的。在这个意义上,自引用不参与普通的候选目标选择,因此尤其是从不是主要的;恰恰相反,它们最终总是最低的优先级。在实践中,自引用只是作为最后的手段,例如,通过bean的事务代理调用同一实例的其它方法:在考虑抽出受影响的方法来分隔代理bean的场景中。或者,使用@Resource通过它的唯一名字可能得到一个返回当前bean的代理。
@Qualifier可以应用到字段,构造函数和多参数方法上,允许通过限定符注解在参数层面上缩减候选目标。相比之下,@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定义的信息。你可以添加标记作为
标记的子元素,然后指定匹配你的定制限定符注解的类型和值。类型用来匹配注解的全限定类名称。或者,如果没有名称冲突的风险,为了方便,你可以使用简写的类名称。下面的例子证实了这些方法。
<?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>
在3.10小节,“类路径扫描和管理组件”中,你将看到一个基于注解的替代方法,在XML中提供限定符元数据。特别地,看3.10.8小节,“用注解提供限定符元数据”。
在某些情况下,使用没有值的注解就是足够的。当注解为了通用的目的时,这是非常有用的,可以应用到跨几个不同类型的依赖上。例如,当网络不可用时,你可以提供一个要搜索的离线目录。首先定义一个简单的注解:
@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>
你也可以定义接收命名属性之外的定制限定符注解或代替简单的值属性。如果要注入的字段或参数指定了多个属性值,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元属性可以用来代替子元素。如果可获得,它和它的属性优先级更高,如果当前没有限定符,自动装配机制会将内的值作为备用,正如下面的例子中的最后两个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>