1、什么是装配
任何一个成功的应用都是由多个为了实现某一个业务目标而相互协作的组件构成的。例如:在一个在线购物系统中,订单管理组件需要和产品管理组件以及支付组件协作。
在Spring中,对象无需自己查找或创建与其关联的其他对象,容器负责把需要相互协作的对象引用赋予各个对象。
创建应用对象之间协作的关系的行为通常称为装配,这也是依赖注入的本质。
2、Spring装配bean的可选方案
1、在XML中进行显示配置
2、在Java中进行显示配置
3、隐式的bean发现机制和自动装配
建议:尽可能使用自动配置的机制,显示配置越少越好。但你必须要显示配置bean的时候(比如维护已有的代码),推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
3、自动化装配bean
Spring从两个角度实现自动化装配:
1、组件扫描:Spring会自动发现应用上下文中所创建的bean
2、自动装配:Spring自动满足bean之间的依赖
举个例子,如果不将CD光盘插入CD播放器中,CD其实是没有用处的。所以可以说,CD播放器依赖于CD。
3.1创建可被发现的bean
首先定义CD的一个接口CompactDisc:
public interface CompactDisc {
void play();
}
CompactDisc的具体内容并不重要,重要的是你将其定义为接口。它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小程度。
我们再创建一个该接口的实现类:
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgr. Peppers's Lonely Hearts";
private String artist = "The Beatles";
public void play() {
System.out.println("playing"+title+"by"+artist);
}
}
SgtPeppers类上使用了@Component注解,这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
不过组件扫描默认是不启用的,我们使用@ComponentScan注解,如果没有其它配置的话,Spring会扫描与配置类相同的包及其子包,就能发现CompactDisc并且在Spring中自动为其创建一个bean。
@ComponentScan
public class CDPlayerConfig {
}
也可以通过XML启用组件扫描:
<?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:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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="soundsystem" />
</beans>
为了测试组件扫描的功能,我们创建一个Junit测试 ,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来了。
package com.spring.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.*;
@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。
@Autowired注解用于将CompactDisc这个bean注入到测试代码之中。最后使用断言,判断注入的属性不为null,意味着Spring能够发现这个bean。
尽管我们只创建了一个bean,但同样的配置能够用来发现和创建任意数量的bean。在扫描的范围内,所有带有@Component注解的类都会创建为bean,这种效率还是很高的。
3.2 为组件扫描的bean命名
Spring应用上下文中所有的bean都会默认给定一个ID,也就是将类名的第一个字母变为小写。
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc{
...
}
3.3 通过为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
例如下面代码里面的CDPlayer 类,它的构造器上添加了@Autowired注解,这表明Spring创建CDPlayer这个bean的时候,会通过构造器来实例化并传入一个可设置给CompactDisc类型的bean。
package com.spring.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
@Autowired注解不仅可以用在构造器上,还能用在属性的Settle方法上,比如CDPlayer有一个setCd()方法,那么可以采用如下注解形式进行自动装配:
@Autowired
public void setCd(CompactDisc cd) {
this.cd = cd;
}
在Spring初始化bean之后,它会尽可能得去满足bean的依赖,在本例中,依赖是通过带有@Autowired注解的方法进行声明的,也就是setCd()。
3.4 验证自动装配
为了验证上面在CDPlayer的构造器中添加@Autowired注解是否有效,我们增加一个test,使其借助CDPlayer这个bean播放CD,代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Autowired
private MediaPlayer player;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
@Test
public void play() {
player.play();
}
}
现在,除了注入CompactDisc,我们还将CDPlayer这个bean注入到测试代码的player成员变量中(它是更为通用的MediaPlayer类型)。在play()测试方法中,我们看最终有没有调用CDPlayer的play()方法。如截图所示,内容播放出来了。
4、通过Java代码装配bean
尽管Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要显示配置Spring。比如,你想要将第三方库中的组件配置到你的应用中,你是没有办法在它的类上添加@Component和@Autowired注解的。
在进行显示配置时,JavaConfig是更好的方案,因为它类型安全并且对重构友好,而且它就是Java代码,就像应用程序中的其他Java代码一样。
4.1、创建配置类
在上面我们创建过一个CDPlayerConfig,现在我们在其基础上添加@Configuration注解,该注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。此处我们使用显示配置,所以移除@ComponentScan注解。
@Configuration
public class CDPlayerConfig {
}
4.2、声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。比方说,下面的代码声明了CompactDisc这个bean:
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc SgtPeppers() {
return new SgtPeppers();
}
}
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与方法名一致,可以通过@Bean注解的name属性重命名。
我们甚至可以在一组CD中选择一个来播放:
@Bean
public CompactDisc SgtPeppers() {
int choice = (int)Math.floor(Math.random() * 2);
if (choice==0){
return new SgtPeppers();
}else {
return new Jay();
}
}
4.3、借助JavaConfig实现注入
我们需要声明CDPlayer这个bean,它依赖于CompactDisc。在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。cdPlayer()方法没有使用默认构造器构建实例,而是调用了需要传入CompaciDisc对象的构造器来创建CDPlayer实例。
看起来,CompaciDisc是通过sgtPeppers()方法得到的,但情况并非如此,因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都调用,Spring中的bean都是单例的。
其实还有一种理解起来更为简单的方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
在这里,cdplayer()方法请求一个CompactDisc作为参数。当Spring调用cdplayer()创建cdplayer这个bean的时候,它会自动装配一个CompactDisc到配置方法中。然后方法体就可以按照合适的方式来使用它。借助这种技术,cdplayer()方法也能将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
通过这种方式引用其他的bean通常是最佳选择,因为它不需要将CompactDisc声明到同一个配置类之中。
你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。
5、通过XML装配bean
5.1、创建XML配置规范
在JavaConfig中所需要的只是@Configuration,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 此处声明各个Bean -->
</beans>
5.1、声明一个简单的
在基于XML的Spring配置中声明一个bean,我们要使用spring-beans模式中的另一个元素:<bean>
。相当于JavaConfig中的@Bean注解。比如我们声明CompactDisc bean:
<bean class="soundsystem.SetPeppers"/>
由于没有明确给定ID,所以这个bean会以全限定类名来进行命名。在这个例子中ID将会是"soundsystem.SetPeppers#0"。其中”#0“是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个SgtPeppers,那么它的ID将会是"soundsystem.SetPeppers#1"。
不过我们也可以使用id属性来给bean命名,比如:
<bean id="compactDisc" class="soundsystem.SetPeppers"/>
5.2、XML配置相对JavaConfig的缺点
1、当Spring发现<bean>
元素时,它将会调用SetPeppers的默认构造器来创建bean。在XML配置中,bean的创建显得更加被动,在JavaConfig中,你可以通过任何想象到的方法来创建bean实例。
2、我们将bean的类型以字符串的形式设置在class属性中,如果重命名了类,就不安全了。
5.3、借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:使用<bean>
元素并指定class的属性。但是在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可以选择:
1、<constructor-arg>
元素
2、使用Spring3.0所引入的c-命名空间
构造器注入bean引用
在CDPlayer bean有一个接受CompactDisc类型的构造器。由于我们已经声明了SgtPeppers bean,并且SgtPeppers类实现了CompactDisc接口,所以我们可以将SgtPeppers注入到CDPlayer中。
<bean id="cdPlayer" class="soundSystem.CDPlayer">
<constructor-arg ref="sgtPeppers"></constructor-arg>
</bean>
或者也可以这样:
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cdPlayer" class="soundSystem.CDPlayer" c:cd-ref="sgtPeppers"/>
<!-- 或 -->
<bean id="cdPlayer" class="soundSystem.CDPlayer" c:_0-ref="sgtPeppers"/>
当Spring遇到这个元素时,它会创建一个CDPlayer实例。元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。
5.4、将字面量注入带构造器中
package soundSystem;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String article) {
this.title = title;
this.artist = article;
}
public void play() {
System.out.println("Playing "+title+" by"+artist);
}
}
或者:
<bean id="comPactDisc" class="soundSystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Bane"/>
<constructor-arg value="The Beatles"/>
</bean>
<!-- 或 -->
<bean id="comPactDisc2" class="soundSystem.BlankDisc" c:title="Sgt. Pepper's Lonely Hearts Club Bane" c:artist="The Beatles"/>
<!-- 或 -->
<bean id="comPactDisc" class="soundSystem.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Bane" c:_1="The Beatles"/>
5.5、装配集合
再次为CompcatDisc建模。
package soundSystem;
import java.util.List;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
public void play() {
System.out.println("Playing "+title+" by"+artist);
for (String track : tracks)
System.out.println(track);
}
}
这个时候必须为其提供一个磁道列表。
<bean id="comPactDisc" class="soundSystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Bane"/>
<constructor-arg value="The Beatles"/>
<constructor-arg><null/></constructor-arg>
</bean>
当将<null/>
元素传入构造器,在play()方法调用的时候,将会产生异常,所以更好的解决放方法是为元素声明一个列表:
<bean id="comPactDisc" class="soundSystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Bane"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from my Friends</value>
<value>Lucy in the Sky With Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</constructor-arg>
</bean>
5.6、设置属性
通过Setter方法来设置属性。
<property>
元素为属性Setter方法所提供的功能与<constructor-arg>
元素为构造器所提供的功能是一样的。
package soundSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd){
this.cd=cd;
}
public void play(){
cd.play();
}
}
6、导入和混合配置
在Spring的典型应用中,我们可能会同时自动化和显式配置。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或是在JavaConfig中声明还是通过组件扫描获取到。
6.1、在JavaConfig中导入XML配置
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:CDMix.xml")
public class SoundSystemConfig {
}
6.2、在XML中导入JavaConfig配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.SoundSystem_Mix.CDConfig" />
<bean id="cdPlayer" class="com.SoundSystem_Mix.CDPlayer" />
</beans>