Spring实战 第2章 装配Bean

本章内容:
1. 声明bean
2. 构造器注入和Setter方法注入
3. 装配bean
4. 控制bean的创建和销毁
在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。

创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。


本章目录:
这里写图片描述


2.1 Spring配置的可选方案

当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制

  • 在XML中进行显式配置。
  • 在Java中进行显式配置。
  • 隐式的bean发现机制和自动装配。

三种装配没有什么特定环境,你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的bean让Spring去自动发现。

但是建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。


2.2 自动化装配bean

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

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

组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。

在这里我们创建几个bean,首先,要创建CompactDisc(CD)类,Spring会发现它并将其创建为一个bean。然后,会创建一个CDPlayer(CD播放器)类,让Spring发现它,并将CompactDiscbean注入进来。


2.2.1 创建可被发现的bean

第一步:
CompactDisc接口在Java中定义了CD的概念:

public interface CompactDisc(){
    void play();
}

CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。

第二步:
创建CompactDisc接口的实现类。
带有@Component注解的CompactDisc实现类SgtPeppers:

//该注解证明这个是个bean,需要被创建,待会扫描的就是它
@Component
public class SgtPeppers implements CompactDisc{

    private String title="title";
    private String artList="artList";
    public void play(){ 
        system.out.println("playing "+title+" by "+artList );
    }

}

和CompactDisc接口一样,SgtPeppers的具体内容并不重要。你需要注意的就是SgtPeppers类上使用了@Component注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppersbean,因为这个类使用了@Component注解,所以Spring会为你把事情处理妥当。

第三步:
组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
@ComponentScan注解启用了组件扫描:

/*
    @Configuration表明它是一个配置类,待会扫描的就是它所在的包下面的实体类。
    @ComponentScan 扫描配置
*/
@Configuration
@ComponentScan
public class CDPlayerConfig{

}

类CDPlayerConfig通过Java代码定义了Spring的装配规则。不过,现在我们只需观察一下CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。

或者通过xml来启用组件扫描。
如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的<context:component-scan>元素。

<context:component-scan base-package="soundsystem">

尽管我们可以通过XML的方案来启用组件扫描,但是在后面的讨论中,我更多的还是会使用基于Java的配置。如果你更喜欢XML的话,<context:component-scan>元素会有与@ComponentScan注解相对应的属性和子元素。

第四步:
为了测试组件扫描的功能,我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来。
测试组件扫描能够发现CompactDisc:

/*
@RunWith() 表明使用的是spring自带的Junit测试
@@ContextConfiguration(classes=CDPlayerConfig.class)
和spring的配置文件结合测试
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest{

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull(){
        cd.play();
    }
}

CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDiscbean。

输出结果:playing title by artList

表示执行了实体类SgtPeppers的方法,在整个以上流程中我们自己没有任何显式创建实体对象,而是spring通过配置类CDPlayerConfig的扫描找到了带有@Component的SgtPeppers类,然后创建了他的实体对象。


2.2.2 为组件扫描的bean命名

我们在第一章看到了在xml配置文件中每一个组件对象都有一个指定的ID,确保它的唯一性。
注意在注解中,比如在前面的例子中,尽管我们没有明确地为SgtPeppersbean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。

但是我们也可以手动设置ID,案例如下:

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

还有一种方式使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID。
如下:

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

注意:二者可以互换,没有太大差别,但是建议使用@Component。


2.2.3 设置组件扫描的基础包

@ComponentScan,现在默认扫描的是该配置类所在的包,但是一般情况下,我们将其是单独放在一个配置包下面,避免污染项目代码,所以如果我们想扫描其他包或者多个包,配置如下:

@Configuration
@ComponentScan("包名称")
public class CDPlayerConfig {
}

或者

@Configuration
@ComponentScan(basePackages={"包名称"})
public class CDPlayerConfig {
}

或者扫描多个包

@Configuration
@ComponentScan(basePackages={"包名称1","包名称2"})
public class CDPlayerConfig {
}

但是问题来了,我们如果重构代码的话,比如修改了包名,难道就要一个个修改这里面的包名称吗,所以经常采用如下配置:

@Configuration
@ComponentScan(basePackageClasses={要扫描的实体类名称1.Class,要扫描的实体类名称2.Class})
public class CDPlayerConfig {
}

注意:
没有必要把所有的实体类都写在上面,如果多个实体类在同一个包中,写一个就ok了,因为它扫描的是该类所在包下的所有实体类。
也可以写实体类的接口。设置可以这样,直接写一个空标记接口,专门用来扫描,这样即使你要删除某个实体类也不会有影响。


2.2.4 通过为bean添加注解实现自动装配

自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
也就是说在某个实体类中有引用对象作为属性。

案例
首先创建一个新的实体接口和实体类

实体接口

package com.javaAction.entity;

public interface MediaPlayer {

    public void  play();
}

实现接口的实体类

package com.javaAction.entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer{

    //在该实体类里面引用其他的实体类对象
    private CompactDisc cd;
    @Autowired
    public CDPlayer(CompactDisc cd){    
        this.cd=cd;
    }
    public CompactDisc getCd() {
        return cd;
    }   
    public void setCd(CompactDisc cd) {
        this.cd = cd;
    }
    public void  play(){    
        cd.play();
        System.out.println("我是CDPlayer");
    }
}

注意@Autowired不仅可以写在构造器上,也可以写在set方法上面。

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

@Autowired注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像在setCompactDisc()上那样,发挥完全相同的作用:

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

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。

如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:

@Autowired(required=false)
public void insertDisc(){
    this.cd = cd;
}

将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。

注意:
尽量别配置该属性 否则如果真的没有匹配的bean的话,如果使用就会报空指针异常。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
也可以用@Inject替换@Autowired,两个基本上没有太大差别。


2.2.5 验证自动装配

package com.javaAction.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.javaAction.entity.CDPlayerConfig;
import com.javaAction.entity.CompactDisc;
import com.javaAction.entity.MediaPlayer;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayTest {

    @Autowired
    private MediaPlayer mediaPlayer;

    @Test
    public void cdShouldNotNull(){

        mediaPlayer.play();
    }
}

输出结果:

title=artList
我是CDPlayer

2.3 通过Java代码装配bean

讲完了最常用的自动装配,咱们再说说显式地装配bean。
说一种情况,是不能使用自动装配bean的,比如实体类不是咱们自己写的,而是引用第三方的jar包,想要将第三方带的组件(注意组件也就是实体类)装配到你的应用中,这样没有办法在它的类上添加@Component和@Autowired注解的,这样就不能使用自动化装配方案,必须要采用显式装配的方式。

显式装配有两种方案:使用Java或者xml进行装配(推荐使用java,因为重构性好)。

先说java方案装配bean。
注意这么几点:
(1)首先再javaConfig(就是java装配类)里面不能含有任何业务逻辑代码。
(2)JavaConfig不应该入侵到业务逻辑代码当中去。
(3)尽量将其单独置于一个包下,和业务代码保持分离。


2.3.1 创建配置类

注意看前面的一个配置类:

@Configuration
@ComponentScan
public class CDPlayerConfig {

}

上面中咱们采用扫描并自动装配bean的方式。
此时我们就要开始有所改变了。

@Configuration
public class CDPlayerConfig {

}

@Configuration注解表明这个类是一个配置类,这个不能删除,移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。如果你现在运行CDPlayerTest的话,测试会失败,并且会出现BeanCreationException或者NoSuchBeanDefinitionException异常


2.3.2 声明简单的bean

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

@Configuration
@ComponentScan
public class CDPlayerConfig {

    @Bean
    public CompactDisc sgtPeppers(){

        return new SgtPeppers();
    }
}

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册
为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的
逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的
也可以指定bean的ID:

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

该方法体返回了一个新的SgtPeppers实例。
咱们甚至都可以这样做,
在一组CD中随机选择一个CompactDisc来播放:

@Bean(name="mySgtPeppers")
    public CompactDisc sgtPeppers(){
        int choice=(int) (Math.random()*4);
        if(choice==1){
            return new SgtPeppers();
        }else if(choice==2){

            return new SgtPeppers01();
        }else{

            return new SgtPeppers02();
        }
    }

2.3.3 借助JavaConfig实现注入

在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer的可行方案:

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

cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer,与方法的名字相同。

cdPlayer()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。

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

可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

@bean
public CDplayer cdPlayer(CompactDisc  compactDisc ){

    return new CDPlayer(compactDisc);
}

在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。

通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。

另外,需要提醒的是,我们在这里使用CDPlayer的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说,如果你想通过Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:

@bean
public CDplayer cdPlayer(CompactDisc  compactDisc ){

    CDplayer cDplayer=new CDplayer(compactDisc);
    cDplayer.setCompactDisc(compactDisc);
    return cDplayer;
}

再次强调一遍,带有@Bean注解的方法可以采用任何必要的Java功能
来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。


2.4 通过XML装配bean

本节的内容只是用来帮助你维护已有的XML配置,在完成新的Spring工作时,尽量使用自动化配置和JavaConfig。


2.4.1 创建XML配置规范

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"
     xmlns:p="http://www.springframework.org/schema/p" 
 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">

</beans>

2.4.2 声明一个简单的<bean>

要在基于XML的Spring配置中声明一个bean,我们要使用springbeans模式中的另外一个元素:。元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDiscbean:

<bean class="com.javaAction.entity.SgtPeppers" />

这里声明了一个很简单的bean,创建这个bean的类通过class属性来指
定的,并且要使用全限定的类名。
注意:
如果没有给定ID,bean将会根据全限定类名来进行命名,比如上面的id就是”com.javaAction.entity.SgtPeppers#0”,后面的“#0”是一个计数的形式,用来区分相同类型的其他bean。

但是为了方便后续的引用尽量自己命名。

 <bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />

注意:
  在XML装配中就是你不再需要直接负责创建SgtPeppers的实例,在基于JavaConfig的配置中,我们是需要这样做的。当Spring发现这个元素时,它将会调用SgtPeppers的默认构造器来创建bean。在XML配置中,bean的创建显得更加被动,不过,它并没有JavaConfig那样强大,在JavaConfig配置方式中,你可以通过任何可以想象到的方法来创建bean实例。

在这个简单的<bean>声明中,我们将bean的类型以字符串的形式设置在了class属性中。但是如果我们重命名了类名,那也要很麻烦的来改正XML里面的配置。


2.4.3 借助构造器注入初始化bean

在Spring XML配置中,只有一种声明bean的方式:使用<bean>元素
并指定class属性。Spring会从这里获取必要的信息来创建bean。

但是,在XML中声明DI(是依赖注入,不是ID)时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

  • <constructor-arg>元素
  • 使用Spring 3.0所引入的c-命名空间

两者的区别在很大程度就是是否冗长烦琐。<constructor-arg>元素比使用c-命名空间会更加冗长,从而导致XML更加难以读懂。但是有些事情<constructor-arg>可以做到,但是使用c-命名空间却无法实现。


构造器注入bean引用
按照现在的定义,CDPlayerbean有一个接受CompactDisc类型的构造器。现在已经声明了SgtPeppers bean,并且SgtPeppers类实现了CompactDisc接口,所以实际上我们已经有了一个可以注入到CDPlayerbean中的bean。我们所需要做的就是在XML中声明CDPlayer并通过ID引用SgtPeppers:

方案一:

<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />

     <bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
        <constructor-arg ref="compactDisc"/>
     </bean>

当Spring遇到这个<bean>元素时,它会创建一个CDPlayer实例。<constructor-arg>元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。

方案二:
可以使用Spring的c-命名空间。要使用它的话,必须要在XML的顶部声明其模式,如下所示:

这里写图片描述

在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数
了,如下所示:

<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
        <c:cd-ref="compactDisc"/>
     </bean>

在这里,我们使用了c-命名空间来声明构造器参数,它作为元素的一个属性,不过这个属性的名字有点诡异。下图描述了这个属性名是如何组合而成的。

这里写图片描述

属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”。

关于c-命名空间,就是它直接引用了构造器参数的名称。引用参数的名称看起来有些怪异,因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能就无法正常执行了。解决办法:
使用参数在整个参数列表中的位置信息

<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
        <c:_0-ref="compactDisc"/>
     </bean>

这个c-命名空间属性看起来似乎比上一种方法更加怪异。我将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。

当构造器只有一个参数时候,我们还有另外一个方案——根本不用去标示参数:

<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer" c:_-ref="compactDisc"/>

这里没有参数索引或参数名。只有一个下画线,然后就是用“-ref”来表明正在装配的是一个引用。


将字面量注入到构造器中
迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时候,我们需要做的只是用一个字面量值来配置对象。为了阐述这一点,假设你要创建CompactDisc的一个新实现,如下所示:

public class BlankDisc implements CompactDisc{

    private String title;
    private String artlist;

    public BlankDisc(String title, String artlist) {

        this.title = title;
        this.artlist = artlist;
    }

    public void play() {

        System.out.println("playing " + title + " by " + artlist);
    }

}

xml配置如下:

<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
        <constructor-arg value="aa"/>
        <constructor-arg value="bb"/>
</bean>

这一次我们没有使用“ref”属性来引用其他的bean,而是使用了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。

采用c-命名空间:
引用构造器参数名字:

<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />

<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
c:_title="aa"
c:-artlist="bb"
/>

通过参数索引装配:

<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />

<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
c:_0="aa"
c:-1="bb"
/>

装配集合:

public class BlankDisc implements CompactDisc{

    private String title;
    private String artlist;
    private List<String> tracks;



    public BlankDisc(String title, String artlist, List<String> tracks) {

        this.title = title;
        this.artlist = artlist;
        this.tracks = tracks;
    }

    public void play() {

        System.out.println("playing " + title + " by " + artlist);
        for(int i=0;i<tracks.size();i++){

            System.out.println("tracks"+i+""+"="+tracks);
        }
    }
}

xml装配

    <bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
        <constructor-arg value="aa"/>
        <constructor-arg value="bb"/>
        <constructor-arg>
                <list>
                    <value>001</value>
                    <value>002</value>
                    <value>003</value>
                </list>
        </constructor-arg>
     </bean>

<list>元素是<constructor-arg>的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>元素用来指定列表中的每个元素。
同样 list集合里面也可以是对象,那么只要用<ref>元素替代<value>
同样也可以使用set集合。

注意使用c-命名空间的属性无法实现装配集合的功能。


2.4.4 设置属性

到目前为止,CDPlayer和BlankDisc类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。假设属性注入的CDPlayer如下所示:

@Component
public class CDPlayer implements MediaPlayer{

    //在该实体类里面引用其他的实体类对象
    private CompactDisc cd;

    public CompactDisc getCd() {
        return cd;
    }

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

    public void  play(){
        cd.play();
        System.out.println("我是CDPlayer");
    }
}

该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
XML的配置:

 <bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
    <property name="compactDisc" ref="compactDisc"> 
</bean>

<property>元素为属性的Setter方法所提供的功能。

它引用了ID为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)。

Spring为<constructor-arg>元素提供了c-命名空间作为替代方案,与之类似,Spring提供了更加简洁的p-命名空间,作为<property>元素的替代方案。
这里写图片描述

我们可以使用p-命名空间,按照以下的方式装配compactDisc属性:

<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer"
      p:compactDisc-ref="compactDisc"
/>

p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似。下图
阐述了p-命名空间属性是如何组成的。
这里写图片描述

首先,属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示Spring要进行装配的是引用,而不是字面量。


将字面量注入到属性中
属性也可以注入字面量,这与构造器参数非常类似。不过,BlankDisc这次完全通过属性注入进行配置,而不是构造器注入。新的BlankDisc类如下所示:

public class BlankDisc implements CompactDisc{

    private String title;
    private String artlist;
    private List<String> tracks;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setArtlist(String artlist) {
        this.artlist = artlist;
    }

    public void setTracks(List<String> tracks) {
        this.tracks = tracks;
    }

    public void play() {

        System.out.println("playing " + title + " by " + artlist);
        for(int i=0;i<tracks.size();i++){

            System.out.println("tracks"+i+""+"="+tracks);
        }
    }
}

现在,它不再强制要求我们装配任何的属性。你可以按照如下的方式创建一个BlankDiscbean,它的所有属性全都是空的:

<bean id="reallyBlankDisc"
    class="com.javaAction.entity.BlankDisc"
>

当然,如果在装配bean的时候不设置这些属性,那么在运行期CD播放器将不能正常播放内容。play()方法可能会遇到的输出内容是“Playing null by null”,随之会抛出NullPointerException异常。

<bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
     <property name="title" value="aa">
     <property name="artlist" value="bb">
     <property name="tracks">
                <list>
                    <value>001</value>
                    <value>002</value>
                    <value>003</value>
                </list>
     </property>
     </bean>

除了使用<property>元素的value属性来设置title和artlist,我们还使用了内嵌的<list>元素来设置tracks属性,这与之前通过<constructor-arg>装配tracks是完全一样的。

使用p-命名空间的属性来完成该功能:

<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
p:title="aa"
p:artlist="bb"
>

     <property name="tracks">
                <list>
                    <value>001</value>
                    <value>002</value>
                    <value>003</value>
                </list>
     </property>
     </bean>

但是我们注意到不能使用p-命名空间来装配集合,可以使用Spring util命名空间中的一些功能来简化BlankDiscbean。
首先,需要在XML中声明util-命名空间及其模式:

这里写图片描述

可以利用<util:list>,我们可以将集合属性转移到BlankDisc bean之外,并将其声明到单独的bean之中,如下:

这里写图片描述

然后:

这里写图片描述

如下是util-命名空间提供的所有元素:

这里写图片描述


2.5 导入和混合配置

在典型的Spring应用中,我们可能会同时使用自动化和显式配置。即便你更喜欢通过JavaConfig实现显式配置,但有的时候XML却是最佳的方案。

但是你尽可以将JavaConfig的组件扫描和自动装配和/或XML配置混合在一起。我们至少需要有一点显式配置来启用组件扫描和自动装配。

关于混合配置,第一件需要了解的事情就是在自动装配时,它并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。


2.5.1 在JavaConfig中引用XML配置

我们临时假设CDPlayerConfig已经变得有些笨重,我们想要将其进行拆分。
方案一:
将BlankDisc从CDPlayerConfig拆分出来,定义到它自己的CDConfig类中,如下所示:

这里写图片描述

compactDisc()方法已经从CDPlayerConfig中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig:

这里写图片描述

方案二:

或者不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类中使用@Import将两个配置类组合在一起:

这里写图片描述


2.5.2 在XML配置中引用JavaConfig

在XML中,我们可以使用import元素来拆分XML配置。
比如,假设希望将BlankDisc bean拆分到自己的配置文件中,该文件名为cd-config.xml。我们可以在XML配置文件中使用<import>元素来引用该文件。

<import resource="cd-config.xml"/>
<beans>

</beans>

现在,我们假设不再将BlankDisc配置在XML之中,而是将其配置在JavaConfig中,CDPlayer则继续配置在XML中。问题来了,也就是基于XML的配置该如何引用一个JavaConfig类呢?

注意:<import>元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。

但是,有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:<bean>元素。为了将JavaConfig类导入到XML配置中,我们可以这样声明bean:

<bean class="com.javaAction.entity.CDPlayerConfig"/>
<bean id="cdPlayer"
    class="com.javaAction.entity.CDPlayer"
    c:cd-ref="compactDisc"
/>

不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>或@ComponentScan)。


2.6 小结

Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
Spring中装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。建议尽可能使用自动化配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术闲聊DD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值