在Spring in action里这样描述,Spring从两个角度来实现自动化装配
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean
自动装配(autowiring):Spring自动满足bean之间的依赖
看上去挺神秘的,结合一个具体例子来看看,这里需要将一个音响系统中的bean组件给组装起来
首先CD播放器,它的使命是播放CD,如果没有CD它基本没啥用,也就是依赖于CD
这里先定义CD的一个接口
package com.maoxiaomeng.soundsystem;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CompactDisc
* Author: lihui
* Date: 2018/6/17 下午11:48
*/
public interface CompactDisc {
void play();
}
它是一个接口,定义了CD播放器对一个CD能够进行的操作
定义一个CompactDisc的实现,昨日重现
package com.maoxiaomeng.soundsystem;
import org.springframework.stereotype.Component;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Yesterday
* Author: lihui
* Date: 2018/6/17 上午0:49
*/
@Component
public class Yesterday implements CompactDisc {
private String title = "Yesterday Once More";
private String artist = "Carpenters";
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
同样,Yesterday的具体内容不重要,这里Yesterday类使用了@Component注解,这个注解表明该类会作为组件类,告知Spring要给这个类创建bean,Spring会来做好
要注意的是,虽然Spring会来给定义了@Component注解的类创建bean,但是一开始它并不知道哪些需要创建,所以需要进行组件扫描来寻找带有该注解的类,找到了才能创建bean
组件扫描默认是不开启的,因此需要显示配置Spring,命令它去扫描寻找带@Component的类,这里用到了@ComponentScan注解完成
package com.maoxiaomeng.soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayerConfig
* Author: lihui
* Date: 2018/6/17 上午0:11
*/
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
这个类使用了@ComponentScan注解,使得在Spring中启用组件扫描
对于扫描的范围,如果没有其它配置,@ComponentScan默认会扫描和配置类相同的包,由于CDPlayerConfig类在soundsystem包里,因此Spring会扫描这个包以及所有子包,为了查找带有@Component注解的类;这样最终发现了CompactDisc类,然后就会在Spring中自动为它创建一个bean
到了这里,似乎只创建了两个类,就 完成了组件扫描功能,这里可以通过JUnit做下测试,创建Spring上下文,来判断CompactDisc是否真的创建出来
创建一个CDPlayer类,不用添加信息,只为了添加测试类
package com.maoxiaomeng.soundsystem;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayer
* Author: lihui
* Date: 2018/6/17 下午11:09
*/
public class CDPlayer {
}
测试类
package com.maoxiaomeng.soundsystem;
import static org.junit.Assert.*;
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;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayerTest
* Author: lihui
* Date: 2018/6/20 下午1:10
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}
CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,这样在测试开始时自动创建Spring的应用上下文
@ContextConfiguration注解说明需要在CDPlayerConfig中加载配置;因为CDPlayerConfig累中包含了@ComponentScan,那么应用上下文中也包含了CompactDisc bean
为了证明这这点,测试类里定义了一个 CompactDisc类型属性,带有@Autowired注解,以便将CompactDisc bean注入 到测试代码中
最后测试方法里断言cd的属性不为null;如果它不为null,说明Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并注入到测试代码中
这段测试代码执行是没问题的
Spring应用上下文 中,所有的bean都会给定一个ID,比如Yesterday bean的ID为yesterday,加入你想给bean自定义设置ID,那就将ID传给@Component注解,比如
@Component("oncemore")
此时,@ComponentScan没有设置任何属性,name它扫描的范围就是所在包为基础扫描组件,加入想自定义扫描的基础包,直接注解的value属性指明即可
除此之外,还可以直接指定组件类,不过basePackages属性要修改为basePackageClasses,如下
package com.maoxiaomeng.soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayerConfig
* Author: lihui
* Date: 2018/6/17 上午0:11
*/
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class})
public class CDPlayerConfig {
}
这里的Yesterday bean没有任何依赖,只需要扫描,而有很多对象都要依赖其他对象才能完成,这时候就需要一种方法能够将组件扫描得到的bean和它们的依赖装配一起
自动装配就能满足上面要求,它让Spring自动满足bean依赖;这个过程中,在Spring应用上下文中 寻找匹配某个bean需求的其他bean
为了声明要进行自动装配,需要用到Spring的@Autowired注解;下面在构造函数上用到了@Autowired注解,表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化,而且会传入一个客设置给CompactDisc类型的bean
package com.maoxiaomeng.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayer
* Author: lihui
* Date: 2018/6/17 下午11:09
*/
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
CDPlayer构造器中添加了@Autowired注解,Spring将把一个可分配给 CompactDisc类型的bean自动注入进来
为了测试这点,修改测试代码,借助CDPlayer bean播放CD
package com.maoxiaomeng.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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: CDPlayerTest
* Author: lihui
* Date: 2018/6/20 上午1:10
*/
@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 Yesterday Once More" +
" by Carpenters\n",
log.getLog()
);
}
}
除了注入CompactDisc,还将CDPlayer bean注入到测试代码的player成员变量之中(它是更通用的MediaPlayer类型,在play()测试方法中,可以调用CDPlayer的play()方法,并断言它行为和预期一致
这里例子中用了StandardOutputStreamLog,来自System Rules库的一个JUnit规则,能够基于控制台输出编写断言,这里断言Yesterday.play()方法的输出发送到了控制台上
如果断言出错
最后是maven的配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>spring</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>StandardOutputStreamLog</groupId>
<artifactId>StandardOutputStreamLog</artifactId>
<version>unknown</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
有点绕,一句话,如果不用spring的这些注解,在一个类里引用另一个类,甚至有多层依赖的时候,只有不停地new,而通过注解能够将这些依赖关系的处理全部交给spring来处理