本章内容
- 声明bean
- 构造器注入和Setter方法注入
- 装配bean
- 控制bean的创建和销毁
在Spring中,对象无需自己查找或创建与其所关联的其他对象,相反,容器负责把需要协作的对象引用赋予给各个对象,创建应用对象之间协作关系的行为我们称之为装配(waring),这也是DI(依赖注入)的本质
注意 DI是Spring最基本的要素,所以在开发基于Spring的应用时,你随时都在使用这些技术
Spring配置中最常见的三种方法
当描述Bean如何进行装配时,Spring具有很大的灵活性,它提供了三种主要的装配机制
1.在Xml中进行显性配置
2.在java中进行显性配置
3.隐式的bean发现机制和自动装配
至于选择哪种装配机制,Spring的装配风格是多样的,你可以自由组合,但是应当尽可能的选择自动配置的机制,显式配置越少越好,当你需要显式配置bean的时候(比如这些代码不是由你维护,而你需要装配bean的时候),应当用类型安全并且比Xml更强大的JavaConfig,最后只有当你想要使用便利的Xml并且Java Config没有相关的实现时,才使用Xml
自动化装配bean(便利性最强)
Spring从两个角度来实现自动化装配
- 自动化扫描(componment Scanning):Spring会自动发现应用上下文中所创建的bean
- 自动配装(autowiring):Spring自动满足bean之间的依赖
组件扫描和自动装配组合在一起就能发挥强大的威力,他们能将你的显式配置降低到最少
创建可以被发现的bean
例子:这里有个cd播放器类,你必须将cd注入
package soundsystem;
public interface CompactDisc {
// 定义cd的接口概念
void play();
}
在这里,接口的内容不重要,重要的是你即将其定义为了接口,作为接口,他定义了CD播放器对一盘CD能做的操作,他将cd播放器与 cd的耦合度降到了最小的程度
我们去创建一个CompactDisc的具体实现,事实上可以有多个实现,目前我们只创建这一个,也就是下面的sgtPeppers类
带有@Component注解的CompactDisc实现类sgtPeppers
package soundsystem;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc{
private String title = "small love song";
private String artist = "jim";
@Override
public void play() {
System.out.println("playing"+title+"by"+artist);
}
}
和CompactDisc接口一样,sgtPeppers的具体内容不重要,重要的是sgtPeppers类上使用了@Component注解,这个简单的注解会表明该类会成为组件类,并告知Spring要为这个类创建bean
但是,组件扫描默认是不启用的,我们还需要显式的配置下Spring,从而命令他去寻找带有@Component注解的类,并为其创建bean,下面的程序完成了这项简单的配置
@ComponentScan开启了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//@ComponentScan注解开启了组件扫描
@ComponentScan
public class CDPlayerConfig {
}
类CDPlayerConfig通过java代码定义了Spring的装配规则,稍后再详细了解基于Java的详细配置,现在我们主要观察CDPlayerConfig类并没有显式的声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描
如果没有其他配置的话,@ComponentScan会首先扫描与配置类相同的包,因为CDPlayerConfig位于soundsystem包中,因此Spring将会扫描这个包与他之下的所有子包,找到带有@Conponent注解的类,这样就能发现CompactDisc,并且会在Spring中自动为其创建一个bean
通过Xml启用组件扫描
comtext:compoenet-scan会有与@CompoenetScan对应的属性和子元素
目前为止,创建了两个类,我们就已将能够完成组件测试了
看下面的测试实例(CompactDisc),我们创建一个简单的Junit测试,他会创建Spring上下文,并判断CompactDisc是不是真的被创建出来了
package soundsystem;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.Assert.assertNotNull;
//使用Spring的SpringJunit4ClassRunner,以便在测试开始的时候创建Spring的应用上下文
@RunWith(SpringJunit4ClassRunner.class)
//在ContextConfiguration告诉他需要在CDPlayConfig中加载配置
//因为CDPlayerConfig类中包含@ComponentScan,因此最终的应用上下文中应该包含CompactDisc bean
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayTest {
// @Autowired注解,以便将CompactDisc bean注入到测试代码中
@Autowired
private CompactDisc cd;
@Test
//简单的测试方法断言cd的属性不为null
// (不为null就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码中)
public void sdShowNotBeNull(){
assertNotNull(cd);
}
}
因为Spring实战这本书没有(maven)pom.xml依赖详情,所以程序会出现错误,可以参照我的pom.xml,就可以完成测试
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3.RC2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hamcrest/hamcrest-library -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
如果成功运行,测试区会显示绿色,至此为止,你已经写下一个简单的扫描组件测试类,
下面,我们将深入探讨@ComponentScan和@Component
为组件扫描的bean命名
Sprng应用上下文中的所有bean都会给定一个ID,之前的例子中,虽然没有给SgtPeppers bean设置ID,但Spring会根据类名为其指定一个ID,具体来说,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变小写.
-
如果想为这个bean设置不同的ID,就要你所要期望的ID作为值传递给@Component注解,比如想将这个bean表示为lonelyHeartsClub
@Component(“lonelyHeartsClub”)
//就应该在SgtPeppers类的@Component配置为如下所示
public class SgtPeppers implements CompactDisc{private String title = “small love song”;
private String artist = “jim”;
@Override
public void play() {
System.out.println(“playing”+title+“by”+artist);
}
} -
还有另一种为bean命名的方式,这种方式不使用@Component注解,而是使用java依赖注入规范( Java Dependency Injection)中所提供的@Named注解来为bean来设置ID
package soundsystem;
import javax.inject.Named;
//使用java依赖注入规范( Java Dependency Injection)中所提供的@Named注解来为bean来设置ID
@Named(“lonelyHeartsClub”)
public class SgtPeppers implements CompactDisc{private String title = “small love song”;
private String artist = “jim”;
@Override
public void play() {
System.out.println(“playing”+title+“by”+artist);
}
}
Spring支持将@Named()作为@Component注解的替代方案,双方有些细微差异,但是大多数场景中,他们是可以相互转换的
对我而言,@Component注解更具有目的性,@Named很容易不知道他是干什么的,因此在之后我将不会使用@Named
设置组件扫描的基础包
到现在为止,我们没有为@ComponentScan设置任何属性,这意味着,按照默认规则,他会以配置类所在的包作为基础包(base package)来扫描组件
有一个原因会促使我们明确的设置基础包,那就是我们想要将配置类放在单独的包中,使其与其他的代码分开来,但是这样的话,默认的基础包就不能满足要求了
为了满足需求,只需要在@ComponentScan的value属性中指明包的名称
@Configuration
//@ComponentScan注解开启了组件扫描
//在@ComponentScan的value属性中指明包的名称
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
如果你想更加清晰的表明你所设置的是基础包,那么你可以通过basePackage进行设置
@Configuration
//@ComponentScan注解开启了组件扫描
//可以通过basePackage属性更加清晰的表明所属包
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {
}
有趣的是,basePackages是一个复数属性,这就意味着你可以给配置类指定多个基础包,只需要将basePackage属性设置为要扫描的一个数组即可
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//@ComponentScan注解开启了组件扫描
@ComponentScan(basePackages = {"soundsystem", "video"})
public class CDPlayerConfig {
}
在上面的例子中,所设置的基础包是以String类型表示的,这种方法虽然可以,但是被认为是(not type-safe)类型不安全,因为如果你重构代码的话,那么所指定的基础包就可能会出现错误
除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {
}
我们为basePackageClasses属性所设置的数组中包含了类,这些类所在的包将会作为组件扫描的基础包
尽管在样例中,我为basePaseageClass设置的是组件类,但是你可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface),通过标记接口的方式,你依然能够保持对重构友好的接口引入,但是可以避免引用任何实际的应用程序代码(在稍后重构中,这些代码可能会从想要扫描的包中移除掉)
在你的应用中,如果所有队形都是独立的,彼此之间没有任何依赖,就像SgtPerpers bean 这样,那么你需要的就是组件扫描而已
但是,很多对象会依赖其他的对象才能完成任务,这样的话,我们就需要有一种方法能够将组件扫描到的bean和他们的依赖装配在一起,要完成这项任务,我们需要了解一下Spring自动化配置的另外一方面内容,那就是自动装配.
通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean,为了声明要自动装配,我们可以借助Spring的@Autowired注解
比如下面的CDPlayer bean的时候,会通过这个构造器来进行实例化并且传入一个可设置给CompactDisc类型的bean
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
//这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且传入一个可设置给CompactDisc类型的bean
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play(){
cd.play();
}
}
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上,比如说,如果CDPlayer有一个setCompactDisc方法,可以采用如下的注解形式进行自动装配
例如
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
在Spring初始化bean之后,他会尽可能的去满足bean的依赖,在本例中,依赖是通过@Autowired注解的方法进行注解声明的,也就是setCompactDisc();
实际上,@Autowired可以用在任何方法上,同样会发生作用
不管是任何方法,Spring都会尝试满足方法参数上所声明的依赖,假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来.
注意
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常.为了避免异常的出现,你可以将@Autowired的required属性设为false
@Autowired(required = false)
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
将required属性设置为false时,如果没有匹配的bean的话,Spring会将这个bean处于未装配的状态,但是你设置为false时,如果你的代码中没有Null检查的话,这个处于未装配状态的属性很可能会出现空指针异常(NullPointerException),相反如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确的指定要选择哪个bean进行装配
@Autowired是Spring特有的注解,如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以将其替换为@Inject
@Inject
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
@Inject来源于java依赖注入规范,同时提供了@Name和@Inject注解,在大多数场景下,@Inject和@Autowired可以替换,你可以根据自己的情况,任意选择一个.
验证自动装配
现在,我们已经在CDPlayer的构造器中添加了@Autowired注解,Spring将把一个可分配给CompactDisc类型的bean注入进来,为了验证这一点,让我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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.assertNotNull;
//使用Spring的SpringJunit4ClassRunner,以便在测试开始的时候创建Spring的应用上下文
@RunWith(SpringJUnit4ClassRunner.class)
//在ContextConfiguration告诉他需要在CDPlayConfig中加载配置
//因为CDPlayerConfig类中包含@ComponentScan,因此最终的应用上下文中应该包含CompactDisc bean
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayTest {
@Rule
// 这个方法被列为不推荐使用,就是说他可能在下个jdk版本中被删除
// 在测试代码中使用System.out.print()是很棘手的事,所以使用StandardOutputStreamLog规则,该规则基于控制台的输出编写断言
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired
private MediaPlayer player;
// @Autowired注解,以便将CompactDisc bean注入到测试代码中
@Autowired
private CompactDisc cd;
@Test
//简单的测试方法断言cd的属性不为null
// (不为null就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码中)
public void sdShowNotBeNull(){
assertNotNull(cd);
}
@Test
public void play(){
player.play();
assertEquals(
"playing Sgt,small love song"+"jim\n",
log.getLog()
);
}
}
现在,除了注入CompactDisc,我们还将CDPlayer bean注入到测试代码的player成员变量之中(他是更为通用的Mediaplayer类型),在player测试方法中,我们可以调用CDPlayer的plar()方法,并断言他的行为与你预期的一致
现在,我们已经了解了组件扫描和自动装配的基础知识,在之后我们介绍如何处理自动装配的歧义性时,还会继续研究组件扫描
但是现在,我们先将组件扫描和自动装配放在一边,看一下Spring中如何显式的配置bean,首先从java代码编写配置开始
通过java代码装配bean
尽管大多数场景可以实现Spring自动化装配,但是当你想将第三方库中的应用和组件装配到你的应用中,是没办法在他的类上添加@Component和@Autowired注解的,因此就不能使用自动化的装配方案了
在这种情况下,你必须学会显式装配的方式,在进行显式装配的时候,有两种可选择方案:java和Xml
在进行显式配置时,javaConfig是更好的方案
- 更强大,类型安全,和重构友好
- 他就是java代码,就像应用程序中的java代码一样
javaConfig和其他的java代码又有所区别
- 在概念上,他与应用程序中的业务逻辑和领域代码是不同的,javaConfig是配置代码
- javaConfig不应该包含任何业务代码,也不应该侵入到业务代码中
- 应该将javaConfig放到单独的包中,使他与其他的应用程序逻辑分离开来
如何javaConfig显式配置Spring
1. 创建配置类
此时移除了,@ConponentScan,CDPlayerConfig类就没有任何作用了,因为组件扫描发现不了他们
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//@ConponentScan
public class CDPlayerConfig {
}
2. 声明简单的bean
要在JavaConfig中声明bean
-
我们需要编写一个方法
-
这个方法会创建所需类型的实例
-
然后给这个方法添加bean注解
例如 @Bean public CompactDisc sgtPeppers(){ return new SgtPeppers(); } //方法体中包含了最终产生bean实例的逻辑
@Bean注解会告诉Spring这个方法将会产生一个对象,该对象要注册为Spring应用上下文中的bean
默认情况下,bean的Id与bean的方法名是一样的,如果你想重命名,也可以通过name属性指定一个不同的名字
@Bean(name="smallLoveSong")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
因为是使用java进行描述的,因此我们可以发挥java提供的所有功能,只要最终返回一个CompactDisc实例即可
例如做点更疯狂的事情,随机选择一首cd播放
@Bean
public CompactDisc randomBeatlesCD(){
int choice = (int)Math.floor(Math.random()*4);
if(choice == 0){
return new SgtPeppers();
}else if(choice == 1){
return new LinJunJie();
}else if(choice ==2 ){
return new ZhouJieLun();
}else(choice == 3){
return new ChenYiXun();
}
}
可以尽你最大的想象,发挥java的全部威力来产生bean
我们回过头来看下,在JavaConfig中,如何将CompactDisc注入到CDPlayer中
3.借助JavaConfig实现注入
我们前面声明的CompactDisc bean是很简单的,他自身没有其他的依赖,但现在我们需要声明CDPlayer bean
在javaConfig中装配bean的最简单方式就是引用创建bean的方法
例如:下面就是一种声明CDPlayer的可行方案:
@bean
public CDPlayer cdPlayer(){
return new CDPlayer(SgtPeppers());
}
在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来 创建CDPlayer实例
看起来,CompactDisc是通过调用sgtPerppers()得到的,但实际上,Spring中的bean默认情况下都是单例的,因为sgtPrppers()方法上添加了@bean注解,Spring将会拦截所有对他的调用,并确保直接返回该方法创建的bean,而不是每次都进行实际的调用
除了方法调用,还有一种理解起来更简单的方式
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
原理: cdPlayer()方法请求一个CompactDisc作为参数,当Spring调用cdPlayer方法()创建CDPlayerbean的时候,他会自动装配一个CompactDisc到配置方法中,然后,方法体就可以按照合适的方法来使用他.
- 通过这种方式引用其他bean通常是最好的选择
- 你可以将配置分散到多个配置类,Xml文件以及自动扫描和装配bean中(只要功能健全)
我们在这里使用了CDPlayer的构造器实现了DI功能,但是我们完全可以使用其他风格的DI配置
例如:你想通过Setter方法注入CompactDisc的话
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CompactDisc();
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
重点
带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例,构造器和Setter方法只是@bean的两个简单样例,这里所存在的可能性仅仅收到java语言的限制
通过Xml装配bean
学习原因:
Xml不应该是你的第一选择了,但是鉴于已经存在很多基于Xml的Spring配置,所以理解和使用如何在Spring中使用Xml配置还是很重要的,但是这部分知识只是为了维护现有的Xml配置,在新的Spring工作时,最好使用自动化配置和JavaConfig.
-
1. 创建Xml配置规范
在Xml配置中,这意味着要创建一个Xml文件,并且要以为根
这是一个最简单的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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
可以通过编辑器自动创建Spring Xml文件,并且可以选择可用的命名空间
-
用来装配bean的最基本的Xml元素包含在Spring-beans模式之中,在上面这个Xml文件当中,是该模式中的一个元素,他是所有Spring配置文件的根元素.
-
在他没有声明任何bean之前,他是一个没有任何用处的配置,而不是使用JavaConfig和自动化配置.
2.声明一个简单的
要在基于Xml的Spring配置中声明一个bean,我们要使用Spring-beans模式的另外一个元素,元素类似于javaConfig中的@Bean注解.
例如
<bean class="soundsystem.SgtPeppers"/>
//这里声明了一个简单的bean,创建这个bean是通过class属性来指定的,并且要是用全限定的类名
由于没有给定一个Id,在本例中的Id将会是 “soundsystem.SgtPeppers#0”,其中 "#0"是一个计数的形式,如果你声明了另一个SgtPeppers,且没有明确标识,那么他自动得到的ID将是 “soundsystem.SgtPeppers#1”
通常来讲,更好的办法是借助id属性,为每个bean设置一个你自己选择的名字
<bean id= "CompactDisc" class="soundsystem.SgtPeppers"/>
稍后将这个bean装配到CDPlayer bean之中的时候会用到这个Id
减少繁琐为了减少Xml中繁琐的配置,只对那些需要按名字引用的bean(比如,需要将他们的引用注入到另一个bean中)进行准确的命名
简单bean声明的特质
- 你不需要直接创建SetPeppers的实例
- 在这个简单的声明中,我们将bean以字符串的形式设置在了class属性中
借助构造器来初始化bean
在SpringXml中,只有一种声明bean的方式:使用元素并指定class属性.
Xml有两种基本的配置方案可供选择:
- 元素
- 使用Spring3.0所引入的c-命名空间
两者区别:是否冗长繁琐
- 更加冗长,从而导致Xml更加难懂
- 有些事情c-命名空间难以做到
构造器注入bean引用
在Xml中声明CDPlayer并通过ID引用SgtPeppers
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
- 当Spring遇到这个bean时,它会创建一个cdPlayer实例
- constructor-arg元素会告知Spring要将一个ID为compactDisc的bean传入CDPlayer的构造器中
作为替代方案,你也可以使用Spring的c-命名空间,是在Spring3.0中引入的,他是在Xml中更为简洁的描述构造器参数的方式.要使用它的话,必须在Xml顶部声明其他模式
在c- 命名空间和模式声明之后,我们就可以使用他来声明构造器参数了
例如:
//用c-命名空间来声明构造器函数
<bean id = "cdPlayer" class = "soundsystem.CDPlayer"
c:cd-ref="compactDisc"/>
- c 命名空间的前缀
- cd 构造器参数名
- ref 注入bean的引用
- " " 要注入的bean的Id
c-命名空间的优化
原因:c-命名空间直接引用了参数的名称,这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中,如果你优化构建过程,将调试标志除掉,那么这种方式就无法正常执行了
替代方案:
1.使用参数在整个参数列表的位置信息
//使用索引来识别构造器参数
<bean id = "cdPlayer" class = "soundsystem.CDPlayer"
c:_0-ref="compactDisc"/>
2.只有一个构造器参数时(这是c-命名空间中最奇特的一个)
//相比上面连索引都不用带
<bean id = "cdPlayer" class = "soundsystem.CDPlayer"
c:_-ref="compactDisc"/>
接下来看如何将字面量值装配到(literal value)装配到构造器之中
有时候,我们需要做的只是一个字面量来配置对象
假设
package soundsystem;
public class BlankDisc implements CompactDisc{
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
@Override
public void play() {
System.out.println("playing" +title+"song"+artist);
}
}
相比之前的SgtPeppers,你更能指定歌曲与艺术家,现在我们将SgtPeppers换下来
1. //使用<constructor-arg>元素进行构造器注入
<bean id = "CompactDisc"
class = "soundsystem.BlackDisk">
<constructor-arg value="Small Love Song"/>
<constructor-arg value="Jim"/>
<bean/>
2.//使用c-命名空间
<bean id="CompactDisc" class = "soundsystem.BlackDisc"
c:_0 = "Small Love Song"
c:_1 = "Jim"/>
在装配bean引用和字面量值方面,和c-命名空间的功能是相同的.
但是有一种情况是能够实现,但是c-命名空间做不到的,让我们看看如何让将集合装配到构造器参数中
装配集合
到现在为止,我们假设CompactDisc只有艺术家和歌曲的内容,但实际上CD上会有十多个磁道,每个磁道上包含一首歌
例如:CompactDisc为真正的磁带建模,他应该是这样
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;
}
@Override
public void play() {
System.out.println("playing" +title+"song"+artist);
for(String track:tracks){
System.out.println("-track:"+track);
}
}
}
这个变更会对Spring如何配置产生影响,在声明bean的时候,我们必须要提供一个磁道列表
方式:
-
最简单的方式是将列表设置为null,将null传给构造器.但这不是解决问题的好办法,但在注入期他能正常执行(当调用play()方法时,他会出现NullPointException异常)
-
更好的办法是提供一个磁道名称的列表, 要达到这一点,我们可以有多个可选方案,首先,可以用元素将其声明为一个列表
//这表明一个包含值的列表将会传递到构造器中,其中value元素用来指定列表中的每个元素
Small Love Song B
Small Love Song C
Small Love Song D
Small Love Song E
与之类似,我们也可以使用元素代替value,实现bean引用列表的装配.
例如:有一个Discography类
//他的构造函数如下
public Discography(String artist,List<CompactDisc> cds){...};
那么可以用下面的方式配置bean
<bean id = "beatlesDiscography"
class = "soundsystem.Discography">
<constructor-arg value="The beats"/>
<constructor-arg >
//这表明一个包含值的列表将会传递到构造器中,其中value元素用来指定列表中的每个元素
<List>
<ref bean = "ZhouJieLun"/>
<ref bean = "LinJunJie"/>
<ref bean = "ChenYiXun"/>
<ref bean = "CaiJianYa"/>
<List/>
<constructor-arg/>
<bean/>
当构造器参数类型是java.unit.List时,使用 List元素时合情合理的,尽管如此,我们还可以按照同样的方式使用元素
<bean id = "CompactDisc"
class = "soundsystem.BlackDisk">
<constructor-arg value="Small Love Song"/>
<constructor-arg value="Jim"/>
<constructor-arg >
//这表明一个包含值的列表将会传递到构造器中,其中value元素用来指定列表中的每个元素
<set>
<value> Small Love Song B</value>
<value> Small Love Song C</value>
<value> Small Love Song D</value>
<value> Small Love Song E</value>
<set/>
<constructor-arg/>
<bean/>
和元素的区别
- set的话,所有重复的值都会被忽略掉,存放顺序也不会受到保证
- 但无论在那种情况下,和都可以用来装配List,Set甚至数组
目前,使用c-命名空间的属性无法达到实现装配集合的功能
通过Xml来设置属性
选择构造器注入和属性注入的情况
-
对强依赖使用构造器注入
-
对可选性依赖使用属性注入
CDPlayer没有任何的构造器(除了隐含的默认构造器),他也没有任何的强依赖
//将其声明为Spring bean
但因为我们没有注入CDPlayer的CompactDisc属性,因此在测试中会出现空指针异常
按照下面方法修改Xml配置可以解决该问题
<bean/>
为属性的Setter方法提供的功能与元素为构造器所提供的功能是一样的.
原理:他通过ref引用了Id为compactDisc的bean,并将其注入到compactDisc属性中(通过setCompactDisc()方法),现在进行测试就可以通过
元素替代方案
-
Spring为
p-命名空间的属性与c命名空间相似
- p:命名空间声明
- compactDisc:属性名
- ref:注入bean引用
- compactDisc:所注入的bean
将字面量注入到属性中
属性注入字面量与构造器参数非常相似
//BlackDisc完全通过属性注入,而不是构造器注入 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 setTitle(String title) { this.title = title; } public void setArtist(String artist) { this.artist = artist; } public void setTracks(List<String> tracks) { this.tracks = tracks; } @Override public void play() { System.out.println("playing" +title+"song"+artist); for(String track:tracks){ System.out.println("-track:"+track); } } }
现在他不再要求我们装配任何的属性,现在创建一个属性都是空的BlankDiscbean
<bean id = "reallyBlankDisc" class = "soundsystem.BlankDisc" />
下一步,我们需要借助的value属性来装配这些属性
<bean id = "CompactDisc" class = "soundsystem.BlackDisk"> <properly name ="title" value="Small Love Song"/> <properly name = "artist" value="Jim"/> <properly name = "tracks"> //这表明一个包含值的列表将会传递到构造器中,其中value元素用来指定列表中的每个元素 <List> <value> Small Love Song B</value> <value> Small Love Song C</value> <value> Small Love Song D</value> <value> Small Love Song E</value> <List/> <properly/> <bean/>
我们也可以通过p-命名空间来装配这些属性,但是它不能装配集合,我们可以通过util-命名空间的一些功能来简化BlankDisc bean
//同样要现在Xml中声明util-命名空间及其模式
util-命名空间的功能之一就是util:list元素
原理:他会创建一个列表的bean,并且声明到单独的bean之中
<util:list id = "trackList"> <value> Small Love Song B</value> <value> Small Love Song C</value> <value> Small Love Song D</value> <value> Small Love Song E</value> </util:list>
然后就像使用其他bean那样,将列表bean注入带tracks属性中
<bean id = "CompactDisc" class = "soundsystem.BlackDisk" p:title ="Small Love Song" p:artist ="Jim" p:tracks-ref = "trackList"/>
util:List只是util-命名空间中的多个元素之一
Spring 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,包含值和引用
导入和混合配置
在典型的Spring应用中,我们会同时使用自动化和显式配置
关于混合配置: 需要明白自动装配时,它并不在意bean的来源
假如希望将BlankDisc bean拆分到他自己的配置文件中(比如cd-config.xml),在Xml配置文件中,可以使用元素来引用该文件
<bean class="soundsystem.CDConfig"/> <bean id = "cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
现在,我们假设将其配置在JavaConfig当中,CDPlayer则继续配置在Xml当中
<bean class="soundsystem.CDConfig"/> <bean id = "cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
采用这样的方式,两种配置被组合在一起
采用更高层次的配置文件,这个文件不声明任何bean,只是负责将两个或者更多的配置组合起来
<bean class= "soundsystem.CDConfig"/> <import resource = "cdplayer-config.xml">
不管是使用JavaConfig还是Xml进行装配,推荐创建一个根配置,这个配置会将更多的配置类和Xml文件组合起来
小结
- Spring框架的核心就是Spring容器,容器负责管理应用中组件的生命周期,它会创建这些组件并保证他们的依赖能够得到满足
- 尽可能的使用自动化配置,以避免显式配置带来的维护成本
- 不得不使用显式配置的时候,优先使用javaConfig
- java语言描述比Xml功能强大,易于重构和类型安全
- DI(依赖注入)是Spring技术的重要组成部分