声明:后面的章节是看了《Spring实战》所做的笔记,相关内容也是摘抄下来,这里只是自己做个记录。
一、Spring配置的可选方案
Spring提供了三种主要的装配机制:
- 在
XML中进行显示配置 - 在
Java中进行显示配置 - 隐式的
bean发现机制和自动装配
建议是尽可能地使用自动配置的机制,显示配置越少越好。当必须要显示配置的时候,推荐使用类型安全并且比XML更加强大的JavaConfig。只有当想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
二、自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描:
Spring会自动发现应用上下文中所创建的bean - 自动装配:
Sping自动满足bean之间的依赖
2.1 创建可被发现的bean
下面使用例子说明:
CompactDisc.java
package soundsystem;
//这是一个CD接口,表示CD
public interface CompactDisc {
void play();
}
SgPeppers.java
package soundsystem;
import org.springframework.stereotype.Component;
//这是CD接口的一个实现类,其中包含CD名字和艺术家的名字
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
说明:@Component表示这个类是一个组件类,在装配过程中要将其创建为一个bean。下面配置自动扫面:
CDPlayerConfig
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
说明:这里通过这个类定义了Spring装配的规则,其中@Configuration表明这是一个装配规则,而虽然这里没有显示的声明要装配哪些bean,但是使用@ComponentScan就表示默认扫面本包中的所有类,如果发现某个类中配置了@Component注解,那么就将那些类装配为bean。当然也可以显示注明扫面哪个包,下面先看使用XML的方式,之后会说明使用Java的方式:
<context:component-scan base-package="soundsystem" />
说明:<context:component-scan>元素还有一些属性和子元素,这里不细说,加入我们将之前的@ComponentScan去掉,而是使用上面的XML配置,那如何才能让CDPlayerConfig知道呢?这需要使用后面要讲到的@Import注解。下面看一个测试:
package soundsystem;
import static org.junit.Assert.*;
import ......
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);//断言cd不为null
}
}
说明:这里@RunWith(SpringJUnit4ClassRunner.class)表明让Spring自动创建上下文。而@ContextConfiguration(classes=CDPlayerConfig.class)表明告诉此类要在CDPlayerConfig类中加载相关的配置,@Autowired表示自动注入实现了CompactDisc接口的实例。
2.1.1 为组件扫面的bean命名
在创建一个bean时,默认使用组件类的类名为ID(但是将首字母小写),但是我们也可以自己显示定义ID:
@Component("longelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}
说明:此时,这个类在被创建为bean的时候的ID就为longelyHeartsClub。
2.1.2 设置组件扫描的基础包
之前我们没有为@ComponentScan配置任何参数,于是其默认扫描的是类的包,同时也可以使用XML方式显示的指明要扫描的包,下面我们为其配置相关的属性:
@Configuration
@ComponentScan("soundsystem")
public class CSPlayerConfig(){}
说明:这里就是配置了一个自动扫面的基础包,当然我们可以更清晰的指明这是一个扫描基础包:
@ComponentScan(basePackeges="soundsystem")
说明:当然这里也可以同时指定多个扫描的基础包:
@ComponentScan(basePackeges={"soundsystem","vedio"})
说明:但是这里使用字符串的方式不够安全,我们推荐类的方式,就是将其指定为包中所含类或接口:
@ComponentScan(basePackegeClasses={CDPlayer.class, DVDPlayer.class})
说明:此时在扫描的时候就会扫描这两个类或接口所在包的所有类和接口。即这些类或接口所在的包将被指定为扫描基础包。我们还可以考虑在包中创建一个用来进行扫描的空标记接口,这样不会影响业务类今后的重构工作。
2.2.3 通过为bean添加注解实现自动装配
有些组件类在被创建为bean的过程中可能依赖其他的bean,可以通过注解的方式让Spring自动帮我们注入进来:
package soundsystem;
public interface MediaPlayer {
void play();
}
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();
}
}
说明:这里CDPlayer类在被创建为bean时依赖一个CompactDisc的实现类,于是使用@Autowired将这个bean注入进来。如果没有匹配的bean或者有多个匹配的bean,将会产生异常。
三、通过Java代码装配bean
前面说的是一种自动装配和自动注入的方式,但是有时候是没办法使用这种自动方式的,比如向将第三方库中的组件装配到你的应用中。此时必须使用显示的方式,即Java和XML配置方式。而对于Java方式配置bean则可以直接在CDPlayerConfig.java中配置,而对于XML方式的bean,可以使用@Import引用。
3.1 创建配置类
首先修改之前的配置类,让其不要自动扫描装配了:
package soundsystem;
@Configuration
public class CDPlayerConfig {
}
说明:此时没有配置@ComponentScan则就不会自动创建相关的bean了。
3.2 声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法创建所需类型的实例,然后给这个方法添加@Bean注解:
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
说明:这里注解会告诉Spring这个方法会返回一个对象,该对象要注册为Spring易用上下文中的bean。默认的ID和带有@Bean注解的方法名一样,当然也可以自己指定:
@Bean(name="lonelyHeartsClubBand")
3.3 借助JavaConfig实现注入
有些bean的创建可能依赖于其他bean的创建,我们需要将多个bean装配在一起,在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法,如:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
说明:这里可以看到CDPlayer bean的创建依赖于CompactDisc bean,于是我们调用了能够产生CompactDisc bean的方法。但是这里注意,看起来,CompactDisc是通过sgtPeppers()得到的,但是并不是如此,因为这个方法上添加了@Bean注解,Spring会拦截所有对它的实际调用,确保直接返回该方法所创建的bean,比如此时还有另一个bean的创建依赖CompactDisc bean:
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
说明:如果每次创建bean都是实际调用sgtPeppers()方法,那么每个bean都拥有自己特有的CompactDisc bean,但其实不是,默认情况下,Spring中的bean都是单例的。当然还有一种更为简单的方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
说明:这种方式可能更容易理解,这里不用明确引用CompactDisc的@Bean方法,通过这种方式引用其他的bean通常是最佳的选择。此时,不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。这里我们使用的是CDPlayer的构造器实现了DI功能,但是还有其他方式可以实现,比如通过Setter方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);//这里实现了DI功能
return cdPlayer;
}
说明:我们可以采用任何必要的Java功能来产生bean实例,仅仅收到Java语言的限制。
四、通过XML装配bean
4.1 声明一个简单的<bean>
这里我们需要添加一个<bean>元素,其类似于JavaConfig中的@Bean:
<bean class="soundsystem.SgtPeppers" />
说明:这里声明了一个简单的bean,创建这个bean的类通过class属性来指定,并且要使用全限定的类型。而这里没有明确指定ID,所以这个bean将会根据全限定类名来进行命名,这里即为"soundsystem.SgtPeppers#0",其中"#0"是一个计数的形式,用来区分相同类型的其他bean,如果另外声明一个SgtPeppers,并且没有明确指定ID,那么其ID即为"soundsystem.SgtPeppers#1"。
4.2 借助构造器注入初始化bean
在XML中声明DI时,会有多种可选的配置方案和风格,具体到构造器注入,有两种基本的配置方案:
-
<constructor-age>元素 - 使用
spring3.0所引入的c-命名空间
4.2.1 构造器注入bean引用
之前我们已经声明了一个SgtPeppers的bean,并且这个类实现了CompactDisc接口,所以实际上我们已经有了一个可以注入到CDPlayer bean中的bean,所以现在要做的就是通过DI引用SgtPeppers:
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-age ref="compactDisc"/>
</bean>
说明:spring会创建一个CDPlayer实例,同时 <constructor-age>会告知spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。
当然作为替代方案,可以使用c-命名空间,只是需要在XML配置的顶部声明其模式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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">
......
</beans>
说明:于是可以将之前的配置方法改为如下:
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
说明:属性名以"c-"开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是"-ref",它会告诉spring,正在装配的是一个bean的引用,这个bean名字是comapactDisc,而不是字面量"comapactDisc"。
注意:这里的"cd"是构造器参数名,但是直接使用参数名可能不太好,我们也可以这样:
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" />
说明:这里使用数字表示构造器参数的位置,也就是第几个参数,由于在XML中不允许数字作为属性的第一个字符,所以在前面加了一个下划线。当然如果只有一个构造器参数,可以将数字拿掉也可以。
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />
4.2.2 将字面量注入到构造器中
这里先给出一个CompactDisc的新实例:
package soundsystem.properties;
import soundsystem.CompactDisc;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc (String title,String artist) {
this.artist = artist;
this.title = title;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
说明:在之前的SgtPeppers类中,唱片名称和艺术家的名字都是硬编码的,这里我们让其更加灵活:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt.Peppers's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
</bean>
说明:这里使用value属性,就是表示给定的值要以字面量的行驶证注入到构造器中。如果要使用"c-"命名空间,则配置如下:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_title="Sgt.Peppers's Lonely Hearts Club Band"
c:_artist="The Beatles"/>
当然亦可以这样:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt.Peppers's Lonely Hearts Club Band"
c:_1="The Beatles"/>
4.2.3 装配集合
这里先将上面的BlankDisc类改动:
package soundsystem.properties;
import java.util.List;
import soundsystem.CompactDisc;
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.artist = artist;
this.title = title;
this.tracks= tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
说明:这里增加了一个磁道集合属性,这个属性在配置时必须配置,如果没有具体的值传递,可以配置为null,但是这在调用play()方法时会抛出空指针异常,于是我们需要配置一个List列表:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt.Peppers's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little...</value>
<value>Getting Better</value>
...
</list>
</constructor-arg>
</bean>
说明:当然集合列表也可以配置为bean引用:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt.Peppers's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<ref bean="sgtPeppers"/>
<ref bean="whiteAlbum"/>
<ref bean="revolver"/>
...
</list>
</constructor-arg>
</bean>
说明:也可以使用Set集合。而目前使用c-命名空间的属性无法实现装配集合的功能。
4.3 设置属性
在之前的注入中都是使用构造器注入的,没有使用Setter方法,这里看看如何使用XML配置实现属性注入:
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
说明:可能我们会觉得即使没有将CompactDisc装入进来,CDPlayer依然还能具备一些有限的功能,但是在测试相关功能时可能会出现空指针异常,使用XML配置实现属性配置的方式如下:
<bean id="cdPlayer" class="soundsystem.cdPlayer">
<property name="compactDisc" ref="compactDisc"/>
</bean>
说明:<property> 元素为属性的Setter 方法所提供的功能与<constructor-arg> 元素为构造器提供的功能是一样的。使用命名空间的方式为:
<bean id="cdPlayer" class="soundsystem.cdPlayer"
p:compactDisc-ref="compactDisc"/>
说明:当然和之前一样,也要在配置文件头部加上:
xmlns:p="http://www.springframework.org/schema/p"
相关内容和之前的c- 命名空间类似。
4.3.1 将字面量注入到属性中
相关配置基本上和c-命名空间一致,这里不再细说。虽然也不能使用p-命名空间来装配集合,但是可以使用spring util-命名空间中的一些功能类简化BlankDisc bean,首先在配置文件头部加上:
xmlns:util="http://www.springframework.org/schema/util"
说明:util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表bean。如下:
<util:list id="trackList">
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little...</value>
<value>Getting Better</value>
...
</util:list>
于是我们就可以使用p-命名空间简化属性配置了:
<bean id="compactDisc" class="soundsystem.BlankDisc"
p:title="Sgt.Peppers's Lonely Hearts Club Band"
p:artist="The Beatles"
p:tracks-ref="trackList"/>
说明:util-命名空间中还有很多其他元素:
| 元素 | 描述 |
|---|---|
<util:constant> |
引用某个类型的public static域,并将其暴露为bean
|
<util:list> |
创建一个java.util.List类型的bean,其中包含值或引用 |
<util:map> |
创建一个java.util.Map类型的bean,其中包含值或引用 |
<util:properties> |
创建一个java.util.Properties类型的bean
|
<util:property-path> |
引用一个bean属性(或内嵌属性),并将其暴露为bean
|
<util:set> |
创建一个java.util.Set类型的bean,其中包含值或引用 |
五、导入和混合配置
5.1 在JavaConfig中引用XML配置
现在假设CDPlayerConfig已经变得很复杂,需要将其拆分为多个配置:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc(){
return new SgtPeppers();
}
}
说明:此时compactDisc()方法已经从CDPlayerConfig中移除掉了,这里需要将两个配置组合在一起:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(compactDisc());
}
}
这样便使用@Import将两个配置组合在了一起,当然更好的方法是创建一个更高级别的SoundSystemConfig,在其中将两个配置组合在一起:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(CDPlayerConfig.class, CDConfig.class)
public class SoundSystemConfig{
}
说明:如果此时BlankDisc配置在了XML中(cd-config.xml),如何让spring同时加载它和其他基于Java的配置呢?如下:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig{
}
5.2 在XMl中配置引用JavaConfig
在XML导入XML配置如下:
<import resource="cd-config.xml">
在XML中导入JavaConfig配置如下:
<bean class="soundsystem.CDConfig">







网友评论