《Spring in Acton》第四版读书笔记之如何装配Bean

Spring中装配的主要方式

  • XML显式配置
  • Java显式配置
  • 使用Java隐式扫描bean并自动装配

三种方式各有好处,作者认为选哪种只是口味问题,但强烈建议使用第三种(使用Java隐式扫描bean并自动装配),并且建议即使要用显式配置,也尽量使用Java配置,因为Java更好用(powerful),具有类型安全检查,并且更好重构。只有在XML有命名空间可以很方便使用,而JavaConfig中没有的时候才使用XML显式配置。

使用Java隐式扫描bean并自动装配

Spring的这种方案实在是太好用了,应该尽量利用这种机制。这个方案中,我们要做的事情包括两个方面:让bean可以被扫描以及在使用bean的地方声明注入bean。这样,Spring就可以扫描并生成bean,并且在要使用的地方注入bean了。

让bean可以被扫描到

  1. 在类上加@Component标签,标记这个类可以作为组件供其它类使用。
  2. 写一个Java配置类,加上@Configuration和@ComponentScan标签,(也可以用xml实现,略)

在启动项目时,Spring就会在这个配置类所在的目录下查找有加@Component标签的类,并且自动生成bean了。

在类上加@Component标签:

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.print("Playing " + title + " by " + artist);
    }

}

配置类加加上@Configuration和@ComponentScan标签:

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

@Configuration
@ComponentScan
public class CDPlayerConfig {
}


标注被扫描bean的位置

有时候,配置类和要扫描的类不在同一个目录下,此时要标明要扫描的类所在的包或者直接标明要扫描的类。

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


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

为bean定义id

Spring会默认将类的第一个字母改为小写,作为bean的id,如果一个类有多个bean,或者只是想改用其它id,可以通过以下两种方式,定义bean的id:


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

package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}

在使用bean的地方声明注入bean

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 CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }

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

}

要使用bean的地方(类成员变量,构造函数或普通方法都可以),加 @Autowired或 @Inject标签,注入bean,就可以开始使用了。从Autowired或和Inject的名称可以看出,其实wire(装配)和inject(注入)的意思差不多,都是表示将bean连接起来。其实wire的意思就是连接。

因为只有SgtPeppers实现了CompactDisc,所以上面的例子会自动注入一个SgtPeppers实例,有时,有多个bean都实现了CompactDisc接口,那么在注入时,需要声明注入的bean的id。代码如下:

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

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

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

}

验证自动化注入


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

    @Rule
    public final StandardOutputStreamLog log = new StandardOutputStreamLog();

    @Autowired
    private MediaPlayer player;

    @Autowired
    private CompactDisc cd;

    @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());
    }

}

为了避免不同操作系统换行符的不同,我将原代码中的换行符去掉了,相应的,将SgtPeppers的play方法的println也改成print了。

在Java配置文件中显式注册bean

定义配置文件


@Configuration
public class CDPlayerConfig {

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

}

CDPlayerConfig加上Configuration标签,标注为配置文件,但是去掉了ComponentScan标签,所以要显式定义bean。

使用Bean标签

Bean标签表示该方法返回的bean会被注册到Spring容器里,并且id和方法名称一样,也可以使用name参数定义id。

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

下面的例子通过构造函数将CompactDisc注入CDPlayer:

@Configuration
public class CDPlayerConfig {

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

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

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

}

Config文件中,两个方法内调用相同的Bean方法,是否会生成两个sgtPeppers实例?如果没有使用Spring的话,是会有两个实例,使用Spring机制之后,CDPlayer并不是在调用sgtPeppers(),而是从Spring容器中获取bean,所以只有一个bean。这个调用过程有些让人困惑,所以,更好的方式是直接引用bean:

@Configuration
public class CDPlayerConfig {

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

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

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

}

在这里,cdPlayer和anotherCdPlayer这两个方法,因为在Java配置文件中,并且加了@Bean标签,都会尝试自动注入CompactDisc类型的bean。Spring会通过扫描目录,查找Java配置文件中带Bean标签的方法,甚至查找XML配置文件来找到需要的bean。在这里例子中,是通过在带有Configuration的Java配置文件CDPlayerConfig(这里正好是同一个文件)中查找带有Bean标签的compactDisc()方法,找到对应的bean的,这个bean是一个SgtPeppers实例。

验证使用Java配置文件显示注册bean

public class SgtPeppers implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "The Beatles";
    private int times = 0;

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

}

以下是测试代码:

package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

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

    @Rule
    public final StandardOutputStreamLog log = new StandardOutputStreamLog();


    @Autowired
    @Qualifier("cdPlayer")
    private MediaPlayer player;

    @Autowired
    @Qualifier("anotherCdPlayer")
    private MediaPlayer anotherPlayer;

    @Test
    public void play() {
        player.play();
        assertEquals(
                "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles,1",
                log.getLog());
        log.clear();
        anotherPlayer.play();
        assertEquals(
                "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles,2",
                log.getLog());
    }

}

笔者注:我在SgtPeppers类中加了个成员变量times,这个变量不是static的,而是对象级别的,所以通过验证times是否递增,可以验证两个player是否使用的是同一个bean。测试通过,说明使用的确实是同一个bean。另外,为了避免不同操作系统换行符的不同,我将原代码中的换行符去掉了,相应的,将SgtPeppers的play方法的println也改成print了。

在XML配置文件中显式注册bean

混用Java和XML配置文件显式注册bean

欢迎扫描下方二维码关注微信公众号【谈谈IT】,第一时间获取最新文章。
欢迎关注同名公众号

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值