Bean的装配
任何一个优秀的应用都是由多个为了实现某一个业务目标而相互协作的组件构成的。而创建对象之间的关联关系的传统方法一般是通过构造器或者查找,很难复用,也很难测试。在Spring中,对象不需要自己查找或者创建与其关联的其他对象,容器负责把需要相互协作的对象引用赋予各个对象。
例如,一个订单管理组件需要信用卡认证的组件,他不需要自己去创建这个组件,反之,容器会主动赋予它一个信用卡认证的组件。
这种创建对象之间的协作关系的行为叫做装配(wiring), 这其实是依赖注入的本质了。
Spring配置的方案
在上一讲中我们说了Spring的容器,其负责创建应用程序当中的Bean并通过依赖注入的方式来协调这些对象之间的关系。而开发人员需要做的是, 告知容器,要创建哪些Bean,并且如何将其拼装在一起 , Spring提供了三种装配的方式:
- 在XML中配置
- 在Java中配置
- 隐藏的bean发现机制和自动装配 (autowiring)
这三种方式,autowiring的方法有自己的一些使用限制,因为其在引用第三方的库的时候,是无法自动装配的,而在XML和Java中显式配置是等价的,但是Java下更有利于类型安全,下面通过例子来对这三种方式分别进行分析:
自动化装配Bean
Spring从两个角度来实现自动化装配的:
- 组件扫描(component scanning): Spring会自动发现应用上下文中所创建的Bean
- 自动装配(autowiring): Spring自动满足应用之间的依赖
创建几个Bean,代表一个音响系统的组件:
public interface CompactDisc {
void play();
}
CD播放器在这里定义了一个接口,他的具体实现(如何播放)和哪一张CD没有关系,通过这种方式达到解耦合的目的。
@Component //Spring会给Bean配置一个ID,默认的是sgtPeppers,即类名,第一个字母小写
public class SgtPeppers implements CompactDisc {
private String title = "Yi ran yi bao zha";
private String artist = "Li Chen";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
这里提供了CD播放器的具体实现,这里要注意注释 @Component, 是告诉Spring,这个类会作为组件类,需要Spring为其创建bean(不再需要显式的声明Bean了)
组件扫描默认是不启用的,因此我们需要显式配置一下Spring,命令其去寻找带有@Component的注解的类,并为其创建Bean。
@Configuration
@ComponentScan // 启用了组件扫描
public class CDPlayerConfig {
// 这里默认会再同一个包下进行搜索,但是也可用通过basePackages属性来进行搜索范围的设置
// @ComponentScan(basePackage = {"soundSystem", "video"}) 这里使用String进行范围配置
// @ComponentScan(basePackage = {"CDPlayer.class", "DVDPlayer.class"}) 也可以使用类来进行配置
}
进行测试,看是否进行了自动装配
@RunWith(SpringJUnit4ClassRunner.class) // 容器,以便自动创建Spring的应用上下文
@ContextConfiguration(classes=CDPlayerConfig.class) // 需要加载配置
public class CDPlayerTest {
@Autowired // 将bean注入到test的代码当中来
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}
给bean命名,可以用@Component 也可以用@Named
@Component("your_name") // spring package
@Named("your_name") // javax.inject.Named
上述说明的是ComponentScan相关的内容,因为上述的Bean是相互独立的,即在使用的时候,并没有对于其他的Bean的依赖。但是很多对象会依赖其他对象才可以完成任务,我们需要一种方法能够将组件扫描得到的bean和他们的依赖装配在一起——自动装配(Autowiring)
@Autowired
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
在这个例子里,Autowired注解被用在了构造器当中。这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并会传入一个可以设置给CompactDisc类型的bean。
@RunWith(SpringJUnit4ClassRunner.class) // 容器,以便自动创建Spring的应用上下文
@ContextConfiguration(classes=CDPlayerConfig.class) // 需要加载配置
public class CDPlayerTest {
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired // 将bean注入到test的代码当中来
private CompactDisc cd;
@Autowired
private MediaPlayer player;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
@Test
public void play() {
player.play();
assertEquals("Yi ran yi bao zha" + "Li Chen\n",
log.getLog());
}
}
通过Java代码装配Bean
自动化配置在要引入第三方库当中的组件的时候,是无法执行的。在这种时候我们要选择显式配置的方案,
- 在Java中显式配置
- 在XML中显式配置
JavaConfig相对来说是更好的方案,因为其更强大,类型安全,对重构友好
值得注意的是
JavaConfig是配置代码,这意味着其不应该包含任何业务逻辑,也不应该侵入到业务逻辑代码当中去。习惯做法,是放到单独的包中,使其与其他的应用程序逻辑分离开。
@Bean // Bean注解告诉Spring要返回一个对象,要注册为Spring应用上下文的bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
这里值得注意的是我们在创建cdPlayer这个bean的时候,构造器需要用sgtPeppers这个bean,这里不是调用,因为sgtPeppers是一个bean,Spring会拦截所有对其的调用,并确保直接返回该方法锁创建的bean
默认情况下,Spring中的Bean都是单例的
在XML中装配Bean
在XML中装配,不推荐,在这里介绍更多的是为了能看懂原先的代码。大致有以下几个流程
- 创建XML的配置规范
- 类似于@Configuration
- 可以借助工具 Spring Tool Suite
- 声明一个简单的Bean
- 通过class属性来指定 <bean class="soundsystem.SgtPeppers"/>
- 通过id属性赋予名字
- 借助构造器注入初始化Bean
- <constructor-arg>
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
- c-命名空间 两种方式是可以替代的
// c:c-命名空间前缀 cd:构造器参数名 ref:注入bean的引用
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
- 可以将字面量注入到构造器当中
- 装配单个值
<constructor-arg value="yi ran yi bao zha">
- 装配集合
<constructor-arg value="chang pian ji">
<constructor-arg value="Li Chen">
<constructor-arg>
<list> // <set>
<value>Yi ran yi bao zha</value>
<value>Qi miao neng li ge</value>
<value>Da feng chui</value>
<value>Blabla...</value>
</list>
</constructor-arg>
- 设置属性
<bean id="cdPlayer"
class="soundsystem.CdPlayer">
<property name="compactDisc" ref="compactDisc" />
<!--字面量注入属性当中,一样是加<list>, <value>-->
</bean>
导入和混合配置
在JavaConfig中引用XML配置
使用注解@ImportResource
在XML中引用JavaConfig
<import>