在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。
创建应用对象之间协作关系的行为通常成为装配(wiring),这也是依赖注入(DI)的本质。
1、Spring配置的可选方案
当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显示配置
- 在Java中进行显示配置
- 隐式的bean发现机制和自动装配
尽可能地使用自动配置的机制。显示配置越少越好。当你必须要显示配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
2、自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean
- 自动装配(autowiring):Spring自动满足bean之间的依赖
2.1创建可被发现的bean
CompactDisc接口在Java中定义了CD的概念
package soundsystem;
public interface CompactDisc {
void play();
}
带有@Component注解的CompactDisc实现类
package soundsystem;
import org.springframework.stereotype.Component;
/**
* @Component注解,表明该类会作为组件类,并告知Spring要为这个类创建bean
**/
@Component
public class SgtPeppers implements CompactDisc {
private String title = "stg,peppers";
private String artist = "the beatles";
public void play() {
System.out.println("playing " + title + " by " + artist);
}
}
组件扫描默认是不启用的,还需要显示地配置一下Spring,从而命令它寻找带有@Component注解的类,并为其创建bean。
@ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration用于定义配置类
**/
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
CDPlayerConfig类并没有显示地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包(扫描这个包以及这个包下的所有子包,查找带有@Component注解的类,然后在Spring中自动为其创建一个bean)。
若使用XML来启用组件扫描的话,可以使用Spring context命名空间的<context:component-scan>元素。
通过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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="soundsystem"/>
</beans>
测试组件扫描能够发现CompactDisc
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 soundsystem.CDPlayerConfig;
import soundsystem.CompactDisc;
import static junit.framework.TestCase.assertNotNull;
/**
* @ContextConfiguration:告诉它需要在CDPlayerConfig中加载配置
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void test(){
assertNotNull(cd);//判断cd属性是否存在
}
}
如果cd属性不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码中。
2.2为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。默认会根据类名为其指定一个ID。
想要为这个bean设置不同的ID,需要将期望的ID值传递给@Component注解
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
或者可以使用Java依赖注入规范中所提供的@Named注解来为bean设置ID
package soundsystem;
import javax.inject.Named;
/**
* @Component注解,表明该类会作为组件类,并告知Spring要为这个类创建bean
**/
@Named("lonelyHeartClub")
public class SgtPeppers implements CompactDisc {
...
}
2.3设置组件扫描的基础包
如果需要明确地设置基础包,需要在@ComponentScan的value属性中指明包的名称:
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
若想更清晰地表明你所设置的是基础包,可以通过basePackages属性进行配置:
@Configuration
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {
}
若想设置多个基础包,只需要将basePackages属性设置为要扫描的一个数组即可:
@Configuration
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
}
这里所设置的基础包是以String类型表示的,是类型不安全的。如果重构代码的话,那么所指定的基础包可能就会出现错误。
@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {
}
为basePackageClasses属性所设置的数组中包含了类,这些类所在的包将会作为组件扫描的基础包。
2.4通过为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,可以借助Spring的@Autowired注解。
package 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注解,表明当Spring创建CDPlayer bean时,会通过这个构造器进行实例化并会传入一个CompactDisc类型的bean
@Autowired注解可以用在类的任何方法上。
假如有且只有一个bean匹配需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用的上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,可以将@Autowired的required属性设置为false。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定哪个bean进行自动装配。
Java依赖注入规范中的@Inject可以替代@Autowired。
2.5验证自动装配
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Autowired
private MediaPlayer player;
@Test
public void test(){
assertNotNull(cd);//判断cd属性是否存在
}
@Test
public void play(){
player.play();
}
}//output:playing stg,peppers by the beatles
3、通过Java代码装配bean
如果要将第三方库中的组件装配到你的应用中,在这种情况下是没有办法在它的类上添加@Component和 @Autowired注解的,因此就不能使用自动化装配的方案了。
这种情况下,必须要采用显示装配的方案。在进行显示装配的时候,有两种可选方案:Java和XML。
在进行显示装配的时候,JavaConfig是更好的方案,因为他更为强大、类型安全并且重构友好。
JavaConfig是配置代码,意味着不应该包含任何业务逻辑,也不应该侵入到业务逻辑代码中。通常会将JavaConfig放到单独的包中,使它与其他的应用程序裸机分离开来。
3.1创建配置类
@Configuration
public class CDPlayerConfig {
}
创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
为了研究显示配置,这里移除了@ComponentScan。
3.2声明简单的bean
要在JavaConfig中声明bean,需要编写一个方法创建所需类型的实例,然后给这个方法添加@Bean注解:
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。
可以通过name属性重命名该方法:
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
3.3借助JavaConfig实现注入
在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法。
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调解,并确保返回该方法所创建的bean,而不是每次都对其进行实际的调用。
还有一种理解起来更为简单的方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。它可以通过组件扫描功能自动发现或者通过XML来进行配置。
也可以通过Setter方法注入CompactDisc
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public void setCompactDisc(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}//这里的CDPlayer类作了修改
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer();
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到了Java语言的限制。
4、通过XML装配bean
4.1创建XML配置规范
最简单的Spring 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- configuration details go here -->
</beans>
用来装配bean的最基本的XML元素包含在spring-beans模式之中,在上面这个XML文件中,它被定义为根命名空间。
<beans>
是该模式中的一个元素它是所有Spring配置文件的根元素。
在IDEA中配置和创建Spring XML文件:
导入maven包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>
便可以直接创建
4.2声明一个简单的<bean>
要在基于XMl的Spring配置中声明一个bean,需要使用spring-beans模式中的另外一个元素:<bean>
:
<bean class="soundsystem.SgtPeppers"/>
创建这个bean通过class属性来指定,并且要使用全限定的类名。
可以借助id属性,为每个bean设置一个自己选择的名字
<bean id="compactDisc" class="soundsystem.SgtPeppers"/>
4.3借助构造器注入初始化bean
在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
1.<constructor-arg>
元素
2.使用Spring3.0所引入的c-命名空间
4.3.1构造器注入bean引用
在XML中声明CDPlayer并通过ID引用SgtPeppers:
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
作为替代方案,可以使用Spring的c-命名空间。
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
也可以使用参数的索引来识别构造器参数:
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc"/>
4.3.2将字面量注入到构造器中
创建一个新的CompactDisc实现:
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
将已有的SgtPeppers替换为这个类:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Peppers"/>
<constructor-arg value="The Beatles"/>
</bean>
这里是使用value属性,通过该属性表明给定的值要以字面量的形式注入到构造器中。
要使用c-命名空间的话,第一种方案是引用构造器参数的名字:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:title="Sgt. Peppers"
c:artist="The Beatles"/>
可以看出,装配字面量和装配引用的区别在于属性名中去掉了"-ref"后缀。
也可以通过参数索引装配相同的字面量值:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt. Peppers"
c:_1="The Beatles"/>
4.3.3装配集合
为BlankDisc增加一个磁道列表:
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private Set<String> tracks;
public BlankDisc(String title, String artist, Set<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: " + track);
}
}
}
在声明bean的时候,我们必须要提供一个磁道列表。
可以使用<list>
元素将其声明为一个列表:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Peppers"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
<value>track4</value>
<value>track5</value>
<list/>
</constructor-arg>
</bean>
<list>
元素是<constructor-arg>
的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>
元素用来指定列表中的每个元素。
可以使用<ref>
元素替代<value>
,实现bean引用列表的装配。
假设有一个Discography类,它的构造器如下:
public Discography(String artist, List<CompactDisc> cds){...}
可以采取如下配置Discography bean:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<ref bean="sgtPeppers"/>
<ref bean="whiteAlbum"/>
<ref bean="revolver"/>
<list/>
</constructor-arg>
</bean>
当构造器参数类行为java.util.Set时,可以使用<set>
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Peppers"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<set>
<value>track1</value>
<value>track2</value>
<value>track3</value>
<value>track4</value>
<value>track5</value>
</set>
</constructor-arg>
</bean>
4.3设置属性
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public void play() {
compactDisc.play();
}
}
对于强依赖使用构造器注入,对可选性依赖使用属性注入。
现在,CDPlayer没有任何的构造器(除了隐含的默认构造器),它也没有任何的强依赖。因此可以采用如下的方式将其声明为Spring bean:
<bean id="cdPlayer" class="soundsystem.CDPlayer">
</bean>
Spring在创建bean的时候不会有任何问题,但是CDPlayerTest会因为出现NullPointerException而导致测试失败,因为并没有注入CDPlaye的CompactDisc属性,应改成如下的方式:
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc"/>
</bean>
<property>
元素为属性的Setter方法所提供的功能与<constructor-arg>
元素为构造器所提供的功能是一样的。
Spring还提供了更为简洁的p-命名空间,作为<property>
的替代方案。
<bean id="cdPlayer" class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDisc"/>
4.3.1将字面量注入到属性中
属性也可以注入字面量。
创建新的类,完全通过属性注入进行配置:
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks){
System.out.println("-Track: " + track);
}
}
}
可以借助<property>
元素中的value属性实现该功能:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<property name="title" value="sgt.pepper's lonely hearts"/>
<property name="artist" value="the beatles"/>
<property name="tracks">
<list>
<value>with a little help</value>
<value>lucy in the sky</value>
<value>getting better</value>
</list>
</property>
</bean>
另一种可选方案就是使用p-命名空间的属性来完成该功能:
<bean id="compactDisc"
class="soundsystem.BlankDisc"
p:title="sgt.pepper's lonely hearts"
p:artist="the beatles">
<property name="tracks">
<list>
<value>with a little help</value>
<value>lucy in the sky</value>
<value>getting better</value>
</list>
</property>
</bean>
与c-命名空间一样,装配bean引用和装配字面量的唯一区别就在于是否带有"-ref"后缀。如果没有"-ref"后缀的话,所装配的就是字面量。
为了解决p-命名空间不能装配集合的问题,可以使用Spring util-命名空间来简化BlankDisc bean。
util-命名空间所提供的功能之一就是<util:list>
元素,它会创建一个列表的bean。借助<util:list>
,可以将磁道列表转移到BlankDisc bean之外,并将其声明到单独的bean中:
<util:list id="trackList">
<value>with a little help</value>
<value>lucy in the sky</value>
<value>getting better</value>
</util:list>
然后将磁道列表bean注入到BlankDisc bean的tracks属性中:
<util:list id="trackList">
<value>with a little help</value>
<value>lucy in the sky</value>
<value>getting better</value>
</util:list>
5、导入和混合配置
自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
5.1在JavaConfig中引用XML配置
如果将BlankDisc bean从CDPlayerConfig拆分出来,定义到它自己的CDConfig类中:
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc(){
return new SgtPeppers();
}
}
compactDisc()方法已经从CDPlayerConfig中移除掉了,需要有一种方式将这两个类组合在一起。
一种方式就是在CDPlayerConfig中使用@Import注解导入CDConfig:
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
另一种更好的方式,就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundsystemConfig,在这个类中使用@Inport将两个配置类组合在一起:
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
public class SoundsystemConfig {
}
现在,我们假设基于某种原因希望通过XML配置BlankDisc,如下:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<property name="title" value="sgt.pepper's lonely hearts"/>
<property name="artist" value="the beatles"/>
<property name="tracks">
<list>
<value>with a little help</value>
<value>lucy in the sky</value>
<value>getting better</value>
</list>
</property>
</bean>
则需要通过@ImportResource注解让Spring同时加载它和其他基于Java的配置:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:Spring.xml")
public class SoundsystemConfig {
}
5.2在XML配置中引用JavaConfig
在XML中,我们可以使用<import>
元素来拆分XML配置。
假设将BlankDisc bean拆分到自己的配置文件中,该文件命名为cd-config.xml。可以在XML配置文件中使用<import>
元素来引用该文件:
<?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"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<import resource="cd-config.xml"/>
<bean id="cdPlayer" class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDisc"/>
</beans>
再假设,不再将BlankDisc 配置在XML中,而是将其配置在JavaConfig中,CDPlayer则将继续配置在XML中。基于XML的配置引用一个JavaConfig类,需要用到<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"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean class="soundsystem.CDConfig"/>
<bean id="cdPlayer" class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDisc"/>
</beans>
同样,可以创建一个更高层次的配置文件,这个文件不声明任何的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"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean class="soundsystem.CDConfig"/>
<import resource="cdplayer-config.xml"/>
</beans>