控制反转的概念:控制反转是一种通过描述(在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 bean,同时本身也是一个组件类的CDPlayer类:
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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 " + 28 "-Track: Sgt. Pepper's Lonely Hearts Club Band " + 29 "-Track: With a Little Help from My Friends " + 30 "-Track: Lucy in the Sky with Diamonds " + 31 "-Track: Getting Better " + 32 "-Track: Fixing a Hole " + 33 "-Track: She's Leaving Home " + 34 "-Track: Being for the Benefit of Mr. Kite! " + 35 "-Track: Within You Without You " + 36 "-Track: When I'm Sixty-Four " + 37 "-Track: Lovely Rita " + 38 "-Track: Good Morning Good Morning " + 39 "-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise) " + 40 "-Track: A Day in the Life ", 41 log.getLog()); 42 } 43 44 }
到目前为止,CDPlayer和BlankDisc(ListDisc)类完全是通过构造器注入的,没有使用属性的Setter方法。继续研究如何使用Spring XML实现属性注入。
首先有一个问题,那就是该选择构造器还是属性注入呢?通用的规则是,对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
修改CDPlayer类,为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 " + 28 "-Track: Sgt. Pepper's Lonely Hearts Club Band " + 29 "-Track: With a Little Help from My Friends " + 30 "-Track: Lucy in the Sky with Diamonds " + 31 "-Track: Getting Better " + 32 "-Track: Fixing a Hole " + 33 "-Track: She's Leaving Home " + 34 "-Track: Being for the Benefit of Mr. Kite! " + 35 "-Track: Within You Without You " + 36 "-Track: When I'm Sixty-Four " + 37 "-Track: Lovely Rita " + 38 "-Track: Good Morning Good Morning " + 39 "-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise) " + 40 "-Track: A Day in the Life ", 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当中的第一种方法
代码结构为:
在之前的CDPlayConfig配置类中已经定义了两个bean,就姑且认为它很复杂,所以将其中的SgtPeppers bean分开独立到它自己的配置类CDConfig类中:
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 ", 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 }
没有加@Import注解的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 ", 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 " 27 + "-Track: Sgt. Pepper's Lonely Hearts Club Band " 28 + "-Track: With a Little Help from My Friends " 29 + "-Track: Lucy in the Sky with Diamonds " + "-Track: Getting Better " 30 + "-Track: Fixing a Hole ", 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 ", log.getLog()); 27 } 28 29 }
5.<bean>元素能够用来将JavaConfig配置导入到XML配置中
代码结构为:
使用JavaConfig配置的配置类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 ", 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 ", 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()); } }
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() + "}"); } }
有一个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也定义了按名称查找的方法,如果采用名称查找而不是按类型查找的方法,就可以消除歧义性了。
首先把RoleServiceImpl3定义成别名@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 { ... }
代码已上传至GitHub:https://github.com/BigJunOba/SpringDI