本系列博客为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的自动装配方式讲完了, 关于另外两种显式装配方法虽然在上一篇博客中也粗略提到过, 但是还有许多细节用法还没有讲到, 我们将在后续的博客里继续探究.