第三章 最小化Spring XML配置
随着应用的不断发展,我们将不得不编写越来越复杂的XML配置。幸运的是,Spring提供了几种技巧,可以帮助我们减少XML的配置数量。
- 自动装配(autowiring):有助于减少甚至消除配置
<property>
元素和<constructor-arg>
元素,让Spring自动识别如何装配Bean的依赖关系。- 自动检测(autodiscovery):比自动装配更进一步,让Spring能够自动识别哪些类需要被配置成Spring Bean,从而减少对
<bean>
元素的使用。
3.1 自动装配Bean属性
3.1.1 4种类型的自动装配
当涉及自动装配Bean的依赖关系时,Spring有多种处理方式。因此,Spring提供了4种各具特色的自动装配策略。
byName——把与Bean的属性具有相同名字(或者ID)的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的名字相匹配的Bean,则该属性不进行装配。
byType——-把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的类型相匹配的Bean,则该属性不被装配。如果Spring寻找到多个符合要求的Bean,Spring会抛出异常。为了避免因为使用byType自动装配而带来的歧义,Spring为我们提供了另外两种选择:可以为自动装配标识一个首选Bean,或者可以取消某个Bean自动装配的候选资格。
- 为自动装配标识一个首选Bean,可以使用
<bean>
元素的primary属性。如果只有一个自动装配的候选Bean的primary属性设置为true,name该Bean将比其他候选Bean优先被选择。但是primary属性默认设置为true,所以为了使用primary属性,需要将所有非首选Bean的primary属性设置为false。 - 取消某个Bean的候选资格,可以设置这些Bean的autowire-candidate属性为false。
- 为自动装配标识一个首选Bean,可以使用
constructor——把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。constructor自动装配具有和byType自动装配相同的局限性。当发现多个Bean匹配某个构造器入参时,Spring会报错。如果一个类有多个构造器,它们都满足自动装配的条件时,spring也会报错。
autodetect——-首先尝试使用constructor进行自动装配。如果失败,再尝试使用byType进行自动装配。
<!-- 通过设置autowire属性为byName,Spring将为kenny的所有属性寻找与其名字相同的Spring Bean。在这里,Spring会发现instrument属性可以通过setter注入来进行自动装配。 -->
<bean id="instrument" class="com.springinaction.springidol.Saxophone" />
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" autowire="byName">
<property name="song" value="Jingle Bells" />
</bean>
3.1.2 默认自动装配
如果需要为Spring应用上下文中的每一个Bean(或者其中的大多数)配置相同的autowire属性。我们可以在根元素<bean>
上增加一个default-autowire属性。默认情况下,default-autowire属性为none。该属性配置仅对该配置文件下的Bean有效,我们也可以使用<bean>
元素的autowire属性来覆盖默认自动装配策略。
3.1.3 混合使用自动装配和显式装配
我们对某个Bean选择了自动装配策略时,也可以同时对该Bean的某些属性进行显式装配。我们仍然可以为任意一个属性配置<property>
元素。
3.2 使用注解装配
从Spring 2.5 开始,Spring开始支持使用注解自动装配Bean的属性。使用注解自动装配与在XML中使用autowire属性自动装配并没有太大的差别。但是使用注解方式允许更细粒度的自动装配。
Spring容器默认禁用注解装配。所以,在使用基于注解的自动装配前,我们需要在Spring配置中启用它。最简单的启用方式是使用Spring的context命名空间配置中的<context:annotation-config />
元素。
Spring 3 支持几种不同的用于自动装配的注解:
- Spring自带的@Autowired注解;
- JSR-330 (Java依赖注入标准)的 @Inject 注解;
- JSR-250 的 @Resource 注解。
3.2.1 使用 @Autowired
Spring的@Autowired注解是减少Spring XML配置的一种方式。但使用它的类会引入对Spring的特定依赖。
(1)Autowired注解标注setter方法,Spring 会尝试对该方法执行byType自动装配。
@Autowired
public void setInstrument(Instrument instrument){
this.instrument = instrument;
}
(2)Autowired注解可以标注需要自动装配Bean引用的任意方法。
@Autowired
public void heresYourInstrument(Instrument instrument){
this.instrument = instrument;
}
(3)Autowired注解标注构造器
@Autowired
public Instrumentalist(Instrument instrument){
this.instrument = instrument;
}
(4)Autowired注解可直接标注属性,不需要setter方法。
@Autowired
private Instrument instrument;
可选的自动装配:配置required属性为false
默认情况下,@Autiwired具有强契约特征,其所标注的属性或参数必须是可以装配的。如果没有Bean可以装配到@Autowire的所标注的属性或参数中,自动装配就会失败(NoSuchBeanDefinitionWxception)。
通过设置@Autowired的required属性为false来配置自动装配是可选的。当Spring没有查找到与之匹配的类型为Instrument的Bean,应用就不会发生任何问题,而装配的属性值会设置为null。
注意,required属性可以用于@Autowired注解所使用的任意地方。但是当使用构造器装配时,只有一个构造器可以将@Autowired的requeired属性设置为true,其他使用@Autowired所标注的构造器只能将required属性设置为false。此外,当使用@Autowired标注多个构造器时,Spring就会从所有满足装配条件的构造器中选择入参最多的那个构造器。
@Autowired(required=false)
private Instrument instrument;
限定歧义性的依赖:使用@Qualifier注解
对于具有两个或多个bean都满足装配条件的情况,@Autowired注解没有办法选择哪一个Bean才是它真正需要的。所以会抛出NoUniqueBeanDefinitionException异常,明确表明装配失败了。
我们可以配合使用Spring的@Qualifier注解来帮助@Autowired鉴别出哪一个Bean才是我们所需要的。
<!-- 使用@Qualifier来明确指定装配id为guitar的Bean -->
@Autowired
@Qualifier("guitar")
private Instrument instrument;
<!-- 通过在Bean上直接使用Qualifier来缩小范围 -->
<bean class="com.springinaction.springidol.Guitar">
<qualifier value="stringed" />
</bean>
<!-- 除了可以在 XML 中指定 qualifier,还可以使用 @Qualifier 注解来标注 Guitar 类-->
@Qualifier("stringed")
public class Guitar implements Instruments{
...
}
创建自定义的限定器(Qualifier):
/**
* 定义一个注解,并使用@Qualifier注解作为它的元注解。例如,让我们创建自己的@StringedInstrument注解来充当限定器。
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface StringedInstrument{
}
/**
* 通过自定义注解来代替@Qualifier标注Guitar
*/
@StringedInstrument
public class Guitar implements Instrument{
}
/**
* 使用@StringedInstruement 对自动装配的instrument属性进行限定
*/
@Autowired
@StringedInstrument
private Instrument instrument;
3.2.2 借助@Inject实现基于标准的自动装配
从 Spring 3 开始,Spring 开始兼容 JSR-330 依赖注入规范。
@Inject注解是 JSR-330 的核心部件。该注解几乎可以完全替换 Spring 的 @Autowired 注解。使用 @Inject 注解,需要添加javax.inject包。
和 @Autowired 一样,@Inject 可以用来自动装配属性、方法和构造器;与 @Autowired 不太的是,@Inject 没有 required 属性。因此,@Inject 注解所标注的依赖关系必须存在,如果不存在,则会抛出异常。
/**
* 除了 @Inject 注解,JSR-330 规范还提供了另一种技巧。Provider 接口可以实现 Bean 引用的延迟注入以及注入 Bean 的多个实例等功能。
* 我们有一个knifeJuggler类需要注入一个或多个knife的实例。假设 knife bean 的作用域声明为 prototype,下面的 KnifeJuggler 的构造器将获得5个 Knife Bean:
* KnifeJuggler 将获得一个 Provider<Knife> ,而不是在构造器中获得一个Knife实例。这个时候,只有 provider 被注入进去;在调用provider的 get() 方法前,实际的 Knife 对象并没有被注入。
*/
private Set<Knife> knives;
@Inject
public KnifeJuggler(Provider<Knife> knifeProvider){
knives = new HashSet<Knife>();
for(int i=0;i<5;i++){
knives.add(knifeProvider.get());
}
}
限定 @Inject 所标注的属性:@Named注解
@Inject 注解与 @Autowired 注解一样易导致歧义性的Bean定义。相对于 @Autowired 所对应的 @Qualifier,@Inject 所对应的是 @Named 注解。
import javax.inject.Inject;
import javax.inject.Named;
...
@Inject
@Named("guitar")
private Instrument instrument;
创建自定义的 JSR-330 Qualifier
JSR-330 在 javax.inject 包里有自己的 @Qualifier 注解。JSR-330 不建议使用该注解,但 JSR-330 鼓励我们使用该注解来创建自定义的限定器注解,就像我们使用Spring 的 @Qualifier 来创建自定义注解一样。实际上,@Named 注解就是一个使用 @Qualifier 注解所标注的注解。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@Documented
public @interface StringedInstrument{
}
3.2.3 在注解注入中使用表达式:@Value
Spring 3.0 引入了 @Value ,它是一个新的装配注解,可以让我们使用注解装配String类型的值和基本类型的值。
我们可以通过 @Value 直接标注某个属性、方法或者方法参数,并传入一个String类型的表达式来装配属性,例如:
@Value("Eruption")
private String song;
装配简单的值并不是 @Value 所擅长的,不过,可以借助 SpEL 表达式,是使@Value 的功能得到强化。它是一种有效的基于注解驱动的装配方式,它可以根据SpEL表达式进行动态的求值计算。例如:
@Value("#{systemProperties.myFavoriteSong}")
private String song;