控制反转的概念:控制反转是一种通过描述(在Java中或者是XML或者注解)并通过第三方去产生或获取特定对象的方式。
在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection, DI)。
在Spring中,对象无需自己查找或者创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
依赖注入的3种方式:
- 构造器注入:构造器注入依赖于构造方法实现,而构造方法可以是有参数或者无参数的。在大部分情况下,都是通过类的构造方法来创建类对象,Spring也可以采用反射的方式,通过使用构造方法来完成注入,这就是构造器注入的原理。使用<constructor-arg index="0" value="参数值"/>来对构造器中第一个参数赋值,其他同理。
- setter注入:是Spring中最主流的注入方式,它利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高。它消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后使用setter注入为其设置对应的值,其实也是通过Java反射技术实现的。
- 接口注入:有些时候资源并非来自于自身系统,而是来自于外界,比如数据库链接资源完全可以在Tomcat下配置,然后通过JNDI的形式去获取它,这样数据库连接资源是属于开发工程外的资源,这个时候可以采取接口注入的形式类获取它。
一、Spring配置的可选方案。
Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系,当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置
- 在Java中进行显式配置
- 隐式Bean的发现机制和自动装配
原则上,有三条准则:
- 尽可能地使用自动装配的机制,显式配置越少越好。
- 当你必须要显式配置bean的时候(有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全并且比XML更加强大的JavaConfig。
- 只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
二、自动化装配bean
在便利性方面,最强大的还是Spring的自动化配置。
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显示配置降低到最少。
利用带有注释的代码来解释这种装配方式:
- 第一种方式是通过Java代码定义了Spring的装配规则:
代码结构为:
示例程序为:
CompactDisc接口:
1 package autoConfig1;
2 /**
3 * 如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实没有太大用处的。
4 * 所以可以这样说,CD播放器依赖于CD才能完成它的使命。
5 * CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。
6 * 它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
7 */
8 public interface CompactDisc {
9
10 void play();
11 }
MediaPlayer接口:
1 package autoConfig1;
2 /**
3 * MediaPlayer接口作为CD播放器的接口。
4 */
5 public interface MediaPlayer {
6
7 void play();
8
9 }
CDPlayerConfig类用于开启Spring的组件扫描:
1 package autoConfig1;
2 import org.springframework.context.annotation.ComponentScan;
3 import org.springframework.context.annotation.Configuration;
4 /**
5 * @Configuration用于定义配置类,可替换XML文件。
6 * @ComponentScan注解能够在Spring中启用组件扫描:
7 * 1.如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。
8 * 2.因此Spring将会扫描autoConfig1包以及这个包下的所有子包,寻找带有@Component注解的类。
9 * 3.由于SgtPeppers类带有@Component注解,所以被发现了,并且会在Spring中自动为其创建一个bean。
10 */
11 @Configuration
12 @ComponentScan
13 // 类CDPlayerConfig通过Java代码定义了Spring的装配规则,并没有显式地声明任何bean。
14 public class CDPlayerConfig {
15
16 }
CompactDisc接口的组件类SgtPeppers类:
1 package autoConfig1;
2 import org.springframework.stereotype.Component;
3 /**
4 * 《Sgt. Pepper's Lonely Hearts Club Band》 是英国摇滚乐队The Beatles发行的第8张录音室专辑。
5 * 在SgtPeppers类上使用了@Component注解。
6 * 组件扫描默认是不启用的,还需要命令Spring去寻找带有@Component注解的类,并为其创建bean。
7 */
8 @Component // 这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
9 public class SgtPeppers implements CompactDisc {
10
11 private String title = "Sgt. Pepper's Lonely Hearts Club Band";
12 private String artist = "The Beatles";
13
14 public void play() {
15 System.out.println("Playing " + title + " by " + artist);
16 }
17
18 }
MediaPlayer接口,并且自动装配CompactDisc
1 package autoConfig1;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.stereotype.Component;
5
6 /**
7 * 声明CDPlayer类作为组件类,并且添加注解实现自动装配。
8 * 自动装配就是让Spring自动满足bean依赖的一种方法。
9 * 在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。
10 * 如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。
11 * 为了避免异常的出现,可以使用@Autowired(required=false),让没有匹配的bean处于为匹配状态。
12 * 但是,这种情况如果没有进行null检查的话,这个处于为装配状态的属性有可能会出现空指针异常。
13 */
14 @Component // 这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
15 public class CDPlayer implements MediaPlayer {
16 private CompactDisc cd;
17 // 1.在CDPlayer类的构造器上添加@Autowired注解。
18 // 2.这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化,
19 // 3.并且会传入一个可设置给CompactDisc类型的bean。
20 @Autowired
21 public CDPlayer(CompactDisc cd) {
22 this.cd = cd;
23 }
24
25 public void play() {
26 cd.play();
27 }
28
29 }
CDPlayerTest,包括两部分的测试:
1 package autoConfig1;
2 import static org.junit.Assert.*;
3 import org.junit.Rule;
4 import org.junit.Test;
5 import org.junit.contrib.java.lang.system.SystemOutRule;
6 import org.junit.runner.RunWith;
7 import org.springframework.beans.factory.annotation.Autowired;
8 import org.springframework.test.context.ContextConfiguration;
9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10
11 // 声明测试套件运行器,为了让测试在Spring容器环境下执行,以便在测试开始的时候自动创建Spring的上下文。
12 @RunWith(SpringJUnit4ClassRunner.class)
13 // 1.告诉Spring要在CDPlayerConfig中加载配置,因为CDPlayerConfig类中包含了@ComponentScan,启动了Spring的组件扫描。
14 // 2.由于Spring启动了组件扫描,因此可以扫描出所有带有@Component注解的类,即SgtPeppers类和CDPlayer类,并且在Spring中为其创建一个bean。
15 @ContextConfiguration(classes = CDPlayerConfig.class)
16 public class CDPlayerTest {
17
18 @Rule // 这个注解是为了在执行case的时候加入测试者特有的操作,而不影响原有的case代码:减小了特有操作和case原逻辑的耦合。
19 public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
20
21 // 将MediaPlayer bean注入到测试代码之中。
22 @Autowired
23 private MediaPlayer player;
24
25 // 将CompactDisc bean注入到测试代码之中。
26 @Autowired
27 private CompactDisc cd;
28
29 // 简单的测试断言cd属性不为null。
30 // 如果它不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
31 @Test
32 public void cdShouldNotBeNull() {
33 assertNotNull(cd);
34 }
35
36 // 简单的测试断言player属性不为null。
37 // 如果它不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
38 @Test
39 public void playerShouldNotBeNull() {
40 assertNotNull(player);
41 }
42
43 // systemOutRule规则可以基于控制台的输出编写断言,这里断言play()方法的输出被发送到了控制台。
44 @Test
45 public void play() {
46 player.play();
47 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", systemOutRule.getLog());
48 }
49
50 }
- 第二种方式是通过XML配置文件定义了Spring的装配规则:
代码结构为:
XML配置文件,用于开启Spring的组件扫描:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:c="http://www.springframework.org/schema/c"
6 xmlns:p="http://www.springframework.org/schema/p"
7 xsi:schemaLocation="http://www.springframework.org/schema/beans
8 http://www.springframework.org/schema/beans/spring-beans.xsd
9 http://www.springframework.org/schema/context
10 http://www.springframework.org/schema/context/spring-context.xsd">
11
12 <!-- 使用XML来启用组件扫描 -->
13 <context:component-scan base-package="autoConfig2" />
14
15 </beans>
用于测试XML自动装配的测试类,通过定义配置文件的路径加载配置信息,同样包括两部分的测试。
1 package autoConfig2;
2
3 import static org.junit.Assert.*;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 // 设置配置文件xml文件的路径,Spring回去这个路径下面去寻找配置文件中的相关配置。
15 @ContextConfiguration(locations = "classpath:autoConfig2/autoConfig2.xml")
16 public class CDPlayerXMLConfigTest {
17
18 @Rule
19 public final SystemOutRule log = new SystemOutRule().enableLog();
20
21 @Autowired
22 private MediaPlayer player;
23
24 @Autowired
25 private CompactDisc cd;
26
27 @Test
28 public void cdShouldNotBeNull() {
29 assertNotNull(cd);
30 }
31
32 @Test
33 public void play() {
34 player.play();
35 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
36 }
37
38 }
·三、通过Java代码装配bean
如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。在这种情况下,必须要采用显式装配的方式。
在进行显式配置的时候,有两种方案可以选择:
- Java
- XML
在进行显式装配的时候,JavaConfig是更好的方案,因为它更为强大,类型安全并且对重构友好。因为它就是Java代码,就像应用程序中其他Java代码一样。
尽管它与其他的组件一样都使用相同的语言进行表述,但是JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。
尽管不是必须的,但通常会将JavaConfig放到单独的包中,使他与其他的应用程序逻辑分离开,这样对于它的意图就不会产生困惑了。
代码结构为:
CompactDisc接口,和之前一样没有变化。
1 package javaConfig;
2 /**
3 * 如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实没有太大用处的。
4 * 所以可以这样说,CD播放器依赖于CD才能完成它的使命。
5 * CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。
6 * 它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
7 */
8 public interface CompactDisc {
9
10 void play();
11 }
MediaPlayer接口,和之前一样也没有变化。
1 package javaConfig;
2 /**
3 * MediaPlayer接口作为CD播放器的接口。
4 */
5 public interface MediaPlayer {
6
7 void play();
8
9 }
SgtPeppers类,和之前的不一样,少了@Component注解:
1 package javaConfig;
2
3 public class SgtPeppers implements CompactDisc {
4
5 private String title = "Sgt. Pepper's Lonely Hearts Club Band";
6 private String artist = "The Beatles";
7
8 public void play() {
9 System.out.println("Playing " + title + " by " + artist);
10 }
11
12 }
CDPlayer类,和之前不一样,同样少了@Component注解:
1 package javaConfig;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4
5 public class CDPlayer implements MediaPlayer {
6
7 private CompactDisc cd;
8
9 @Autowired
10 public CDPlayer(CompactDisc cd) {
11 this.cd = cd;
12 }
13
14 public void play() {
15 cd.play();
16 }
17
18 }
在这之前的例子中,都是通过@Component装配Bean,但是@Component只能注解在类上,不能注解到方法上。对于Java而言,大部分的开发都需要引入第三方的包(jar文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让它们变为开发环境的Bean。但可以使用新类扩展(extends)其包的类,然后在新类上使用@Component注解,这样显得不伦不类。
为了解决这个问题,Spring的@Bean注解可以在方法上使用,并且将方法返回的对象作为Spring的Bean。
CDPlayerConfig配置类:
1 package javaConfig;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5 /**
6 * 创建JavaConfig类的关键在于为其添加@Configuration注解
7 * 在没有@ComponentScan注解的情况下,即不开启组件扫描时,会出现BeanCreationException异常。
8 * 要在JavaConfig中声明bean,需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。
9 * 构造器和Setter方法,是支持@Bean方法的两个简单的例子,可以采用任何必要的Java功能来产生bean实例。
10 */
11 @Configuration // 这个注解表明这个类是一个配置类,该类包含在Spring应用上下文中如何创建bean的细节。
12 public class CDPlayerConfig {
13
14 //第一种情况是:CompactDisc bean是非常简单的,它自身没有其他的依赖。
15 //@Bean注解会告诉Spring的是compactDisc方法将会返回一个SgtPeppers对象,该对象要注册为Spring应用上下文中的bean。
16 @Bean
17 public CompactDisc sgtPeppers() {
18 return new SgtPeppers();
19 }
20
21 // 第二种情况是:CDPlayer bean依赖于CompactDisc bean
22 // @Bean注解会告诉Spring的是cdPlayer方法将会返回一个CDPlayer对象,该对象要注册为Spring应用上下文中的bean。
23 // 1.当Spring调用cdPlayer方法创建CDPlayer bean的时候,它会自动装配一个CompactDisc bean到配置方法中。
24 // 2.然后,方法体就可以按照合适的方式来使用它。
25 // 3.cdPlayer方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
26 // 4.不管CompactDisc bean是通过什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。
27 @Bean
28 public CDPlayer cdPlayer(CompactDisc compactDisc) {
29 return new CDPlayer(compactDisc);
30 }
31
32 }
CDPlayerTest类:
1 package javaConfig;
2
3 import static org.junit.Assert.*;
4 import org.junit.Rule;
5 import org.junit.Test;
6 import org.junit.contrib.java.lang.system.SystemOutRule;
7 import org.junit.runner.RunWith;
8 import org.springframework.beans.factory.annotation.Autowired;
9 import org.springframework.test.context.ContextConfiguration;
10 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11
12 @RunWith(SpringJUnit4ClassRunner.class)
13 @ContextConfiguration(classes = CDPlayerConfig.class)
14 public class CDPlayerTest {
15
16 @Rule
17 public final SystemOutRule log = new SystemOutRule().enableLog();
18
19 @Autowired
20 private MediaPlayer player;
21
22 @Test
23 public void play() {
24 player.play();
25 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
26 }
27
28 }
另外,由于@Bean不能在类上使用,只能使用在方法上,因此要想在注解中实现自定义的初始化方法和销毁方法,也可以通过@Bean的配置项来实现,@Bean的配置项包含4个配置:
- name:是一个字符串数组,允许配置多个BeanName
- autowire:标识是否是一个引用的Bean对象,默认值是Autowire.NO
- initMethod:自定义初始化方法
- destroyMethod:自定义销毁方法
例如:
@Bean(name="juiceMaker2", initMethod="init", destroyMethod="myDestroy")
public JuiceMaker2 initJuiceMaker2(){
JuiceMaker2 juiceMaker2 = new JuiceMaker2();
juiceMaker2.setBeverageShop("贡茶")
Source source = new Source();
source.setFruit("橙子");
source.setSize("大杯");
source.setSugar("少糖");
juiceMaker2.setSource(source);
return juiceMaker2;
}
四、通过XML装配bean
在Spring刚刚出现的时候,XML是描述配置的主要方式。但是,Spring现在有了强大的自动化配置和基于Java的配置,XML不应该再是第一选择了。
在基于XML的Spring配置中声明一个bean,要使用 <bean></bean>标签,相当于JavaConfig中的@Bean注解。
在没有明确给定ID的情况下,需要通过class属性指定“包名+类名”来指定bean类,创建的bean将会根据全限定类名类命名:
<bean class="soundsystem.BlankDisc" />
尽管自动化的bean命名方式非常方便,但如果稍后引用的话,自动产生的名字就没有多大的用处了,因此,通常更好的方法是借助id属性,为每个bean设置一个你自己选择的名字:
<bean id="compactDisc" class="soundsystem.BlankDisc" />
同样 x1,在JavaConfig中,也可以给bean命名:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
同样 x2,在自动装配中,也可以给bean命名:
@Component("lonelyHeartClubBand")
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);
}
}
那么再来分析一下XML方式配置bean的一些特征:
<bean id="compactDisc" class="soundsystem.BlankDisc" />
- 在基于JavaConfig的配置中,已经知道了通过@Bean注解,可以不用再创建BlankDisc的实例了。同样,当Spring发现这个<bean>元素时,它将会调用BlankDisc的默认构造器来创建bean
- 在这个简单的<bean>声明中,将bean的类型以字符串的形式设置在了class属性中,但是,如何保证给class属性的值是真正的类呢?万一对类进行重命名就用不了了,这是XML配置的一个重大的缺点。
下面通过几个典型的分类举例说明XML配置方式可以实现哪些功能。
1.使用<constructor-arg>元素实现依赖注入。
代码结构为:
CompactDisc接口:
1 package xmlConfigTest1;
2
3 public interface CompactDisc {
4
5 void play();
6
7 }
MediaPlayer接口:
1 package xmlConfigTest1;
2
3 public interface MediaPlayer {
4
5 void play();
6
7 }
CompactDisc 接口的SgtPeppers类(正常Java代码,没有任何注解):
1 package xmlConfigTest1;
2
3 public class SgtPeppers implements CompactDisc {
4
5 private String title = "Sgt. Pepper's Lonely Hearts Club Band";
6 private String artist = "The Beatles";
7
8 public void play() {
9 System.out.println("Playing " + title + " by " + artist);
10 }
11
12 }
MediaPlayer接口的CDPlayer类(正常Java代码,没有任何注解):
1 package xmlConfigTest1;
2
3 public class CDPlayer implements MediaPlayer {
4 private CompactDisc cd;
5
6 public CDPlayer(CompactDisc cd) {
7 this.cd = cd;
8 }
9
10 public void play() {
11 cd.play();
12 }
13
14 }
ConstructorArgReferenceTest-context.xml配置文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <!-- 声明SgtPeppers bean,并且SgtPeppers类实现了CompacDisc接口,所以实际上已经有了一个可以注入到CDPlayer bean中的bean -->
8 <bean id="compactDisc" class="xmlConfigTest1.SgtPeppers" />
9
10 <!-- 1.当Spring遇到这个标签时,会创建一个CDPlayer实例。 -->
11 <!-- 2.<constructor-arg>元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。 -->
12 <bean id="cdPlayer" class="xmlConfigTest1.CDPlayer">
13 <constructor-arg ref="compactDisc" />
14 </bean>
15
16 </beans>
ConstructorArgReferenceTest类(这里有一个疑问:测试类中没有指明xml配置文件的路径,那么是不是默认读取对应的“类名-context.xml”配置文件呢?通过通知台可以发现,是的!):
1 package xmlConfigTest1;
2
3 import static org.junit.Assert.*;
4 import org.junit.Rule;
5 import org.junit.Test;
6 import org.junit.contrib.java.lang.system.SystemOutRule;
7 import org.junit.runner.RunWith;
8 import org.springframework.beans.factory.annotation.Autowired;
9 import org.springframework.test.context.ContextConfiguration;
10 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11
12 @RunWith(SpringJUnit4ClassRunner.class)
13 @ContextConfiguration
14 public class ConstructorArgReferenceTest {
15
16 @Rule
17 public final SystemOutRule log = new SystemOutRule().enableLog();
18
19 @Autowired
20 private MediaPlayer player;
21
22 @Test
23 public void play() {
24 player.play();
25 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
26 }
27
28 }
2.使用Spring的c-命名空间实现依赖注入。
代码结构为(其中4个基础类不变):
CNamespaceReferenceTest-context.xml配置文件类:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <bean id="compactDisc" class="xmlConfigTest2.SgtPeppers" />
9
10 <!-- 1.使用c-命名空间来声明构造器参数 -->
11 <!-- 2.使用c-命名空间属性要比使用<constructor-arg>元素简练得多-->
12 <!-- 3.这里要注意的是“c:cd-ref”中cd是CDPlayer类的构造器中指明的CompactDisc类型的字段。 -->
13 <!-- 4.可以将参数名称替换为索引,即“c:_0-ref”表示的是第一个构造器参数 -->
14 <!-- 5.在只有一个构造器参数的情况下,根本不用去标示参数。 -->
15 <bean id="cdPlayer" class="xmlConfigTest2.CDPlayer" c:cd-ref="compactDisc" />
16
17 </beans>
CNamespaceReferenceTest类(也没有写xml文件路径,但是可以自动发现):
1 package xmlConfigTest2;
2
3 import static org.junit.Assert.*;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13
14 @RunWith(SpringJUnit4ClassRunner.class)
15 @ContextConfiguration
16 public class CNamespaceReferenceTest {
17
18 @Rule
19 public final SystemOutRule log = new SystemOutRule().enableLog();
20
21 @Autowired
22 private MediaPlayer player;
23
24 @Test
25 public void play() {
26 player.play();
27 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
28 }
29
30 }
目前所做的DI通常指的都是类型的装配-也就是将对象的引用装配到依赖于它们的其他对象之中--而有时候,需要做的只是用一个字面量值来配置对象。
因此,需要增加一个实现了CompactDisc接口的新的唱片类,即BlankDisc类,这个类像空磁盘一样,可以设置成任意想要的艺术家和唱片名:
1 public class BlankDisc implements CompactDisc {
2
3 private String title;
4 private String artist;
5
6 public BlankDisc(String title, String artist) {
7 this.title = title;
8 this.artist = artist;
9 }
10
11 public void play() {
12 System.out.println("Playing " + title + " by " + artist);
13 }
14
15 }
title和artist这两个属性,即将给定的值以字面量的形式注入到构造器之中。
3.使用<constructor-arg>元素进行构造器参数的注入
代码结构为:
ConstructorArgValueTest-context.xml配置xml文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <!-- 使用value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。-->
8 <bean id="compactDisc" class="xmlConfigTest3.BlankDisc">
9 <constructor-arg value="You Don't Love Me, LaDao" />
10 <constructor-arg value="Jay Chou" />
11 </bean>
12
13 <bean id="cdPlayer" class="xmlConfigTest3.CDPlayer">
14 <constructor-arg ref="compactDisc" />
15 </bean>
16
17 </beans>
ConstructorArgValueTest:
1 package xmlConfigTest3;
2
3 import static org.junit.Assert.*;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration
15 public class ConstructorArgValueTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing You Don't Love Me, LaDao by Jay Chou\r\n", log.getLog());
27 }
28
29 }
4.使用c-命名空间进行构造器参数的注入
代码结构为:
配置xml文件CNamespaceValueTest-context.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <bean id="compactDisc" class="xmlConfigTest4.BlankDisc"
9 c:_0="You Don't Love Me, LaDao"
10 c:_1="Jay Chou" />
11
12 <bean id="cdPlayer" class="xmlConfigTest4.CDPlayer" c:_-ref="compactDisc" />
13
14 </beans>
配置xml文件CNamespaceValueTest-context.xml中还可以换一种方案来写:
1 <bean id="compactDisc" class="xmlConfigTest4.BlankDisc"
2 c:_title="You Don't Love Me, LaDao"
3 c:_artist="Jay Chou" />
在装配bean引用和字面量值方面,<constructor-arg>元素和c-命名空间的功能是相同的。但是,有一种情况是<constructor-arg>元素能够实现,而c-命名空间却无法做到的,那就是将结合装配到构造器参数中。
5.使用<constructor-arg>将集合装配到构造器参数中
修改BlankDisc类为ListDisc类,增加CD中包含的所有歌曲列表,播放的时候,将每首歌都播放出来:
1 package xmlConfigTest5;
2
3 import java.util.List;
4 public class ListDisc implements CompactDisc {
5
6 private String title;
7 private String artist;
8 private List<String> tracks;
9
10 public ListDisc(String title, String artist, List<String> tracks) {
11 this.title = title;
12 this.artist = artist;
13 this.tracks = tracks;
14 }
15
16 public void play() {
17 System.out.println("Playing " + title + " by " + artist);
18 for (String track : tracks) {
19 System.out.println("-Track: " + track);
20 }
21 }
22
23 }
代码结构为:
配置xml文件ConstructorArgCollectionTest-context.xml(使用<list>元素表明一个包含值的列表将会传递到构造器中,<value>用来指定列表中的每个元素。也可以按照同样的方式使用<set>元素,但是要把ListDisc中引用import java.util.Set,使用Set的时候,所有重复的值都会被忽略掉,存放顺序也不会得以保证。):
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="compactDisc" class="xmlConfigTest5.ListDisc">
8 <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
9 <constructor-arg value="The Beatles" />
10 <constructor-arg>
11 <list>
12 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
13 <value>With a Little Help from My Friends</value>
14 <value>Lucy in the Sky with Diamonds</value>
15 <value>Getting Better</value>
16 <value>Fixing a Hole</value>
17 <value>She's Leaving Home</value>
18 <value>Being for the Benefit of Mr. Kite!</value>
19 <value>Within You Without You</value>
20 <value>When I'm Sixty-Four</value>
21 <value>Lovely Rita</value>
22 <value>Good Morning Good Morning</value>
23 <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
24 <value>A Day in the Life</value>
25 </list>
26 </constructor-arg>
27 </bean>
28
29 <bean id="cdPlayer" class="xmlConfigTest5.CDPlayer">
30 <constructor-arg ref="compactDisc" />
31 </bean>
32
33 </beans>
ConstructorArgCollectionTest:
1 package xmlConfigTest5;
2
3 import static org.junit.Assert.*;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration
15 public class ConstructorArgCollectionTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals(
27 "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n" +
28 "-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n" +
29 "-Track: With a Little Help from My Friends\r\n" +
30 "-Track: Lucy in the Sky with Diamonds\r\n" +
31 "-Track: Getting Better\r\n" +
32 "-Track: Fixing a Hole\r\n" +
33 "-Track: She's Leaving Home\r\n" +
34 "-Track: Being for the Benefit of Mr. Kite!\r\n" +
35 "-Track: Within You Without You\r\n" +
36 "-Track: When I'm Sixty-Four\r\n" +
37 "-Track: Lovely Rita\r\n" +
38 "-Track: Good Morning Good Morning\r\n" +
39 "-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise)\r\n" +
40 "-Track: A Day in the Life\r\n",
41 log.getLog());
42 }
43
44 }
到目前为止,CDPlayer和BlankDisc(ListDisc)类完全是通过构造器注入的,没有使用属性的Setter方法。继续研究如何使用Spring XML实现属性注入。
首先有一个问题,那就是该选择构造器还是属性注入呢?通用的规则是,对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
CompactDisc属性值增加Setter类并去掉CDPlayer类的构造器,现在CDPlayer没有任何的构造器(除了隐含的默认构造器),同时也没有任何的强依赖:
1 package xmlConfigTest6;
2
3 public class CDPlayer implements MediaPlayer {
4 private CompactDisc compactDisc;
5
6
7 public void setCompactDisc(CompactDisc compactDisc) {
8 this.compactDisc = compactDisc;
9 }
10
11 public void play() {
12 compactDisc.play();
13 }
14
15 }
修改BlankDisc类成reallyBlankDisc类,reallyBlankDisc完全通过属性注入进行配置,而不是构造器注入:
1 package xmlConfigTest6;
2
3 import java.util.List;
4
5 public class reallyBlankDisc implements CompactDisc {
6
7 private String title;
8 private String artist;
9 private List<String> tracks;
10
11 public void setTitle(String title) {
12 this.title = title;
13 }
14
15 public void setArtist(String artist) {
16 this.artist = artist;
17 }
18
19 public void setTracks(List<String> tracks) {
20 this.tracks = tracks;
21 }
22
23 public void play() {
24 System.out.println("Playing " + title + " by " + artist);
25 for (String track : tracks) {
26 System.out.println("-Track: " + track);
27 }
28 }
29
30 }
6.使用<property>元素装配bean引用与装配字面量(唯一的区别是是否带有“-ref”后缀,如果没有“-ref”后缀的话,所装配的就是字面量)
代码结构为:
PropertyRefTest-context.xml配置xml文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:p="http://www.springframework.org/schema/p"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <!-- 通过 <property> 元素的value属性来设置title、artist和tracks属性-->
9 <bean id="compactDisc" class="xmlConfigTest6.reallyBlankDisc">
10 <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
11 <property name="artist" value="The Beatles" />
12 <property name="tracks">
13 <list>
14 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
15 <value>With a Little Help from My Friends</value>
16 <value>Lucy in the Sky with Diamonds</value>
17 <value>Getting Better</value>
18 <value>Fixing a Hole</value>
19 <value>She's Leaving Home</value>
20 <value>Being for the Benefit of Mr. Kite!</value>
21 <value>Within You Without You</value>
22 <value>When I'm Sixty-Four</value>
23 <value>Lovely Rita</value>
24 <value>Good Morning Good Morning</value>
25 <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
26 <value>A Day in the Life</value>
27 </list>
28 </property>
29 </bean>
30
31 <!-- 1.<property>元素为属性的Setter方法所提供的功能与<contructor-arg>元素为构造器所提供的功能是一样的 -->
32 <!-- 2.通过ref属性引用了ID为compactDisc的bean,并将其通过setCompactDisc()方法注入到compactDisc属性中 -->
33 <bean id="cdPlayer" class="xmlConfigTest6.CDPlayer">
34 <property name="compactDisc" ref="compactDisc" />
35 </bean>
36
37
38 </beans>
PropertyRefAndValueTest(和之前的测试类并没有什么变化):
1 package xmlConfigTest6;
2
3 import static org.junit.Assert.*;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration
15 public class PropertyRefAndValueTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals(
27 "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n" +
28 "-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n" +
29 "-Track: With a Little Help from My Friends\r\n" +
30 "-Track: Lucy in the Sky with Diamonds\r\n" +
31 "-Track: Getting Better\r\n" +
32 "-Track: Fixing a Hole\r\n" +
33 "-Track: She's Leaving Home\r\n" +
34 "-Track: Being for the Benefit of Mr. Kite!\r\n" +
35 "-Track: Within You Without You\r\n" +
36 "-Track: When I'm Sixty-Four\r\n" +
37 "-Track: Lovely Rita\r\n" +
38 "-Track: Good Morning Good Morning\r\n" +
39 "-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise)\r\n" +
40 "-Track: A Day in the Life\r\n",
41 log.getLog());
42 }
43
44
45 }
7.使用p-命名空间装配bean引用与装配字面量
代码结构为:
配置xml文件PNamespaceRefAndValueTest-context.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:p="http://www.springframework.org/schema/p"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <!-- 使用p-命令空间来设置属性值,与c-命名空间一样, 不能使用p-命名空间来装配集合 -->
9 <bean id="compactDisc" class="xmlConfigTest7.reallyBlankDisc"
10 p:title="Sgt. Pepper's Lonely Hearts Club Band"
11 p:artist="The Beatles">
12 <property name="tracks">
13 <list>
14 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
15 <value>With a Little Help from My Friends</value>
16 <value>Lucy in the Sky with Diamonds</value>
17 <value>Getting Better</value>
18 <value>Fixing a Hole</value>
19 <value>She's Leaving Home</value>
20 <value>Being for the Benefit of Mr. Kite!</value>
21 <value>Within You Without You</value>
22 <value>When I'm Sixty-Four</value>
23 <value>Lovely Rita</value>
24 <value>Good Morning Good Morning</value>
25 <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
26 <value>A Day in the Life</value>
27 </list>
28 </property>
29 </bean>
30
31 <!-- 1.使用p-命名空间装配compactDisc属性-->
32 <!-- 2.通常的格式是p:属性名-ref="所注入bean的ID"-->
33 <bean id="cdPlayer" class="xmlConfigTest7.CDPlayer"
34 p:compactDisc-ref="compactDisc" />
35
36 </beans>
8.虽然不能使用p-命名空间来装配集合,但是可以使用Spring util-命名空间来简化reallyBlankDisc bean
代码结构为:
配置xml文件PNamespaceWithUtilNamespaceTest-context.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
4 xmlns:util="http://www.springframework.org/schema/util"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd
7 http://www.springframework.org/schema/util
8 http://www.springframework.org/schema/util/spring-util.xsd">
9
10 <!-- 3.这样就能向使用其他的bean那样,将磁道列表bean注入到reallyBlankDisc bean中的tracks属性中 -->
11 <bean id="compactDisc" class="xmlConfigTest8.reallyBlankDisc"
12 p:title="Sgt. Pepper's Lonely Hearts Club Band"
13 p:artist="The Beatles"
14 p:tracks-ref="trackList" />
15
16 <!-- 1.util-命名空间的<util:list>元素,会创建一个列表的bean -->
17 <!-- 2.借助<util:list>元素,可以将磁道列表转移到reallyBlankDisc bean之外,并将其声明到单独的bean之中 -->
18 <util:list id="trackList">
19 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
20 <value>With a Little Help from My Friends</value>
21 <value>Lucy in the Sky with Diamonds</value>
22 <value>Getting Better</value>
23 <value>Fixing a Hole</value>
24 <value>She's Leaving Home</value>
25 <value>Being for the Benefit of Mr. Kite!</value>
26 <value>Within You Without You</value>
27 <value>When I'm Sixty-Four</value>
28 <value>Lovely Rita</value>
29 <value>Good Morning Good Morning</value>
30 <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
31 <value>A Day in the Life</value>
32 </util:list>
33
34 <bean id="cdPlayer" class="xmlConfigTest8.CDPlayer"
35 p:compactDisc-ref="compactDisc" />
36
37 </beans>
五、导入和混合配置
混合配置的原理就是,Spring在自动装配时,并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
没有任何变化的几个类:
CompactDisc接口:
1 package mixedConfig1;
2
3 public interface CompactDisc {
4
5 void play();
6
7 }
MediaPlayer接口:
1 package mixedConfig1;
2
3 public interface MediaPlayer {
4
5 void play();
6
7 }
MediaPlayer接口的CDPlayer类:
1 package mixedConfig1;
2
3 public class CDPlayer implements MediaPlayer {
4 private CompactDisc cd;
5
6 public CDPlayer(CompactDisc cd) {
7 this.cd = cd;
8 }
9
10 public void play() {
11 cd.play();
12 }
13
14 }
CompactDisc接口的第一个唱片类SgtPeppers 类:
1 package mixedConfig1;
2
3 public class SgtPeppers implements CompactDisc {
4
5 private String title = "Sgt. Pepper's Lonely Hearts Club Band";
6 private String artist = "The Beatles";
7
8 public void play() {
9 System.out.println("Playing " + title + " by " + artist);
10 }
11
12 }
CompactDisc接口,使用构造器来进行属性注入,并且拥有歌曲磁道列表的第二个唱片类ListBlankDisc类:
1 package mixedConfig2;
2
3 import java.util.List;
4
5 public class ListBlankDisc implements CompactDisc {
6
7 private String title;
8 private String artist;
9 private List<String> tracks;
10
11 public ListBlankDisc(String title, String artist, List<String> tracks) {
12 this.title = title;
13 this.artist = artist;
14 this.tracks = tracks;
15 }
16
17 public void play() {
18 System.out.println("Playing " + title + " by " + artist);
19 for (String track : tracks) {
20 System.out.println("-Track: " + track);
21 }
22 }
23
24 }
接着,梳理一下混合配置常见的几种情况:
1.使用@Import注解,将其中一个JavaConfig导入到另一个JavaConfig当中的第一种方法
代码结构为:
SgtPeppers
1 package mixedConfig1;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDConfig {
8 @Bean
9 public CompactDisc compactDisc() {
10 return new SgtPeppers();
11 }
12 }
CompactDisc()方法,因此需要有一种方法将这两个类组合到一起,因此就在CDPlayerConfig类中使用@Import(CDConfig.class)来导入CDConfig配置类:
1 package mixedConfig1;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5 import org.springframework.context.annotation.Import;
6
7 @Configuration
8 @Import(CDConfig.class)
9 public class CDPlayerConfig {
10
11 @Bean
12 public CDPlayer cdPlayer(CompactDisc compactDisc) {
13 return new CDPlayer(compactDisc);
14 }
15
16 }
JavaImportJavaConfigTest 中,需要通过@ContextConfiguration(classes = CDPlayerConfig.class)来指明被导入CDConfig的CDPlayerConfig配置类:
1 package mixedConfig1;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration(classes = CDPlayerConfig.class)
15 public class JavaImportJavaConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
27 }
28
29 }
2.不在其中一个配置类当中使用@Import注解,创建一个更高级别的配置类SoundSystemConfig类,在这个类中使用@Import将两个配置类组合在一起
代码结构为:
CDConfig配置类:
1 package mixedConfig2;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDConfig {
8
9 @Bean
10 public CompactDisc compactDisc() {
11 return new SgtPeppers();
12 }
13 }
CDPlayerConfig配置类:
1 package mixedConfig2;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDPlayerConfig {
8
9 @Bean
10 public CDPlayer cdPlayer(CompactDisc compactDisc) {
11 return new CDPlayer(compactDisc);
12 }
13
14 }
SoundSystemConfig类:
1 package mixedConfig2;
2
3 import org.springframework.context.annotation.Configuration;
4 import org.springframework.context.annotation.Import;
5
6 @Configuration
7 @Import({CDPlayerConfig.class, CDConfig.class})
8 public class SoundSystemConfig {
9
10 }
JavaImportJavaConfigTest类,通过@ContextConfiguration(classes = SoundSystemConfig.class)将读取最高级别的配置类SoundSystemConfig类:
1 package mixedConfig2;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration(classes = SoundSystemConfig.class)
15 public class JavaImportJavaConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
27 }
28
29 }
3.使用@ImportResource注解,将配置在XML中的ListBlankDisc bean注入到配置在JavaConfig中的CDPlayer bean中
代码结构为:
CDPlayerConfig类:
1 package mixedConfig3;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDPlayerConfig {
8
9 @Bean
10 public CDPlayer cdPlayer(CompactDisc compactDisc) {
11 return new CDPlayer(compactDisc);
12 }
13
14 }
配置xml文件cd-config.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="compactDisc" class="mixedConfig3.ListBlankDisc"
8 c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles">
9 <constructor-arg>
10 <list>
11 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
12 <value>With a Little Help from My Friends</value>
13 <value>Lucy in the Sky with Diamonds</value>
14 <value>Getting Better</value>
15 <value>Fixing a Hole</value>
16 </list>
17 </constructor-arg>
18 </bean>
19
20 </beans>
SoundSystemConfig类:
1 package mixedConfig3;
2
3 import org.springframework.context.annotation.Configuration;
4 import org.springframework.context.annotation.Import;
5 import org.springframework.context.annotation.ImportResource;
6
7 @Configuration
8 @Import(CDPlayerConfig.class)
9 @ImportResource("classpath:/mixedConfig3/cd-config.xml")
10 public class SoundSystemConfig {
11
12 }
JavaImportXmlConfigTest类,指明SoundSystemConfig为读取的配置类(这里遇到了一个问题,就是如果直接copy xml文档不注意去掉空格的话,可能会报错,所以要先对xml文件format一下):
1 package mixedConfig3;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration(classes = SoundSystemConfig.class)
15 public class JavaImportXmlConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n"
27 + "-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n"
28 + "-Track: With a Little Help from My Friends\r\n"
29 + "-Track: Lucy in the Sky with Diamonds\r\n" + "-Track: Getting Better\r\n"
30 + "-Track: Fixing a Hole\r\n", log.getLog());
31 }
32
33 }
4.使用<import>元素在XML配置文件中进行配置拆分,在其中一个XML中引用另一个XML
代码结构为:
配置xml文件cd-config.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: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="compactDisc" class="mixedConfig4.SgtPeppers" />
</beans>
import>元素导入其中一个配置文件到配置xml文件cdplayer-config.xml中:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <import resource="cd-config.xml" />
9 <bean id="cdPlayer" class="mixedConfig4.CDPlayer" c:cd-ref="compactDisc" />
10
11 </beans>
XMLImportXMLConfigTest类,给定最高级别的配置文件路径“/mixedConfig4/cdplayer-config.xml”
1 package mixedConfig4;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration("classpath:/mixedConfig4/cdplayer-config.xml")
15 public class XMLImportXMLConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
27 }
28
29 }
5.<bean>元素能够用来将JavaConfig配置导入到XML配置中
代码结构为:
CDConfig类:
1 package mixedConfig5;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDConfig {
8
9 @Bean
10 public CompactDisc compactDisc() {
11 return new SgtPeppers();
12 }
13 }
使用<bean>元素将CDConfig配置类导入到XML中:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5
6 <bean class="mixedConfig5.CDConfig" />
7
8 <bean id="cdPlayer" class="mixedConfig5.CDPlayer" c:cd-ref="compactDisc" />
9
10 </beans>
XMLImportJavaConfigTest类,指明xml配置文件路径为/mixedConfig5/cdplayer-config.xml:
1 package mixedConfig5;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration("classpath:/mixedConfig5/cdplayer-config.xml")
15 public class XMLImportJavaConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
27 }
28
29 }
6.比较合理的做法是,创建一个更高层次的配置文件,这个文件不包含任何的bean,只是负责将两个或者更多的配置组合起来
代码结构为:
CDConfig类:
1 package mixedConfig6;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class CDConfig {
8
9 @Bean
10 public CompactDisc compactDisc() {
11 return new SgtPeppers();
12 }
13 }
配置xml文件cdplayer-config.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd">
7
8 <bean id="cdPlayer" class="mixedConfig6.CDPlayer" c:cd-ref="compactDisc" />
9
10 </beans>
最高级别配置文件SoundSystemConfig.xml:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5
6 <bean class="mixedConfig6.CDConfig" />
7
8 <import resource="cdplayer-config.xml" />
9
10 </beans>
SoundSystemConfigTest类:
1 package mixedConfig6;
2
3 import static org.junit.Assert.assertEquals;
4
5 import org.junit.Rule;
6 import org.junit.Test;
7 import org.junit.contrib.java.lang.system.SystemOutRule;
8 import org.junit.runner.RunWith;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration("classpath:/mixedConfig6/SoundSystemConfig.xml")
15 public class SoundSystemConfigTest {
16
17 @Rule
18 public final SystemOutRule log = new SystemOutRule().enableLog();
19
20 @Autowired
21 private MediaPlayer player;
22
23 @Test
24 public void play() {
25 player.play();
26 assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
27 }
28
29 }
六、其他问题
1.自动装配--@Autowired
接口类:
package com.ssm.chapter10.annotation.service;
public interface RoleService2 {
public void printRoleInfo();
}
实现类:
package com.ssm.chapter10.annotation.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService2;
@Component("RoleService2")
public class RoleServiceImpl2 implements RoleService2 {
@Autowired
private Role role = null;
public Role getRole() {
return role;
}
// @Autowired
public void setRole(Role role) {
this.role = role;
}
@Override
public void printRoleInfo() {
System.out.println("id =" + role.getId());
System.out.println("roleName =" + role.getRoleName());
System.out.println("note =" + role.getNote());
}
}
(1)第一种方式:在字段上注入。
这里的@Autowired表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。
@Autowired
private Role role = null;
(2)@Autowired除了可以配置在属性之外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入。
@Autowired
public void setRole(Role role) {
this.role = role;
}
2.自动装配的歧义性(@Primary和@Qualifier)
自动装配在有些时候并不能使用,原因在于按类型的注入方式。按照Spring的建议,在大部分情况下会使用接口编程,但是定义一个接口,并不一定只有一个与之对应的实现类。也就是说,一个接口可以有多个实现类,例如:
有一个接口:RoleService
package com.ssm.chapter10.annotation.service;
import com.ssm.chapter10.annotation.pojo.Role;
public interface RoleService {
public void printRoleInfo(Role role);
}
接口
和两个实现类:RoleServiceImpl和RoleServiceImpl3
package com.ssm.chapter10.annotation.service.impl;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;
@Component
public class RoleServiceImpl implements RoleService {
@Override
public void printRoleInfo(Role role) {
System.out.println("id =" + role.getId());
System.out.println("roleName =" + role.getRoleName());
System.out.println("note =" + role.getNote());
}
}
实现类1
package com.ssm.chapter10.annotation.service.impl;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;
@Component("roleService3")
public class RoleServiceImpl3 implements RoleService {
@Override
public void printRoleInfo(Role role) {
System.out.print("{id =" + role.getId());
System.out.print(", roleName =" + role.getRoleName());
System.out.println(", note =" + role.getNote() + "}");
}
}
实现类2
有一个RoleController类,它有一个字段是RoleService接口类型,由于RoleService有两个实现类,因此Spring IoC容器无法判断要把哪个对象注入进来,于是就会抛出异常,这样@Autowired就会注入失败。产生这样的状况是因为它采用的是按类型来注入对象,而在Java中接口可以有多个实现类,同样的抽象类也可以有多个实例化的类,这样就会造成通过类型(by type)获取Bean的不唯一,从而导致Spirng IoC容器类似于按类型的方法无法获得唯一的实例化类。
package com.ssm.chapter10.annotation.controller;
@Component
public class RoleController {
@Autowired
private RoleService roleService = null;
public void printRole(Role role) {
roleService.printRoleInfo(role);
}
}
(1)使用@Primary解决
注解@Primary代表首要的,当Spring IoC通过一个接口或者抽象类注入对象时,@Primary注解的Bean会被优先注入。
package com.ssm.chapter10.annotation.service.impl;
@Component("roleService3")
@Primary
public class RoleServiceImpl3 implements RoleService {
@Override
public void printRoleInfo(Role role) {
System.out.print("{id =" + role.getId());
System.out.print(", roleName =" + role.getRoleName());
System.out.println(", note =" + role.getNote() + "}");
}
}
(2)使用@Qualifier解决
除了按类型查找Bean,Spring IoC容器的最底层接口BeanFactory也定义了按名称查找的方法,如果采用名称查找而不是按类型查找的方法,就可以消除歧义性了。
@Component("roleService3")
@Qualifier("roleService3")来注入这个指定的类了。
package com.ssm.chapter10.annotation.controller;
@Component
public class RoleController {
@Autowired
@Qualifier("roleService3")
private RoleService roleService = null;
public void printRole(Role role) {
roleService.printRoleInfo(role);
}
}
3.装载带有参数的构造方法类
对于一些带有参数的构造方法,也允许我们通过注解进行注入。
例如,可以在构造方法中使用@Autowired和@Qualifier注解对参数进行注入。
package com.ssm.chapter10.annotation.controller;
@Component
public class RoleController2 {
private RoleService roleService = null;
public RoleController2(@Autowired @Qualifier("roleService3") RoleService roleService) {
this.roleService = roleService;
}
public RoleService getRoleService() {
return roleService;
}
public void setRoleService( RoleService roleService) {
this.roleService = roleService;
}
public void printRole(Role role) {
roleService.printRoleInfo(role);
}
}
4.使用Profile
为了在不同的环境下装载不同的Bean,Spring提供了Profile进行支持。
应用场景:开发人员使用开发数据库,而测试人员使用测试数据库。
(1)定义Profile有两种方式,使用Java代码中的@Profile注解或者是XML中的profile元素
使用@Profile注解:
package com.spring.profile
@Component
public class ProdileDataSOurce {
@Bean(name="devDataSource")
@Profile("dev")
public DataSource getDevDataSource(){
...
return dataSource;
}
@Bean(name="testDataSource")
@Profile("test")
public DataSource getDevDataSource(){
...
return dataSource;
}
}
使用XML中的profile元素:
<beans profile = "test">
<bean .../>
</beans>
<beans profile = "dev">
<bean .../>
</beans>
(2)激活Profile的方式
- 在使用SpringMVC的情况下可以配置Web上下文参数,或者DispatchServlet参数
- 作为JNDI条目
- 配置环境变量
- 配置JVM启动参数
- 在集成测试环境中使用@ActiveProfiles
例如:
package com.spring.test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ProfileConfig.class)
@ActiveProfiles("dev")
public class ProfileTest {
@Autowired
private DataSource dataSource;
@Test
public void test() {
System.out.println(dataSource.getClass().getName());
}
}
5.加载属性(properties)文件
使用属性文件可以有效地减少硬编码,很多时候修改环境只需要修改配置文件就可以了,这样能够有效地提高运维人员的操作便利性。
给定属性文件database-config.properties
jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/chapter10
jdbc.database.username=root
jdbc.database.password=123456
(1)使用注解方式加载属性文件
Spring提供了@PropertySource来加载属性文件,有一些配置项:
- name:字符串,配置这次属性配置的名称
- value:字符串数组,可以配置多个属性文件
- ignoreResourcesNotFound:boolean值,默认为false,表示如果找不到对应的属性文件是否进行忽略处理,false表示如果找不到就抛出异常
- encoding:编码,默认为“”
定义Java配置类:ApplicationConfig.java
@Configuration
@PropertySource(value={"classpath:database-config.properties"}, ignoreResourceNotFound=true)
public class ApplicationConfig {
}
在Spirng中使用属性文件中的内容:
private static void test9() {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
String url = context.getEnvironment().getProperty("jdbc.database.url");
System.out.println(url);
}
如果仅仅是这样,在Spring中没有解析属性占位符的能力,Spring推荐使用一个属性文件解析类进行处理,它就是PropertySourcesPlaceholderConfigurer,使用它就意味着允许Spring解析对应的属性文件,通过占位符去引用对应的配置。
PropertySourcesPlaceholderConfigurer类的Bean,作用是为了让Spring能够解析属性占位符。
@Configuration
@ComponentScan
@PropertySource(value={"classpath:database-config.properties"}, ignoreResourceNotFound=true)
public class ApplicationConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
通过占位符引用加载进来的属性:
package com.ssm.chapter10.annotation.config;
@Component
public class DataSourceBean {
@Value("${jdbc.database.driver}")
private String driver = null;
@Value("${jdbc.database.url}")
private String url = null;
@Value("${jdbc.database.username}")
private String username = null;
@Value("${jdbc.database.password}")
private String password = null;
/**getter and setter**/
@Bean(name = "dataSource1")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
(2)使用XML方式加载属性文件
通过<context:property-placeholder>元素也可以加载一个属性文件或者是多个属性文件。
<context:component-scan base-package="com.ssm.chapter10.annotation" />
<!--
<context:property-placeholder
ignore-resource-not-found="false" location="classpath:database-config.properties" />
-->
<!--字符串数组,可配置多个属性文件 -->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<array>
<value>classpath:database-config.properties</value>
<value>classpath:log4j.properties</value>
</array>
</property>
<property name="ignoreResourceNotFound" value="false" />
</bean>
</beans>
6.条件化装配Bean
在某些条件下不需要去装配Bean,比如当属性文件中没有属性配置时,就不要去创建数据源,这时候,需要通过条件化去判断。
Spring提供了注解@Conditional可以配置一个或多个类
首先定义一个实现了Condition接口的类,需要实现matches方法,首先获取运行上下文环境,然后判断在环境中属性文件是否配置了数据库的相关参数,如果参数全部配置了就返回true。
package com.ssm.chapter10.annotation.condition;
public class DataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取上下文环境
Environment env = context.getEnvironment();
//判断是否存在关于数据源的基础配置
return env.containsProperty("jdbc.database.driver")
&& env.containsProperty("jdbc.database.url")
&& env.containsProperty("jdbc.database.username")
&& env.containsProperty("jdbc.database.password");
}
}
@Conditional({DataSourceCondition.class})去配置,如果所有参数在配置文件中都已经配置了,则返回为true,那么Spring会去创建对应的Bean,否则是不会创建的。
@Bean(name = "dataSource")
@Conditional({DataSourceCondition.class})
public DataSource getDataSource(
@Value("${jdbc.database.driver}") String driver,
@Value("${jdbc.database.url}") String url,
@Value("${jdbc.database.username}") String username,
@Value("${jdbc.database.password}") String password) {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
7.Bean的作用域
在默认的情况下,Spring IoC容器只会对一个Bean创建一个实例,而不是多个。
Spring提供了4种作用域,它会根据情况来决定是否生成新的对象:
- 单例(singleton):默认的选项,在整个应用中,Spring只为其生成一个Bean的实例。
- 原型(prototype):当每次注入,或者通过Spring IoC容器获取Bean时,Spring都会为它创建一个新的实例。
- 会话(session):在Web应用中使用,就是在会话过程中Spring只创建一个实例
- 请求(request):在Web应用中使用,就是在一次请求中Spring会创建一个实例,但是不同的请求会创建不同的实例。
可以通过@Scope声明作用域为原型,这样两次分别从Spirng IoC容器中就会获得不同的对象,
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleDataSourceServiceImpl implements RoleDataSourceService {
...
}