自建博客地址:https://bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇
本文作者: Jeffrey
本文链接: https://bytelife.net/articles/33415.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
在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还有更多的细节,我会在后续的文章中进行阐述。
--Posted from Rpc