Spring学习笔记(二) --- 装配Bean之自动化装配

本系列博客为spring In Action 这本书的学习笔记

前面谈到了装配Bean可以有XML装配和基于Java的装配, 但是这两种装配都属于显式装配, 也就是说我们得手动写配置文件, 那么有没有更简便的方法呢? 就是我们今天介绍的自动化装配 !


装配

在说自动化装配之前, 我们先来介绍一下装配, 毕竟在上一篇博客中只是粗略地提到过.

创建应用对象之间协作关系的行为称为装配, 这也是依赖注入的本质.

Spring提供了3种装配机制, 它们分别为:

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

一般来说, 这三种都是可以完成装配的, 选择哪种装配方式取决于开发者. 但是就我自己而言, 与书中作者一样, 更倾向于使用自动装配, 基于Java配置次之, 最后才是使用XML配置.
怎么说, Spring本身提供给了我们自动装配这种强大的装配机制, 我们就应该好好利用它, 单是从便利性这方面就能博得大家的青睐, 显式配置越少, 代码就越容易维护. 而基于Java和XML相对比, 作者也提到了, JavaConfig的安全性和功能强大性都比XML要好.
但是, 究竟使用哪种就看个人喜好了, 当然, 在有些情况下, 三种装配方式混搭使用也是可以的.

在上一篇博客中, 我们已经小小地使用了XML和基于Java的装配方式, 那么今天就来看一下自动装配 .

自动化装配Bean

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

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

组件扫描和自动装配相互组合, 就能将你的显式配置降到最低 .

是不是一脸懵逼? 没事, 来个例子你就懂了.

这次我们的例子是CD和CD播放器.


1. 组件扫描 : 创建可被发现的Bean

首先我们先定义一个CD的接口, 以便后面实现具体的CD.

程序1 : CD接口

//CD接口
public interface CompactDisc {
    public void play();
}

接下来我们具体实现一盘周杰伦的CD(专辑:Jay)

程序2 : 实现名为Jay的CD

//Component注解表明该类会作为组件类, 并告知Spring要为这个类创建bean.
@Component
public class Jay implements CompactDisc {
    private String title = "专辑Jay";
    private String artist = "周杰伦";

    public void play() {
        System.out.println("正在播放" + artist + "的" + title);
    }
}

可以注意到, 程序中使用了@Component注解(component 组件), 这个注解表明该类会作为组件类, 并会告知Spring要为这个类创建Bean.
我们不必显式配置Bean, 因为该类使用了@Component注解, Spring会将事情处理妥当 .

对比上一篇博客, 无论是使用XML还是基于Java, 都进行了显式Bean的声明.
比如在XML中:

<bean id="quest" class="com.Knight.SlayDragonQuest">
    <constructor-arg value="#{T(System).out}" />
</bean>

比如在Java中:

 @Bean
 public Quest quest(){
     return new SlayDragonQuest(System.out);
 }

不过, 组件扫描默认是不启用的, 所以我们还需显式配置一下Spring, 从而命令它去寻找带有@Component注解的类, 并为其创建bean.

同样的, 显式配置有两种方式, XML和基于Java.

(1) 在Java代码中定义Spring的装配规则启用组件扫描.

程序3 : CDPlayerConfig.java 通过Java代码定义Spring的装配规则

//该类通过Java代码定义了spring的装配规则, 其使用了@ComponentScan注解, 这个注解能在Spring中启用组件扫描
//用@Configuration注解该类, 等价于XML中配置beans;
//用@Bean注解方法, 等价于XML中配置bean(顺便提到一下)

@Configuration
@ComponentScan
public class CDPlayerConfig {
}

如果没有其他配置的话, @ComponentScan默认会扫描与配置类相同的包, 因为本类位于soundSystem包中, 所以Spring会扫描这个包以及这个包下所有的子包, 查找带有@Component注解的类.
这样, @ComponentScan就能发现Jay, 并自动为其创建一个Bean.

也可以使用XML来启用组件扫描.

(2) 使用XML配置启用组件扫描

程序4 : CDPlayer.xml 通过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: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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

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

</beans>

我们把所有的事情都抛给Spring , 可是它到底扫描成功了吗? Bean到底创建成功了吗? 接下来测试一下.

为了测试组件扫描的功能, 创建一个简单的JUnit测试, 它会创建Spring上下文, 并判断Jay是不是真的创建出来了.

程序5 : 测试组件扫描能够发现Jay

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath*:CDPlayer.xml") //使用XML启用组件扫描
@ContextConfiguration(classes = CDPlayerConfig.class) //使用基于Java配置启用组件扫描
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull(){
        //若程序运行没有出现异常, 说明断言正确
        assertNotNull(cd);
    }
}

在上述代码中, 分别测试了基于Java代码和XML配置是否成功, 也证实了组件确实发现了Jay.

CDPlayerTest使用了Spring的SpringJUnit4ClassRunner, 以便在测试开始的时候自动创建Spring的应用上下文. 注解@ContextConfiguration会告诉它需要在CDPlayerConfig或CDPlayer.xml中加载配置文件. 因为配置文件中都包含了ComponentScan, 所以最终的应用上下文中都包含Jay Bean .
所以, 所有带有@Component注解的类, Spring都会自动为其创建一个Bean.

接下来, 会更加深入地了解@Component和@ComponentScan, 看看它们还能做哪些事.

2. 为组件扫描的Bean命名

Spring应用上下文中所有的Bean都会有一个ID, 在上一篇博客中, 我们也写过了. 那么你是不是会想, 那Spring自动创建地Bean的ID是什么呢?
Spring应用上下文自动创建的Bean的ID为将该类的名字的第一个字母小写, 也就是说, Jay的Bean的ID为jay.

那么现在想自定义Spring自动创建的Bean的名字该怎么办?
我们可以给@Component传递参数, 假如, 想给Jay Bean起名为jayZhou, 可以这样做:

@Component("jayZhou")
public class Jay implements CompactDisc{
    ...
}

除了这种命名方式, 还可以使用Java依赖注入规范提供的@Named注解来为Bean命名.

@Named("jayZhou")
public class Jay implements CompactDisc{
    ...
}

3. 设置组件扫描的基础包

@ConponentScan用来扫描带有@Conponent注解的组件, 如果没有其它配置, 它会默认扫描当前包以及当前包的所有子包.
但是, 如果想把所有的JavaConfig文件都统一放在一个包里呢? 当前包下并没有JavaConfig需要扫描的类, 又该怎么办呢?

我们可以给@ConponentScan注解里加参数, 指定需要扫描的包. 可以一次指定一个包, 也可以同时指定多个包

一次指定一个包:

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

同时指定多个包:

@Configuration
@ComponentScan(basePackages={"SoundSystem", "Knight"})
public class CDPlayerConfig { }

一次只指定一个包的时候, 可以省略掉前面的basePackages:

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

4. 自动装配 : 通过为Bean添加注解实现自动装配

前面只是使用@Component和@ComponentScan注解进行组件扫描, 可是要怎样将这些扫描到的组件自动装配在一起呢? 这就是我们的金刚钻了@Autowired .

先来看一个例子, 通过程序来解释自动装配@Autowired是怎么回事.

现在有了CD, 要将CD放入播放器里面播放啦.
同样的, 先抽象出媒体播放器的接口, 然后再实现CD播放器.

程序6 : 媒体播放器接口

public interface MediaPlayer {
    public void play();
}

程序7 : 实现CD播放器

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

    @Autowired //(1)这里使用了@Autowired注解
    public CDPlayer(CompactDisc cd){
        this.cd = cd;
    }

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

可以注意到(1)处, 也就是CDPlayer的构造方法处使用了@Autiwired注解.
先来分析一下: CDPlayer构造方法的参数为CompactDisc类类型的对象cd, 也就是说, 将CompactDisc Bean注入到CDPlayer中了.
回想一下, 在上一篇博客中, 将Quest注入到BraveKnight中,我们使用了XML和基于Java的配置文件先声明Bean, 然后再进行注入. 而在这里, 只需要使用@Autowired注解就完成了配置文件完成的事.

什么意思呢?
就是Spring在初始化CDPlayer这个Bean的时候, 发现其构造方法需要依赖CompactDisc Bean, 所以, Spring就会去满足这种依赖关系, 也就是将CompactDisc这个Bean自动装配进来.
这样讲明白了吗?

不仅是在构造方法中, 只要在任何方法前声明@Autowired注解, 只要该方法有参数, Spring都会去尝试满足这种依赖关系.
比如CDPlayer有一个setCompactDisc()方法如下:

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

同样的, Spring也会将CompactDisc Bean装配进这个方法.

但是如果没有匹配的Bean可以被装配进来, 那么Spring在应用上下文创建的时候, 会抛出一个异常. 为了避免异常的出现, 可以将@Autowired的required属性设置为false, 但是需谨慎. 因为这样如果没有匹配的Bean, Spring就会让当前Bean处于未装配的状态.

5. 验证自动装配

好了, 自动装配的两件事组件扫描和自动装配已经干完了, 那接下来就来验证一下装配有没有成功. 我们对之前的CDPlayerTest进行修改:

程序8 : 验证自动装配

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath*:CDPlayer.xml")
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    //这是来源于System Rules库的一个JUnit规则, 该规能够基于控制台的输出编写断言.
    //在这里, 我们断言Jay.play()方法的输出被发送到了控制台上.
    @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("正在播放周杰伦的专辑Jay", log.getLog());
    }
}

到这里, 就将装配Bean的自动装配方式讲完了, 关于另外两种显式装配方法虽然在上一篇博客中也粗略提到过, 但是还有许多细节用法还没有讲到, 我们将在后续的博客里继续探究.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值