声明:后面的章节是看了《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
的方式:
说明:
元素还有一些属性和子元素,这里不细说,加入我们将之前的@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 声明一个简单的
这里我们需要添加一个
元素,其类似于JavaConfig
中的@Bean
:
说明:这里声明了一个简单的bean
,创建这个bean
的类通过class
属性来指定,并且要使用全限定的类型。而这里没有明确指定ID
,所以这个bean
将会根据全限定类名来进行命名,这里即为"soundsystem.SgtPeppers#0"
,其中"#0"
是一个计数的形式,用来区分相同类型的其他bean
,如果另外声明一个SgtPeppers
,并且没有明确指定ID
,那么其ID
即为"soundsystem.SgtPeppers#1"
。
4.2 借助构造器注入初始化bean
在XML
中声明DI
时,会有多种可选的配置方案和风格,具体到构造器注入,有两种基本的配置方案:
-
元素 - 使用
spring3.0
所引入的c-
命名空间
4.2.1 构造器注入bean引用
之前我们已经声明了一个SgtPeppers
的bean
,并且这个类实现了CompactDisc
接口,所以实际上我们已经有了一个可以注入到CDPlayer bean
中的bean
,所以现在要做的就是通过DI
引用SgtPeppers
:
说明:spring
会创建一个CDPlayer
实例,同时
会告知spring
要将一个ID
为compactDisc
的bean
引用传递到CDPlayer
的构造器中。
当然作为替代方案,可以使用c-
命名空间,只是需要在XML
配置的顶部声明其模式:
......
说明:于是可以将之前的配置方法改为如下:
说明:属性名以"c-"
开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是"-ref"
,它会告诉spring
,正在装配的是一个bean
的引用,这个bean
名字是comapactDisc
,而不是字面量"comapactDisc"
。
注意:这里的"cd"
是构造器参数名,但是直接使用参数名可能不太好,我们也可以这样:
说明:这里使用数字表示构造器参数的位置,也就是第几个参数,由于在XML
中不允许数字作为属性的第一个字符,所以在前面加了一个下划线。当然如果只有一个构造器参数,可以将数字拿掉也可以。
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
类中,唱片名称和艺术家的名字都是硬编码的,这里我们让其更加灵活:
说明:这里使用value
属性,就是表示给定的值要以字面量的行驶证注入到构造器中。如果要使用"c-"
命名空间,则配置如下:
当然亦可以这样:
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 tracks;
public BlankDisc (String title,String artist,List 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
列表:
Sgt. Pepper's Lonely Hearts Club Band
With a Little...
Getting Better
...
说明:当然集合列表也可以配置为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
配置实现属性配置的方式如下:
说明:
元素为属性的Setter
方法所提供的功能与
元素为构造器提供的功能是一样的。使用命名空间的方式为:
说明:当然和之前一样,也要在配置文件头部加上:
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-
命名空间所提供的功能之一就是
元素,它会创建一个列表bean
。如下:
Sgt. Pepper's Lonely Hearts Club Band
With a Little...
Getting Better
...
于是我们就可以使用p-
命名空间简化属性配置了:
说明:util-
命名空间中还有很多其他元素:
元素 | 描述 |
---|---|
|
引用某个类型的public static 域,并将其暴露为bean |
|
创建一个java.util.List 类型的bean ,其中包含值或引用 |
|
创建一个java.util.Map 类型的bean ,其中包含值或引用 |
|
创建一个java.util.Properties 类型的bean |
|
引用一个bean 属性(或内嵌属性),并将其暴露为bean |
|
创建一个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
配置如下:
在XML
中导入JavaConfig
配置如下: