SpringInAction读书笔记--第2章装配Bean

  实现一个业务需要多个组件相互协作,创建组件之间关联关系的传统方法通常会导致结构复杂的代码,这些代码很难被复用和单元测试。在Spring中,对象不需要自己寻找或创建与其所关联的其它对象,Spring容器负责把需要相互协作的对象引用赋予各个对象。创建对象之间协作关系的行为称为装配,这也是依赖注入的本质。Spring为装配bean提供了三种主要的装配机制。      

1.自动化装配bean

  Spring从两个角度来实现自动化装配:

  • 组件扫描:Spring会自动发现应用上下文中所创建的bean
  • 自动装配:Spring自动满足bean之间的依赖。

  组件扫描和自动装配组合起来能够将显式配置降低到最少。

  @Component注解表明该类会作为组件类,并告知Spring要为这个类创建bean。

@Component
public class SgtPeppers implements CompactDisc {
    ...
}

  不过,组件扫描默认是不启用的,我们还需要显式配置一下Spring,命令它去寻找带有@Component注解的类,并为其创建bean。我们可以通过JavaConfig定义Spring的装配规则,创建一个类,只需加上注解@Configuration表明它是一个JavaConfig,再使用@ComponentScan注解启用组件扫描。如果没有其它配置的话,@ComponentScan默认扫描与配置类相同的包及其子包,查找带有@Component注解的类,在Spring中自动为其创建一个bean。

@Configuration
@ComponentScan
public class CDPlayerConfig {
}

  也可以使用XML的方案启用组件扫描,<context:component-scan>元素会有与@ComponentScan注解相对应的属性和子元素。

<?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-3.0.xsd   
        http://www.springframework.org/schema/context   
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    <context:component-scan base-package="soundsystem"/>
</beans>

  Spring应用上下文中所有的bean都会给定一个ID,如果没有明确地为bean设置ID,Spring会根据类名为其指定一个ID,如果想为这个bean设置不同的 ID,将ID作为值传递给@Component注解。

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

  还有另一种为bean命名的方式,这种方式使用Java依赖注入规范中提供的@Named注解来为bean设置ID。

@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

  @Named和@Component在大多数场景中可以互相替换,但是@Component更清楚地表明它是做什么的。

  @ComponentScan默认扫描该类所在的包及其子包,但是我们想要将配置类放在单独的包中,使其与其它的应用代码区分开来。此时我们需要扫描多个包,将包名传给@ComponentScan可以指明扫描的包的名称。

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}

  如果想更清晰表明所设置的是基础包,可以通过basePackages属性配置。

@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig {
}

  也可以同时配置多个基础包,只需将basePackages属性设置为要扫描包的数组。

@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {
}

  在上面的例子中,所设置的基础包是以String类型表示的,这种方法类型不安全,如果重构代码,所指定的基础包可能会出现错误。@ComponentScan还提供了另外一种方法,将其指定为包中所包含的类或接口。这些类所在的包会作为组件扫描的基础包。可以在包中创建一个用来进行扫描的空标记接口,因为在稍后重构中,应用代码可能会被移除掉。

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}

      Spring自动配置的另外一方面内容就是自动装配。自动装配是让Spring自动满足bean依赖的一种方法,在满足依赖过程中,会在Spring应用上下文中寻找匹配某个bean需求的其它bean。为了声明自动装配,Spring提供了@Autowired注解,它可以在构造器上添加,表明当Spring创建该类的bean时,通过这个构造器进行实例化并传入可设置给构造器参数类型的bean。

@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;

    @Autowired
    public CDPlayer(CompactDisc cd){
        this.cd = cd;
    }
    ...
}

  @Autowired注解不仅能够用在构造器上,还可以用在属性的Setter方法上,在Spring初始化bean之后,它会尽可能的去满足bean的依赖。

@Autowired
public void setCompactDisc(CompactDisc cd){
        this.cd = cd;
}

  实际上,Setter方法没有什么特殊之处,@Autowired注解可以用在类的任何方法上。无论使用以上哪种方法,Spring都会尝试满足方法参数上声明的依赖,如果只有一个bean匹配依赖需求的话,这个bean将会被装配进来。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常,为了避免异常的出现,可以@Autowired(required=false)。

@Autowired(required=false)
public CDPlayer(CompactDisc cd){
    this.cd = cd;
}        

  将required属性设置为false时,Spring会尝试自动装配,没有匹配的bean时,Spring将会让这个bean处于未装配的状态。但是这个处于未装配状态的属性可能会在接下来的代码运行中出现NullPointerException。如果有多个bean都能满足依赖关系,Spring将会抛出一个异常,表明没有明确选择哪个bean进行装配。如果不愿使用Spring特有注解@Autowired,可以使用来源于Java依赖注入规范的@Inject注解替换。

@Inject
public CDPlayer(CompactDisc cd){
    this.cd = cd;
}

2.通过Java代码装配bean

      有时候自动化配置的方案行不通,如需要将第三方库中的组件装配到自己的应用中时,无法在它的类上添加@Component和@Autowired注解,这时必须要采用显式装配的方式。进行显式装配时,JavaConfig是更好的方案,因为它更强大、类型安全并且对重构友好。因为它就是Java代码,和应用中的其它Java代码一样,同时它与其它的Java代码又有区别,它不应该包含业务逻辑,也不应该侵入到业务逻辑代码中。通常会将JavaConfig放到单独的包中,使它与其它的应用程序逻辑分离开。

      创建JavaConfig类的关键在于为其添加@Configuration注解,表明这是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

@Configuration
public class CDPlayerConfig {
}

  要在JavaConfig中声明bean,我们需要编写一个方法,这个方法创建所需类型的实例,然后给这个方法加上@Bean注解。

@Bean
public CompactDisc sgtPeppers(){
    return new SgtPeppers();
}

  bean的ID与带有@Bean注解的方法名是一样的,也可以通过name属性指定一个不同的名字。

@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
    return new SgtPeppers();
}

  方法体可以使用Java提供的所有功能,只要最终返回一个实例即可。

  在JavaConfig中装配bean的最简单方式是引用创建bean的方法。

@Bean
public CDPlayer cdPlayer(){
    return new CDPlayer(sgtPeppers());
}

  看起来,CompactDisc是通过调用sgtPeppers()得到,但因为sgtPeppers()方法上添加了@Bean注解,实际上Spring将会拦截所有对它的调用,确保直接返回该方法所创建的bean,而不是每次都对其进行实际调用。默认情况下,Spring中的bean都是单例的。

  还有一种更为简单的方式,当Spring调用该方法创建bean的时候,会自动装配一个CpmpactDisc到配置方法之中,不用明确引用CompactDisc的@Bean方法,这种方式引用其它的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类中,甚至它可以通过组件扫描自动发现或XML来进行配置。

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
    return new CDPlayer(compactDisc);
}

  不管CompactDisc是什么方式创建出来的,Spring都会将其传入到配置方法中,用来创建CDPlayer bean。

  另外,除了使用构造器实现DI功能,也可以使用其它风格的DI配置,如通过Setter方法注入。

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
       CDPlayer cdPlayer = new CDPlayer();
       cdPlayer.setCompactDisc(compactDisc);
       return cdPlayer;
}        

      总之,带有@Bean注解的方法可以采用任何必要的Java功能产生bean实例。

3.通过XML装配bean

      在XML配置中,要创建一个XML文件,并且以<beans>元素为根。最为简单的Spring XML配置如下所示。

<?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.xsd
        http://www.springframework.org/schema/context" >
</beans>

      基本的XML配置已经比同等功能的JavaConfig类复杂得多,在JavaConfig中所需要的只是@Configuration,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。创建和管理Spring XML配置文件的一种简便方式是使用Spring Tool Suite。

      要在基于XML的Spring配置中声明一个bean,要使用spring-beans模式的另外一个元素<bean>,它类似于JavaConfig中的@Bean注解。

<bean class="soundsystem.SgtPeppeers"/>

  这个bean 的类通过class属性指定,它会根据全限定类名进行命名,这里bean的ID将会是"soundsystem.SgtPeppeers#0"。其中#0是一个计数形式,用来区分相同类型的其它bean。通常来讲更好的办法是借助id属性,为每个bean设置名字。

<bean id="compactDisc" class="soundsystem.SgtPeppeers"/> 

  当这个bean需要装配到其它bean时,会用到这个具体的名字,通常只对这些需要按名字引用的bean进行明确地命名。

  声明DI可以使用构造器注入,<constructor-arg>元素提供了DI配置的方案。

<bean id="cdPlayer" class="soundsystem.CDPlayer">
    <constructor-arg ref="compactDisc" />
</bean>

  Spring会创建一个CDPlayer实例,将ID为 compactDisc的bean引用传递到CDPlayer的构造器中。

  当我们使用一个字面量值配置对象时,可以使用constructor-arg的value属性。

<bean id="compactDisc" class="soundsystem.SgtPeppers">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
</bean>

  装配集合时,使用<constructor-arg>的子元素<list>,这表明一个包含值的列表将会传递到构造器中。其中<value>元素用来指定列表中的每个元素。

<constructor-arg>
    <list>
        <value>Sgt.Peppers</value>
        <value>With a Little</value>
    </list>
</constructor-arg>

      与之类似,我们也可以使用<ref>代替<value>,实现bean引用列表的装配。

<constructor-arg>
    <list>
        <ref bean="sgtPeppers" />
        <ref bean="whiteAlbum" />
    </list>
</constructor-arg>

  可以按照同样的方式使用<set>元素,它们也可以装配数组。

  以上完全是通过构造器注入,我们也可以使用属性的Setter方法实现属性注入。通常来说,对强依赖使用构造器注入,对可选性的依赖使用属性注入。

<bean id="cdPlayer" class="soundsystem.CDPlayer">
    <property name="compactDisc" ref="compactDisc" />
</bean>

      <property>元素为属性的Setter方法提供的功能与<constructor-arg>元素为构造器提供的功能一样,上例中,它引用了ID为compactDisc的bean,通过setCompactDisc()方法将其注入到compactDisc属性中。

  属性也可以注入字面量和集合,这与构造器参数非常相似。

<bean id="compactDisc" class="soundsystem.SgtPeppers">
    <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
    <property name="artist" value="The Beatles" />
    <property name="tracks" >
        <list>
            <value>Sgt.Peppers</value>
            <value>With a Little</value>
        </list>
    </property>
</bean>    

4.导入和混合配置

      我们可能会同时使用自动化和显式配置,在Spring中支持混合配置。

  在自动装配时它并不在意要装配的bean来自哪里,无论它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。

  在JavaConfig中引用XML配置

  JavaConfig类可以使用@Import注解引入另一个JavaConfig类。

@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc){
        return new CDPlayer(compactDisc);
    }
}

  或者采用一个更好的办法,创建一个更高级别的JavaConfig类,在这个类中使用@Import注解将两个配置类组合在一起。

@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
}

  我们也可以使用@ImportResource注解在JavaConfig中引入XML配置。

@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}

  在XML配置中引用JavaConfig

  在XML中我们可以使用<import>元素来拆分XML配置。

<import resoune="cd-config.xml"/> 

  如果要是使XML元素能够导入JavaConfig类,可以这样声明bean。

<bean class="soundsystem.CDConfig"/>

  同样可以创建一个更高层次的XML文件,负责将多个配置组合起来。

<bean class="soundsystem.CDConfig"/>
<import resoune="cd-config.xml"/> 

  无论使用JavaConfig还是使用XML进行装配,通常都会创建一个根配置,将更多的装配类和XML文件组合起来,同时也会在根配置中启用组件扫描(<context:component-scan>或@ComponentScan)。

5.小结

      Spring的配置风格是可以互相搭配的,建议尽可能地使用自动配置的机制。当你必须要显式配置bean的时候,推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。

posted @ 2016-12-03 09:42  语陌1988  阅读(255)  评论(0编辑  收藏  举报