Spring in Action——装配bean

Spring配置的可选方案

虽然以前介绍过,但是再来介绍一遍:

  • xml中显式配置
  • java代码中显式配置
  • 隐式的bean发现机制和自动装配
    乍一眼看去,三种可选方案可能会让spring变得更加复杂,因为每种配置技术所提供的功能会有一些重叠,在某些场景中不知道选择哪种。
    但是,在大多数场景下,全凭个人喜好使用。

即便如此,作者推荐显式的配置越少越好。当然,不得不使用显式配置的时候,比如有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候。推荐使用类型安全比XML更强大的JavaConfig。最后,只有当你想要使用遍历的XML命名空间,并且在JavaConfig中没有同样实现时,才应该使用XML;

自动化装配bean

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

  • 组件扫描
  • 自动装配
    为了阐述组件扫描和装配,我们需要创建几个bean,他们代表了一个音响系统中的组件。首先,要创建CompactDisc类,Spring会发现它并且将其创建为一个bean。然后,会创建一个CDPlayer类,让Spring发现它并将CompactDisc注入进来。
    创建可被发现的bean
    我们这边以CD为例子:将CD插入(注入)到CD播放器里。
package soundsystem;
public interface CompactDisc{
	void play();
}

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

package soundsystem;
import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc{
    //披头士的碟子
    private String title="Sgt,Pepper's Lonely Hearts Club Band";
    private String artist="The Beatles";
    public void play(){
        System.out.println("Playing "+title+" by "+artist);
    }
}

使用了@Component注解,这个简单的注解表明该类会作为组件类,并告之Spring要为这个类创建bean。
不过组件扫描是默认不启动的,还需要显示的配置一下Spring从而命令它去寻找带有@Component注解的类,并为其创建bean。

package soundsystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig{
}

类CDPlayerConfig通过Java代码定义了Spring的装配规则,它使用了@ComponentScan注解,这个注解能够在Spring中启动组件扫描,如果没有其他配置,它会默认扫描与配置类相同的包。比如假如这个类位于soudsystem包中,Spring会扫描这个包以及它的子包,查找所有带有@Component注解的类。
当然,如果你更倾向于使用XML来启动组件扫描的话,那么可以使用Spring context命名空间的<context:component-sacn>元素:

<beans>
	<context:component-scan base-package="com.soundsystem">
</beans>

我们现在对功能进行一番尝试,我们创建一个简单的JUNIT测试,它会创建Spring上下文,判断这个CompactDisc是不是真的创建出来了:

package 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 soundsystem.CDPlayerConfig;
import soundsystem.CompactDisc;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerConfigTest {
    @Autowired
    private CompactDisc cd;
    @Test
    public void cdShouldNotBeNull(){
        assertNotNull(cd);
    }
}

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

现在我们更深入的探讨@ComponenetScan@Component,看一下使用组件扫描还能做什么。

为组件扫描的bean命名
Spring的ApplicationContext会给所有的bean定一个ID。虽然我们前面的例子没有明确的设置ID,但是Spring会根据类名为其指定一个ID,具体做法就是将类名的第一个字母变为小写,如SgtPeppers类的ID为sgtPeppers。
如果要设置不同的ID:

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

另外还有一种bean的命名方式,这种方式不使用@Component注解,而是使用JAVA DI中所提供的@Named注解来为bean设置ID。

import javax.inject.Named;

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

Spring支持将@Named作为@Component注解的替代方案,两者之间有一些细微的差别,但是大多时候可以替换。
话虽如此,我们@Component更清楚的表达它是做什么的。

补充:id如何使用呢?在显式装配中:两个都叫anothername,这使得容器知道它注入的是哪个bean。

@Configuration
public class CDPlayerConfig {
    @Bean(name = "anothername")
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer(CompactDisc anothername){
        return new CDPlayer(anothername);
    }
}

设置组件扫描的基础包

到目前为止,我们没有为@ComponentScan设置任何属性,这意味着,按照默认规则,它会以当前包作为基础包(base package)来扫描组件。
有一个原因促使我们明确设置基础包,我们想要把配置类单独放在一个包中,使其与其他代码区分开来,所以:


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

或者@ComponentScan(basePackages="soundsystem")
或者指定多个包:@ComponentScan(basePackages="soundsystem","video")
在上面的例子中,所设置的基础包是String类型表示的。虽然可以,但是这是不类型安全(not type-safe)的。如果重构代码,那么所指定的基础包可能出现错误。
除了设置为简单的String类型之外,还有另一种方法:

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

可以看到,basePackages属性被替换成了basePackageClasses(都是复数,可以设置多个)。

通过为bean添加注解实现自动装配
简单来说,自动装配就是让Sprig自动满足bean依赖的一种方法, 在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。

package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer {
    private CompactDisc cd;
    @Autowired
    public CDPlayer(CompactDisc cd){
        this.cd=cd;
    }
    public void play(){
        cd.play();
    }
}

@Autowired注解不仅能够用在构造器上,还能用在属性Setter方法上。比如说,如果CDPlayer有一个setCompactDisc()方法,那么可以采用如下的注解形式进行自动装配:

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

在Spring初始化bean之后,它会尽可能去满足bean的依赖,在本利中,依赖是通过带有@Autowired注解的方法进行声明的,也就是setCompactDisc()。
实际上,Setter方法并没有什么特殊之处,@Autowired注解可以用在类的任何方法上,不一定非要叫setxxx可以叫insertxxx或者其他都可以。
如果没有匹配的bean,那么在ApplicationContext创建的时候,Spring会抛出一个异常,为了避免异常的出现,可以将@Autowired的required属性设置为false:

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

设置为false后,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring会让合格bean处于未装配的状态,但是把required属性置为false时,需要谨慎对待:如果代码没有进行null检查的话,这个未装配状态的属性可能会出现NullPointerException
而如果有多个bean都满足依赖的话,Spring会抛出一个异常,表明没有明确致命哪个bean进行自动装配,之后我们会讨论自动装配中的歧义

@Autowired是Spring特有的注解,如果你不愿意导出使用Spring的特定注解来完成自动装配,可以使用@Inject。这两个可以使用任意一个。

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

验证自动装配
为了验证注入,我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerConfigTest {
    @Rule
    public final StandardOutputStreamLog log=new StandardOutputStreamLog();
    @Autowired
    private CompactDisc cd;
    @Autowired
    private CDPlayer player;
    @Test
    public void cdShouldNotBeNull(){
        assertNotNull(cd);
    }
    @Test
    public void play(){
        player.play();
        assertEquals("Playing Sgt,Pepper's Lonely Hearts Club Band by The Beatles",log.getLog());
    }
}

通过Java代码装配bean

有时候自动化配置方案行不通,因此需要配置Spring。比如说想将第三方库中的组件配置到应用中。

在进行显式配置的时候,推荐使用JavaConfig更好,因为它类型安全对重构友好。

创建配置类
在之前的程序清单中,我们看到过JavaConfig。

@Configuration
public class CDPlayerConfig{

}

创建JavaConfig类的关键在于为其添加@Configuration注解,它表明这是一个配置类,该类应该包含在Spring的ApplicaitionContext中如果创建bean的细节。
在本节中,我们更加关注显式配置,因此,我们将@ComponentScan移除不用,这时候CDPlayerConfig类就没有任何作用了,并且会出现BeanCreationException异常。

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

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

@Bean会告诉Spring这个方法将返回一个对象,该对象要注册为Spring的ApplicationContext的bean。方法体中含有产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果想设置其他ID,可以使用:

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

借助JavaConfig实现注入

@Configuration
public class CDPlayerConfig {
    @Bean
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer(){
        return new CDPlayer(sgtPeppers())
    }
}

这里我们为唱片机注入了一张名为sgtPeppers的唱片。
但是假如有如下的方法:(现在我们有两个唱片机了)


@Configuration
public class CDPlayerConfig {
    @Bean(name = "anothername")
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer(){
        return new CDPlayer(sgtPeppers())
    }
    @Bean
    public CDPlayer anotherCdPlayer(){
        return new CDPlayer(sgtPeppers())
    }
}

虽然在物理上,没法将一张CD放入两个唱片机,然而“singleton”使得我们能够实现这种物理无法实现的方式。我们这里是默认singleton的,两个CDPlayer bean会得到相同的SgtPeppers实例。
可以看到通过调用方法来引用bean的方式有点令人困惑,其实还有一种方式:

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

在这里,cdPlayer()方法请求一个CompactDisc作为参数,当Spring调用CDPlayer()创建CDPlayer bean的时候,它会自动装配一个CompactDisc到配置方法之中。借助这种技术,cdPlayer()方法也能将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。

通过这种方法引用其他的bean通常是最佳选择,因为它不会要求将CompactDisc声明到同一配置类中,甚至没有要求CompactDisc必须要在JavaConfig里面声明。

我们上面都介绍的是使用构造器,如果使用setter方法:

@Configuration
public class CDPlayerConfig {
    @Bean(name = "anothername")
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer(CompactDisc anothername){
        CDPlayer cdPlayer=new CDPlayer(anothername);
        cdPlayer.setCd(anothername);
        return cdPlayer;
    }
}

通过XML装配bean

还有一种可选方案XML,虽然不太合乎大家的心意,但是存在很长时间了,还是有必要了解一下。

创建XML配置规范
在使用XML的时候,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置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">

</beans>

声明一个简单的<bean>
我们之前介绍过很详细的内容了,我们知道<bean>,我们可以按照如下方式声明CompactDisc 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="compactDisc" class="soundsystem.SgtPeppers"></bean>
</beans>

借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:使用<bean>并指定class。
但是在XML声明DI,可以有多种可选的风格:

  • <constructor-arg>
  • 使用Spring3.0所引入的c-命名空间

两者区别就是是否冗长繁琐,比如前一种就更加冗长。但是有些事前者才能做到。
按照现在的定义,CDPlayer bean有一个接受CompactDisc类型的构造器。这样,我们就有了一个很好的场景来学习如何注入bean。
我们在XML声明CDPlayer并通过ID引用SgtPeppers:

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

或者使用c命名空间:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       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">
    <bean id="compactDisc" class="soundsystem.SgtPeppers">
    </bean>
    <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
</beans>

介绍一下c命名空间,他在beans中包含了一段xmlns:c="http://www.springframework.org/schema/c",表明我们可以使用c命名空间:
在这里插入图片描述
构造器参数名称有些怪异,因为这需要在编译代码的时候,将调试标志保存在类代码中。
c命名空间我们就介绍到这里。

** 将字面量(参数)注入到构造器中**
假设有一个新的CD:

package soundsystem;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;

  public BlankDisc(String title, String artist) {
    this.title = title;
    this.artist = artist;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       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">
    <bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg  value="hey"/>
        <constructor-arg><null/></constructor-arg>
    </bean>
    <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
</beans>

设置属性
介绍完构造器注入,我们这里介绍属性注入。

package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

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

    public void play() {
        cd.play();
    }
}

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">
    <bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg  value="hey"/>
        <constructor-arg><null/></constructor-arg>
    </bean>
    <bean id="cdPlayer" class="soundsystem.CDPlayer">
        <property name="cd" ref="compactDisc"/>
    </bean>

</beans>

name属性后的,是方法名:如setXxx,name后写xxx,(第一个字母小写)。

混合配置

在javaConfig里面引用XML
现在,我们临时假设CDPlayerConfig有些笨重,我们想将BlankDisc从CDPlayerConfig拆分出来,定义到自己的CDConfig类中,如下所示:

package soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDConfig {
  @Bean
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }
}

所以,现在CompactDisc()方法已经从CDPlayerConfig里面移除了,我们需要一种方法将两个类组合在一起,一种是使用@Import注解导入CDConfig:

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

}

另一种更好的方法,不是在CDPlayerConfig里使用@Import,而是创建一个更高级别的SoundSystemConfig类,将其余两个配置类组合在一起。

package soundsystem;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

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

在XML配置中引用JavaConfig
暂略。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值