Spring 从两个角度来实现自动化装配:
组件扫描(component scanning):Spring 会自动发现应用上下文中需要创建的 bean.
自动装配(autowiring):Spring 会自动满足 bean 之间的依赖.
为了更形象的解释组件扫描与自动装配, 我们举一个音响系统的例子, 主要包含以下内容:
CD 接口
CD 接口的一个实现类
CD 播放器
关于 CD 和 CD 播放器关系的解释:
如果你不将 CD 插入 (注入) 到 CD 播放器中, 那么 CD 播放器其实是没有太大用处的. 所以, 可以这样说,
CD 播放器依赖于 CD 才能完成它的使命.
1. 创建可被发现的 bean
先创建 CD 接口 CompactDisc:
- package soundsystem;
- public interface CompactDisc {
- void play();
- }
然后创建 CD 接口的一个实现类 SgtPeppers:
- package soundsystem;
- import org.springframework.stereotype.Component;
- @Component
- public class SgtPeppers implements CompactDisc {
- @Override
- public void play() {
- String title = "Sgt.Pepper's Lonely Hearts Club Band ";
- String artists = "The Beatles";
- System.out.println("Playing" + title + "By" + artists);
- }
- }
SgtPeppers 类与以往类的区别在于使用了 @Component 注解. 这个注解表明该类会作为组件类, 并告知 Spring 要为这个类创建 bean.
创建了 bean, 那么如何让 Spring 发现它呢? 这时就需要用到组件扫描, 不过, 在 Spring 中, 组件扫描默认是不启用的. 因此我们需要显式配置一下 Spring, 从而命令它去寻找带有 @Component 注解的类, 并为其创建 bean.
创建 CDPlayerConfig 类:
- package soundsystem;
- import org.springframework.context.annotation.ComponentScan;
- @ComponentScan
- public class CDPlayerConfig {
- }
这个类与以往类的区别是使用了 @ComponentScan 注解, 这个注解能够 Spring 中启用组件扫描.
@ComponentScan 默认会扫描与配置类相同的包以及这个包下的所有子包, 查找带有 @Component 注解的类.
2. 验证组件扫描
为了验证创建的 bean 能否被 Spring 发现, 我们创建一个简单的 JUnit 测试, 完成此测试需要导入以下两个 jar 包:
- hamcrest-core-2.1.jar
- junit-4.12.jar
导入 jar 包的方式如下:
导入完成后的项目结构图如下所示:
- package 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;
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(classes = CDPlayerConfig.class)
- public class CDPlayerTest {
- @Autowired
- private CompactDisc compactDisc;
- @Test
- public void cdShouldNotBeNull() {
- assertNotNull(compactDisc);
- compactDisc.play();
- }
- }
代码简单讲解:
@RunWith(SpringJUnit4ClassRunner.class), 会在测试开始的时候自动创建 Spring 的应用上下文.
@ContextConfiguration(classes = CDPlayerConfig.class)会告诉 Spring 需要在 CDPlayerConfig 中加载配置.
字段 compactDisc 上的 @Autowired 注解, 会将 CompactDisc bean(其实是 SgtPeppers)注入到测试代码之中.
运行测试方法 cdShouldNotBeNull, 会发现测试通过, compactDisc 不为 null:
3. 为组件扫描的 bean 命名
Spring 应用上下文中所有的 bean 都会给定一个 ID, 默认情况下, Spring 会将类名的第一个字母变为小写, 作为该 bean 的 ID.
如上面代码中 SgtPeppers bean 的 ID 为 sgtPeppers.
有以下两种方式来设置 bean ID:
方式 1: 使用 @Component 设置 bean ID
- @Component("lonelyHeartsClub")
- public class SgtPeppers implements CompactDisc {
- ...
- }
方式 2: 使用 @Named 设置 bean ID
@Named 注解不是 Spring 框架的注解, 而是 Java 依赖注入规范 (Java Dependency Injection) 中的注解, 因此需要导入 jar 包: javax.inject-1.jar.
- package soundsystem;
- import javax.inject.Named;
- @Named("lonelyHeartsClub")
- public class SgtPeppers implements CompactDisc {
- ...
- }
在 Spring 项目中建议使用 @Component 注解.
4. 设置组件扫描的基础包
按照默认规则 ,@ComponentScan 注解会以配置类所在的包作为基础包 (base package) 来扫描组件.
但有时候, 我们会将配置类放在单独的包中, 使其与其他的应用代码区分开来.
这种场景下, 默认的基础包就满足不了需求.
@ComponentScan 注解支持传入指定的基础包, 有以下几种场景:
4.1 指定要扫描的基础包(单个)
- @ComponentScan("soundsystem")
- public class CDPlayerConfig {
- }
或者:
- @ComponentScan(basePackages = "soundsystem")
- public class CDPlayerConfig {
- }
4.2 指定要扫描的基础包(多个)
- @ComponentScan(basePackages = {
- "soundsystem", "video"
- })
- public class CDPlayerConfig {
- }
4.3 指定要扫描的基础包(类型安全)
- @ComponentScan(basePackageClasses = {
- CDPlayer.class
- })
- public class CDPlayerConfig {
- }
如上所示, basePackageClasses 也支持指定多个类, 指定类所在的包将会作为组件扫描的基础包.
建议使用这种类型安全方式来指定扫描的基础包.
5. 通过为 bean 添加注解实现自动装配
自动装配是让 Spring 自动满足 bean 依赖的一种方法, 在满足依赖的过程中, 会在 Spring 应用上下文中寻找匹配某个 bean 需要的其他 bean.
实现自动装配, 需要使用 Spring 的 @Autowired 注解.
@Autowired 一般情况下, 有以下 3 种使用方式:
5.1 使用在构造器上
- package soundsystem;
- public interface MediaPlayer {
- void play();
- }
- package soundsystem;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- @Component
- public class CDPlayer implements MediaPlayer {
- private CompactDisc compactDisc;
- @Autowired
- public CDPlayer(CompactDisc compactDisc) {
- this.compactDisc = compactDisc;
- }
- @Override
- public void play() {
- compactDisc.play();
- }
- }
5.2 使用在属性的 Setter 方法上
- package soundsystem;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- @Component
- public class CDPlayer implements MediaPlayer {
- private CompactDisc compactDisc;
- @Autowired
- public void setCompactDisc(CompactDisc compactDisc) {
- this.compactDisc = compactDisc;
- }
- @Override
- public void play() {
- compactDisc.play();
- }
- }
5.3 使用在类的任何方法上
- package soundsystem;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- @Component
- public class CDPlayer implements MediaPlayer {
- private CompactDisc compactDisc;
- @Autowired
- public void insertDisc(CompactDisc compactDisc) {
- this.compactDisc = compactDisc;
- }
- @Override
- public void play() {
- compactDisc.play();
- }
- }
不管是构造器, Setter 方法还是其他的方法, Spring 都会尝试满足方法参数上所声明的依赖.
假如有且只有一个 bean 匹配依赖需求的话, 那么这个 bean 将会被装配进来.
如果没有匹配的 bean, 那么在应用上下文创建的时候, Spring 会抛出一个异常.
可以通过设置 require 属性为 false 避免该异常出现:
- @Autowired(required = false)
- public CDPlayer(CompactDisc compactDisc) {
- this.compactDisc = compactDisc;
- }
不过建议谨慎使用, 避免未找到 bean 进行匹配, 而且代码没有进行 null 检查而出现 NullPointerException.
如果有多个 bean 都能满足依赖关系的话, Spring 将会抛出一个异常, 表明没有明确指定要选择哪个 bean 进行自动装配.
@Autowired 注解也可以替换成 @Inject 注解(来源于 Java 依赖注入规范), 同样可以实现自动装配:
- package soundsystem;
- import org.springframework.stereotype.Component;
- import javax.inject.Inject;
- @Component
- public class CDPlayer implements MediaPlayer {
- private CompactDisc compactDisc;
- @Inject
- public CDPlayer(CompactDisc compactDisc) {
- this.compactDisc = compactDisc;
- }
- ...
- }
在 Spring 项目中建议使用 @Inject 注解.
6. 验证自动装配
修改 CDPlayerTest 类代码测试自动装配
- package soundsystem;
- 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 compactDisc;
- @Test
- public void cdShouldNotBeNull() {
- assertNotNull(compactDisc);
- compactDisc.play();
- }
- @Test
- public void play() {
- player.play();
- assertEquals("Playing Sgt.Pepper's Lonely Hearts Club Band By The Beatles\r\n", log.getLog());
- }
- }
因为代码中使用了 StandardOutputStreamLog 类, 因此需要导入 jar 包: system-rules-1.16.0.jar
运行测试方法 play(), 输出内容和预期一致, 说明字段 player 已经被 MediaPlayer 的实现类 CDPlayer bean 装配, 测试通过, 如下所示:
7. 源码地址
https://github.com/zwwhnly/SpringStudyDemo.git , 欢迎大家下载, 有问题可以多多交流.
来源: https://juejin.im/post/5c7f3cee6fb9a049d61e4bae