Spring从两个角度来实现自动化装配:
为了更形象的解释组件扫描与自动装配,我们举一个音响系统的例子,主要包含以下内容:
关于CD和CD播放器关系的解释:
如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实是没有太大用处的。所以,可以这样说,
CD播放器依赖于CD才能完成它的使命。
先创建CD接口CompactDisc:
package chapter02; public interface CompactDisc { void play(); } 复制代码
然后创建CD接口的一个实现类SgtPeppers:
package chapter02; 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。
那么如何让Spring发现它并创建bean呢?
这时就需要用到组件扫描,不过, 在Spring中,组件扫描默认是不启用的 。
因此我们需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
创建CDPlayerConfig类:
package chapter02; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class CDPlayerConfig { } 复制代码
这个类与以往类的区别是使用了 @ComponentScan
注解,这个注解能够让Spring启用组件扫描。
@ComponentScan默认会扫描与配置类相同的包以及这个包下的所有子包,查找带有@Component注解的类。
为了验证创建的bean能否被Spring发现,我们创建一个简单的JUnit测试,完成此测试需要导入以下两个jar包:
导入jar包的方式如下:
导入完成后的项目结构图如下所示:
package chapter02; 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
注解,会将SgtPeppers bean注入到字段compactDisc上,因为它是接口CompactDisc的实现类并且添加了 @Component
注解。
运行测试方法cdShouldNotBeNull,会发现测试通过,compactDisc不为null:
Spring应用上下文中所有的bean都会给定一个ID, 默认情况下,Spring会将类名的第一个字母变为小写,作为该bean的ID 。
如上面代码中SgtPeppers bean的ID为sgtPeppers。
有以下两种方式来设置bean ID:
@Component("lonelyHeartsClub") public class SgtPeppers implements CompactDisc { ...... } 复制代码
@Named注解不是Spring框架的注解,而是Java 依赖注入规范(Java Dependency Injection)中的注解,因此需要导入jar包:javax.inject-1.jar,导入jar包的方式可以参考 Spring入门(一):创建Spring项目 。
import javax.inject.Named; @Named("lonelyHeartsClub") public class SgtPeppers implements CompactDisc { ...... } 复制代码
在Spring项目中建议使用 @Component
注解。
按照默认规则 , @ComponentScan
注解会以配置类所在的包作为基础包(base package)来扫描组件。
但有时候,我们会将配置类放在单独的包中,使其与其他的应用代码区分开来。
这种场景下,默认的基础包就满足不了需求。
@ComponentScan
注解支持传入指定的基础包,有以下几种场景:
@ComponentScan("chapter02") public class CDPlayerConfig { } 复制代码
或者:
@ComponentScan(basePackages = "chapter02") public class CDPlayerConfig { } 复制代码
@ComponentScan(basePackages = {"chapter01", "chapter02"}) public class CDPlayerConfig { } 复制代码
@ComponentScan(basePackageClasses = {CDPlayer.class}) public class CDPlayerConfig { } 复制代码
basePackageClasses也支持指定多个类,指定类所在的包将会作为组件扫描的基础包。
建议使用这种类型安全方式来指定扫描的基础包。
自动装配是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需要的其他bean。
实现自动装配,需要使用Spring的 @Autowired
注解。
@Autowired
一般情况下,有以下3种使用方式:
package chapter02; public interface MediaPlayer { void play(); } 复制代码
package chapter02; 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(); } } 复制代码
package chapter02; 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(); } } 复制代码
package chapter02; 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 chapter02; 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; } @Override public void play() { compactDisc.play(); } } 复制代码
修改CDPlayerTest类代码测试自动装配。
package chapter02; 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 mediaPlayer; @Autowired private CompactDisc compactDisc; @Test public void cdShouldNotBeNull() { assertNotNull(compactDisc); compactDisc.play(); } @Test public void play() { mediaPlayer.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,导入jar包的方式可以参考 Spring入门(一):创建Spring项目 。
运行测试方法play(),输出内容和预期一致,说明字段mediaPlayer已经被MediaPlayer的实现类CDPlayer bean装配,测试通过,如下所示:
源码地址: github.com/zwwhnly/spr… ,欢迎下载。
Craig Walls 《Spring实战(第4版)》
打个小广告,欢迎扫码关注微信公众号:「申城异乡人」,定期分享Java技术干货,让我们一起进步。