Spring学习笔记之自动化装配Bean

在Spring中可以使用Java代码、XML和自动化装配三种方式来装配Bean。从便利性角度来说,最强大的还是Spring的自动化配置,如果Spring能够进行自动化装配的话,那何苦还要显式的将这些Bean装配在一起呢?

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

组件扫描:Spring会自动发现应用上下文中所创建的Bean;

自动装配:Spring自动满足bean之间的依赖。

为了阐述组件扫描和装配,我们需要创建几个Bean,它们代表了一个音响系统中的组件。

一、创建可被发现的bean

定义CD的一个接口:

package cn.javacodes.spring.beans.soundsystem;
public interface CompactDisc {
    void play();
}

CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。

下面创建一个CompactDisc的实现:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.stereotype.Component;
@Component
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

这里需要注意的是该类使用了@Component注解,表明该类会作为组件类,并告知Spring要为这个组件创建bean。但是在这之前,由于默认组件扫描是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。

下面的这个类展现了完成这件事情的最简介配置方式:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于cn.javacodes.spring.beans.soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解标示的类,并在Spring中自动为其创建一个bean。

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

<?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:configurator="http://www.springframework.org/schema/c"
       xmlns:avalon="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">
    <context:component-scan base-package="cn.javacodes.spring.beans.soundsystem"/>
</beans>

尽管我们可以使用XML的方案来启用组件扫描,但在后面的讨论中,更多的还是会使用基于Java的配置。

下面我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来了。

package cn.javacodes.spring.beans.soundsystem;
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 static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;
    @Test
    public void cdShouldNotBeNull(){
        assertNotNull(cd);
    }
}

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

为了证明这一点,在测试代码中有一个CompactDisc属性,并且这个属性带有@Autowired注解,以便于将CompactDisc bean注入到测试代码中,有关与@Autowired注解的更多内容将在后面讲述。最后,有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到了测试代码中。

这个代码应该能够通过测试,并以测试成功的颜色显示。

二、为组件扫描的bean命名

Spring上下文中所有的bean都有一个id。在前面的例子中,即使我们并没有明确的给定Transfer bean一个id,但Spring会根据类名为其给定一个id。具体来讲,Spring会默认给定一个将类名首字母变为小写的id,例如上例中将给定的id为transfer。

如果想为这个bean给定不同的id,你需要做的就是将你所想要给定的id作为参数传递给@Component注解。例如:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.stereotype.Component;
@Component("transfer")
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

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

package cn.javacodes.spring.beans.soundsystem;
import javax.inject.Named;
@Named("transfer")
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差别,不过大多数场景种它们使可以相互替换的。但是推荐使用@Component而不是@Named,因为@Component注解看起来更加能够知道它是干什么的。

三、设置组件扫描的基础包

现在我们已经知道,默认情况下@ComponentScan注解会扫描当前配置类所在的包及其子包,但我们可能更希望将配置类与其它类放在不同的包中,那么为了指定不同的基础包,可以将指定的包名作为参数传递给@ComponentScan注解即可:

@Configuration
@ComponentScan("cn.javacodes.spring.beans.soundsystem")
public class CDPlayerConfig {
}

当然也可以更加清晰的指明其是基础包,使用basePackages属性:

@Configuration
@ComponentScan(basePackages = "cn.javacodes.spring.beans.soundsystem")
public class CDPlayerConfig {
}

这里我们发现basePackages属性是复数形式,我们猜测它是否可以指定多个基础包呢?答案是正确的,如果想要指定多个包,那么只需要将要扫描的包放到一个数组中即可:

@Configuration
@ComponentScan(basePackages = {"cn.javacodes.spring.beans.soundsystem", "cn.javacodes.spring.beans.video"})
public class CDPlayerConfig {
}

上面的方式中,包名以简单的字符串进行表示,当然这是可以的。但是如果我们日后对代码进行重构,很有可能就会出现问题,所以这种通过简单的字符串来配置基础包的方式是不安全的。为了解决这个问题,我们可以将其指定为包中所包含的类或接口:

package cn.javacodes.spring.configuration;
import cn.javacodes.spring.beans.soundsystem.CDPlayer;
import cn.javacodes.spring.beans.video.DVDPlayer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}

注意:这里不再使用basePackages属性,取而代之的是basePackageClasses属性。我们不再使用String类型的包名来指定包,而是为basePackageClasses属性设置的数组中包含了类。这些类所在的包会作为组件扫描的基础包。

当然,使用组件类直接给basePackageClasses属性并不是很好的方式,我们可以考虑在包中创建一个用来进行扫描的空标记接口。通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码。

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

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

比如下面的CDPlayer类,它的构造器使用了@Autowired注解,表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并会传入一个可以设置给CompactDisc类型的bean:

package cn.javacodes.spring.beans.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 setCompactDisc(CompactDisc cd){
        this.cd = cd;
    }

在Spring完成初始化bean之后,它会尽可能的去满足bean的依赖。实际上,Setter方法并没有什么特殊之处,@Autowired可以出现在任何方法上。

假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被封装起来。

如果没有匹配的bean,那么在应用上下文创建的时候,Spring将会抛出一个异常。为了避免异常,可以将@Autowired的required属性设置为false,Spring会尝试执行自动匹配,但是如果没有匹配的bean的话,Spring会让这个bean处于未装配的状态:

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

但是,把required属性设置为false的时候你需要注意,如果你的代码中没有null检查的话,这个处于未装配状态的属性有可能会出现空指针异常(NullPointerException)。

如果有多个bean都能满足依赖关系的话,Spring会抛出一个异常,表明没有明确指定要选择哪个bean进行装配,有关于Spring自动化装配的歧义性的问题,我会在后续的文章中进行说明。

@Autowired是Spring特有的注解,如果你不希望在代码中到处使用Spring特有的注解的话,那么可以考虑使用@Inject注解对其进行替换,例如:

package cn.javacodes.spring.beans.soundsystem;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class CDPlayer {
    private CompactDisc cd;
    @Inject
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }
    public void play(){
        cd.play();
    }
}

@Inject注解来源于Java依赖注入规范,同@Named注解一样,@Inject注解与@Autowired注解存在一些细微的差别,但大多数情况下它们可以进行相互替换。

五、验证自动装配

我们修改一下测试类CDPlayerTest,使其能够借助CDPlayer bean播放CD:

package cn.javacodes.spring.beans.soundsystem;
import cn.javacodes.spring.beans.MediaPlayer;
import cn.javacodes.spring.configuration.CDPlayerConfig;
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@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("正在播放周传雄/小刚的专辑:transfern", log.getLog());
    }
}

该类中,除了注入CompactDisc,还将CDPlayer bean注入到了测试代码中(更为通用的MediaPlayer类型)。在play()方法中,我们可以调用CDPlayer的play()方法并断言它的行为与你的预期是否一致。
自动化装配Bean还有更多的细节,我会在后续的文章中进行阐述。

 

 

若未注明来源即为原创文章,本站使用“署名·非商业用途·保持一致”版权协议
转载请注明:
转载自老H博客
本文链接地址: Spring学习笔记之自动化装配Bean

共有 0 条评论

Top