spring提供了几种技巧,可以帮助我们减少XML的配置数量:
1、自动装配(autowiring)有助于减少甚至消除配置<property>元素和<constructor-arg>元素,让Spring自动识别如何装配Bean的依赖关系。
2、自动检测(autodiscovery)比自动装配更进了一步,让Spring能够自动识别哪些类需要被配置成Spring Bean,从而减少对<bean>元素的使用。
1.1、自动装配Bean属性
1.1.1、4种类型的自动装配
1、byName-把与Bean的属性具有相同名字的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的名字相匹配的Bean,则该属性不进行装配。
2、byType-把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的类型相匹配的Bean,则该属性不被装配。
3、constructor-把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。
4、autodetect-首先尝试使用constructor进行自动装配。如果失败,再尝试使用byType进行自动装配。
byName自动装配
[html] view plain copy
- <bean id="kenny2" class="com.springinaction.springidol.Instrumentalist">
- <property name="song" value="Jingle Bells"></property>
- <property name="instrument" ref="saxophone"></property>
- </bean>
可以这么写
[html] view plain copy
- <bean id="instrument" class="com.springinaction.springidol.Saxophone"/>
- <bean id="kenny2" class="com.springinaction.springidol.Instrumentalist" autowire="byName">
- <property name="song" value="Jingle Bells"></property>
- </bean>
byType自动装配
byType自动装配的工作方式类似于byName自动装配,只不过不再是匹配属性的名字而是检查属性的类型。当我们尝试使用byType自动装配时,Spring会寻找哪一个Bean的类型与属性的类型相匹配。
但是byType自动装配存在一个局限性:如果Spring寻找到多个Bean,它们的类型与需要自动装配的属性的类型都相匹配,怎么办呢?在这种场景下,Spring不会猜测哪一个Bean更适合自动装配,而是选择抛出异常。所以,应用只允许存在一个Bean与需要自动装配的属性类型相匹配。
为了避免因为使用byType自动装配而带来的歧义,Spring为我们提供了另外两种选择:可以为自动装配表识一个首选的Bean,或者可以取消某个Bean自动装配的候选资格。
为自动装配标识一个首选Bean,可以使用<bean>元素的primary属性。如果只有一个自动装配的候选Bean的primary属性设为true,那么该Bean将比其他候选Bean优先被选择。
但是primary属性有个很怪异的一点:它默认设置为true。所以,为了使用primary属性,我们不得不将所有非首选的primary属性设为false。例如:
[html] view plain copy
- <bean id="instrument" class="com.springinaction.springidol.Saxophone" primary="false"/>
primary属性仅对标识首选Bean有意义。如果在自动装配时,我们希望排除某些Bean,那可以设置这些Bean的autowire-candidate属性为false,如下所示:
[html] view plain copy
- <bean id="instrument" class="com.springinaction.springidol.Saxophone" autowire-candidate="false"/>
constructor装配
constructor自动装配具有和byType自动装配相同的局限性。当发现多个Bean匹配某个构造器的入参时,Spring不会尝试猜测哪一个Bean更适合自动装配。此外,如果一个类有多个构造器,它们都满足自动装配的条件时,Spring也不会尝试猜测哪一个构造器更适合使用。
最佳自动装配
当配置一个Bean的autowire属性为autodetect时,Spring将首先使用constructor自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用byType自动装配。
1.1.2、默认自动装配
我们所需要做的仅仅是在根元素<beans>上增加一个default-autowire属性:
[html] view plain copy
- <span style="color:#009900;"><?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- </span><span style="color:#ff0000;">default-autowire="byType"</span><span style="color:#009900;">></span>
default-autowire应用于指定spring配置文件中的所有Bean,而不是应用于Spring应用上下文中的所有Bean。
你可以在一个Spring应用上下文中定义多个配置文件,每一个配置文件都可以有自己的默认自动装配策略。
同样,不能因为我们配置了一个默认的自动装配策略,就意味着所有的Bean都只能使用这个默认的自动装配策略。我们还可以使用<bean>元素的autowire属性来覆盖<beans>元素所配置的默认自动装配策略。
1.2、使用注解装配
Spring容器默认禁用注解装配。所以,在使用基于注解的自动装配前,我们需要在Spring配置中启用它。最简单的启用方式是使用Spring的context命名空间配置中的<context:annotation-config>元素:
[html] view plain copy
- <context:annotation-config/>
这个元素告诉Spring我们打算使用基于注解的自动装配。一旦配置完成,我们就可以对代码添加注解,标识Spring应该为属性、方法和构造器进行自动装配。
Spring3支持几种不同的用于自动装配的注解:
1、Spring自带的@Autowired注解;
2、JSR-330的@Inject注解
3、JSR-250的@Resource注解。
1.2.1、使用@Autowired
@Autowired注解特别有趣的地方在于,我们不仅能使用它标注setter方法,还可以标注需要自动装配Bean引用的任意方法:
[html] view plain copy
- @Autowired
- public void setInstrument(Instrument instrument){
- this.instrument = instrument ;
- }
[html] view plain copy
- @Autowired
- public void heresYourInstrument(Instrument instrument){
- this.instrument = instrument ;
- }
@Autowired注解甚至可以标注构造器
[html] view plain copy
- @Autowired
- public Instrumentalist(Instrument instrument){
- this.instrument = instrument ;
- }
另外,我们还可以使用@Autowired注解直接标注属性。
[html] view plain copy
- @Autowired
- private Instrument instrument ;
但是@Autowired也有存在的限制:在应用中,必须只能有一个Bean适合装配到@Autowired注解所标注的属性或参数中。如果没有匹配的Bean,或者存在多个匹配的Bean,@Autowired注解就会遇到一些麻烦。幸运的是,上面的这两种场景都有相应的解决办法。首先,看一下如果没有匹配的Bean,如何让@Autowired注解远离失败。
可选的自动装配
属性不一定非要装配,null值也是可以接受的。在这种场景下,可以通过设置@Autowired的required属性为false来配置自动装配是可选的。
[html] view plain copy
- @Autowired(required=false)
- private Instrument instrument ;
此外,当使用@Autowired标注多个构造器时,Spring就会从所有满足装配条件的构造器中选择入参最多的那个构造器。
限制奇异性的依赖
为了帮助@Autowired鉴别出哪一个Bean才是我们所需要的,我们可以配合使用Spring的@Qualifier注解
例如下面我们可以使用@Qualifier来明确指定名为guitar的Bean:
[html] view plain copy
- @Autowired
- @Qualifier("guitar")
- private Instrument instrument ;
@Qualifier注解真正缩小了自动装配挑选Bean的范围。
创建自定义的限定器(Qualifier)
为了创建一个自定义的限定器注解,我们所需要的仅仅是定义一个注解,并使用@Qualifier注解座位它的元注解。例如,让我们创建自己的@StringedInstrument注解来充当限定器。
[html] view plain copy
- 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 {
- }
通过自定义的@StringedInstrument注解,我们现在可以用它来代替@Qualifier来标注Guitar:
[html] view plain copy
- @StringedInstrument
- public class Guitar implements Instrument{
- ...
- }
现在,我们也可以使用@StringedInstrument对自动装配的instrument属性进行限定:
[html] view plain copy
- @Autowired
- @StringedInstrument
- private Instrument instrument ;
当Spring尝试装配instrument属性时,Spring会把所有可选择的乐器Bean缩小到只有被@StringedInstrument注解所标注的Bean。如果只有一个乐器Bean使用@StringedInstrument注解,那么该Bean将会被装配到instrument属性中。
如果使用@StringedInstrument注解的乐器Bean有多个,我们还需要进一步限定来缩小范围。例如,假设除了Guitar Bean,我们还有一个HammeredDulcimer Bean也需要@StringedInstrument注解。所以,为了进一步限定Guitar Bean,我们可以定义另一种限定器注解@Strummed:
[html] view plain copy
- @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface Strummed {
- }
现在,我们可以使用@Strummed注解标注instrument属性把选择范围缩小:
[html] view plain copy
- @Autowired
- @StringedInstrument
- @Strummed
- private Instrument instrument ;
Spring的@Autowired注解是减少Spring XML配置的一种方式。但是使用它的类会引入对Spring的特定依赖。幸运的是,Spring还提供了标准的java注解来替代@Autowired。让我们了解一下如何使用Java依赖注入标准中的@Inject。
1.2.2、借助@Inject实现基于标准的自动装配
和@Autowired一样,@Inject可以用来自动装配属性、方法和构造器;与@Autowired不同的是,@Inject没有required属性。因此,@Inject注解所标注的依赖关系必须存在,如果不存在,则会抛出异常。
相对于@Autowired所对应的@Qualifier,@Inject所对应的是@Named注解。
@Named注解的工作方式非常类似于Spring的@Qualifier,正如我们在这里所看到的:
[html] view plain copy
- @Inject
- @Named("guitar")
- private Instrument instrument ;
Spring的@Qualifier与JSR-330的@Named的关键区别在于语义层面。@Qualifier注解帮助我们缩小所匹配的Bean的选择范围(默认使用Bean的ID),而@Named通过Bean的ID来标识可选择的Bean。
创建自定义的JSR-330 Qualifier
事实上,JSR-330在javax.inject包里有自己的@Qualifier注解。不像Spring的@Qualifier,JSR-330不建议使用该注解。相反,JSR-330鼓励我们使用该注解来创建自定义的限定器注解,就像我们使用Spring的@Qualifier来创建自定义注解一样。
例如,下面的程序战士了一个新的@StringInstrument注解,该注解使用JSR-330的@Qualifier来创建的,取代了之前使用Spring的@Qualifier
[html] view plain copy
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import javax.inject.Qualifier;
- @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface StringedInstrument {
- }
1.2.3、在注解注入中使用表达式
Spring3.0引入了@Value,它是一个新的装配注解,可以让我们使用注解装配String类型的值和基本类型的值,例如int、boolean。
我们可以通过@Value直接标注某个属性、方法或者方法参数,并传入一个String类型的表达式来装配属性。例如:
[html] view plain copy
- @Value(“Eruption”)
- private String song ;
在这里,我们为String类型的属性装配了一个String类型的值。但是传入@Value的String类型的参数只是一个表达式--它的计算结果可以是任意类型,因此@Value能够标注任意类型的属性。
不过,借助SpEL表达式,@Value被赋予了魔力。这一特性也使得@Value注解成为强大的装配可选方案。
例如,与其为song属性硬编码一个静态值,不如使用SpEL从系统属性中获取一个值:
[html] view plain copy
- @Value(“#{systemProperties.myFavoriteSong}”)
- private String song ;
1.3、自动检测Bean
<context:component-scan>元素除了完成与<context:annotation-config>一样的工作,还允许Spring自动检测Bean和定义Bean,这意味着不使用<bean>元素,Spring应用中的大多数Bean都能够实现定义和装配。
[html] view plain copy
- <context:component-scan base-package="com.wiseweb.pom" />
<context:component-scan>元素会扫描指定的包及其所有子包,并查找出能够自动注册为Spring bean的类。base-package属性标识了<context:component-scan>元素所扫描的包。
那么<context:component-scan>又是如何知道哪些类需要注册为Spring Bean呢?
1.3.1、为自动检测标注Bean
默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下。
1、@Component:通用的构造型注解,标识该类为Spring组件。
2、@Controller:标识将该类定义为Spring MVC controller。
3、@Repository:标识将该类定义为数据仓库。
4、@Service:标识将该类定义为服务。
使用@Component标注的任意自定义注解。
当使用<context:component-scan>时,基于注解地自动检测只是一种扫描策略。让我们了解下如何配置<context:component-scan>使用其他扫描策略来查找候选Bean。
1.3.2、过滤组件扫描
事实上,在如何扫描来获得候选Bean方面,<context:component-scan>非常灵活。通过为<context:component-scan>配置<context:include-filter>和/或者<context:exclude-filter>子元素,我们可以随意调整扫描行为。
为了掩饰组件扫描的过滤机制,考虑一下如何给予注解让<context:component-scan>自动注册所有市县Instrument接口的类。我们不得不浏览每一个Instrument实现的源码,并且使用@Component标注它们。至少,这是极为不便的,如果使用了第三方的Instrument实现,或许都没有源码的访问权限来添加注解。
所以,我们替换掉基于注解的组件扫描策略,再增加一个包含过滤器来要求<context:component-scan>自动注册所有的Instrument实现类,如下所示:
[html] view plain copy
- <context:component-scan base-package="com.wiseweb.pom" >
- <context:exclude-filter type="assignable" expression="com.springinaction.springidol.Instrument"/>
- </context:component-scan>
在这种情况下,我们要求派生于Instrument的所有类自动注册为Spring Bean。我们还可以选择如下任一一种过滤器。如下所示:
1、annotation:过滤器扫描使用指定注解所标注的那些类。通过expression属性指定要扫描的注解。
2、assignable:过滤器扫描派生于expression属性所指定类型的哪些类
3、aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的哪些类。
4、custom:使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定。
5、regex:过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的那些类
除了使用<context:include-filter>告知<context:component-scan >哪些类需要注册为Spring Bean以外,我们还可以使用<context:exclude-filter>来告知<context:component-scan >哪些类不需要注册为Spring Bean。