Spring(二)装配Spring Bean
控制反转的概念:控制反转是一种通过描述(在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接口:
package autoConfig1;
/**
* 如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实没有太大用处的。
* 所以可以这样说,CD播放器依赖于CD才能完成它的使命。
* CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。
* 它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
*/
public interface CompactDisc { void play();
}
MediaPlayer接口:
package autoConfig1;
/**
* MediaPlayer接口作为CD播放器的接口。
*/
public interface MediaPlayer { void play(); }
CDPlayerConfig类用于开启Spring的组件扫描:
package autoConfig1;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration用于定义配置类,可替换XML文件。
* @ComponentScan注解能够在Spring中启用组件扫描:
* 1.如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。
* 2.因此Spring将会扫描autoConfig1包以及这个包下的所有子包,寻找带有@Component注解的类。
* 3.由于SgtPeppers类带有@Component注解,所以被发现了,并且会在Spring中自动为其创建一个bean。
*/
@Configuration
@ComponentScan
// 类CDPlayerConfig通过Java代码定义了Spring的装配规则,并没有显式地声明任何bean。
public class CDPlayerConfig { }
实现了CompactDisc接口的组件类SgtPeppers类:
package autoConfig1;
import org.springframework.stereotype.Component;
/**
* 《Sgt. Pepper's Lonely Hearts Club Band》 是英国摇滚乐队The Beatles发行的第8张录音室专辑。
* 在SgtPeppers类上使用了@Component注解。
* 组件扫描默认是不启用的,还需要命令Spring去寻找带有@Component注解的类,并为其创建bean。
*/
@Component // 这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
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);
} }
实现了MediaPlayer接口,并且自动装配CompactDisc bean,同时本身也是一个组件类的CDPlayer类:
package autoConfig1; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* 声明CDPlayer类作为组件类,并且添加注解实现自动装配。
* 自动装配就是让Spring自动满足bean依赖的一种方法。
* 在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。
* 如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。
* 为了避免异常的出现,可以使用@Autowired(required=false),让没有匹配的bean处于为匹配状态。
* 但是,这种情况如果没有进行null检查的话,这个处于为装配状态的属性有可能会出现空指针异常。
*/
@Component // 这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
// 1.在CDPlayer类的构造器上添加@Autowired注解。
// 2.这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化,
// 3.并且会传入一个可设置给CompactDisc类型的bean。
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
} public void play() {
cd.play();
} }
测试类CDPlayerTest,包括两部分的测试:
package autoConfig1;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; // 声明测试套件运行器,为了让测试在Spring容器环境下执行,以便在测试开始的时候自动创建Spring的上下文。
@RunWith(SpringJUnit4ClassRunner.class)
// 1.告诉Spring要在CDPlayerConfig中加载配置,因为CDPlayerConfig类中包含了@ComponentScan,启动了Spring的组件扫描。
// 2.由于Spring启动了组件扫描,因此可以扫描出所有带有@Component注解的类,即SgtPeppers类和CDPlayer类,并且在Spring中为其创建一个bean。
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest { @Rule // 这个注解是为了在执行case的时候加入测试者特有的操作,而不影响原有的case代码:减小了特有操作和case原逻辑的耦合。
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog(); // 将MediaPlayer bean注入到测试代码之中。
@Autowired
private MediaPlayer player; // 将CompactDisc bean注入到测试代码之中。
@Autowired
private CompactDisc cd; // 简单的测试断言cd属性不为null。
// 如果它不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
} // 简单的测试断言player属性不为null。
// 如果它不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
@Test
public void playerShouldNotBeNull() {
assertNotNull(player);
} // systemOutRule规则可以基于控制台的输出编写断言,这里断言play()方法的输出被发送到了控制台。
@Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", systemOutRule.getLog());
} }
- 第二种方式是通过XML配置文件定义了Spring的装配规则:
代码结构为:

XML配置文件,用于开启Spring的组件扫描:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 使用XML来启用组件扫描 -->
<context:component-scan base-package="autoConfig2" /> </beans>
用于测试XML自动装配的测试类,通过定义配置文件的路径加载配置信息,同样包括两部分的测试。
package autoConfig2; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
// 设置配置文件xml文件的路径,Spring回去这个路径下面去寻找配置文件中的相关配置。
@ContextConfiguration(locations = "classpath:autoConfig2/autoConfig2.xml")
public class CDPlayerXMLConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Autowired
private CompactDisc cd; @Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
} @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
·三、通过Java代码装配bean
如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。在这种情况下,必须要采用显式装配的方式。
在进行显式配置的时候,有两种方案可以选择:
- Java
- XML
在进行显式装配的时候,JavaConfig是更好的方案,因为它更为强大,类型安全并且对重构友好。因为它就是Java代码,就像应用程序中其他Java代码一样。
尽管它与其他的组件一样都使用相同的语言进行表述,但是JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。
尽管不是必须的,但通常会将JavaConfig放到单独的包中,使他与其他的应用程序逻辑分离开,这样对于它的意图就不会产生困惑了。
代码结构为:

CompactDisc接口,和之前一样没有变化。
package javaConfig;
/**
* 如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实没有太大用处的。
* 所以可以这样说,CD播放器依赖于CD才能完成它的使命。
* CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。
* 它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
*/
public interface CompactDisc { void play();
}
MediaPlayer接口,和之前一样也没有变化。
package javaConfig;
/**
* MediaPlayer接口作为CD播放器的接口。
*/
public interface MediaPlayer { void play(); }
SgtPeppers类,和之前的不一样,少了@Component注解:
package javaConfig;
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);
}
}
CDPlayer类,和之前不一样,同样少了@Component注解:
package javaConfig;
import org.springframework.beans.factory.annotation.Autowired;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
在这之前的例子中,都是通过@Component装配Bean,但是@Component只能注解在类上,不能注解到方法上。对于Java而言,大部分的开发都需要引入第三方的包(jar文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让它们变为开发环境的Bean。但可以使用新类扩展(extends)其包的类,然后在新类上使用@Component注解,这样显得不伦不类。
为了解决这个问题,Spring的@Bean注解可以在方法上使用,并且将方法返回的对象作为Spring的Bean。
最主要的类,CDPlayerConfig配置类:
package javaConfig; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 创建JavaConfig类的关键在于为其添加@Configuration注解
* 在没有@ComponentScan注解的情况下,即不开启组件扫描时,会出现BeanCreationException异常。
* 要在JavaConfig中声明bean,需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。
* 构造器和Setter方法,是支持@Bean方法的两个简单的例子,可以采用任何必要的Java功能来产生bean实例。
*/
@Configuration // 这个注解表明这个类是一个配置类,该类包含在Spring应用上下文中如何创建bean的细节。
public class CDPlayerConfig { //第一种情况是:CompactDisc bean是非常简单的,它自身没有其他的依赖。
//@Bean注解会告诉Spring的是compactDisc方法将会返回一个SgtPeppers对象,该对象要注册为Spring应用上下文中的bean。
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
} // 第二种情况是:CDPlayer bean依赖于CompactDisc bean
// @Bean注解会告诉Spring的是cdPlayer方法将会返回一个CDPlayer对象,该对象要注册为Spring应用上下文中的bean。
// 1.当Spring调用cdPlayer方法创建CDPlayer bean的时候,它会自动装配一个CompactDisc bean到配置方法中。
// 2.然后,方法体就可以按照合适的方式来使用它。
// 3.cdPlayer方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
// 4.不管CompactDisc bean是通过什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
} }
测试类CDPlayerTest类:
package javaConfig; import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
另外,由于@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接口:
package xmlConfigTest1;
public interface CompactDisc {
void play();
}
MediaPlayer接口:
package xmlConfigTest1;
public interface MediaPlayer {
void play();
}
实现了CompactDisc 接口的SgtPeppers类(正常Java代码,没有任何注解):
package xmlConfigTest1;
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);
}
}
实现了MediaPlayer接口的CDPlayer类(正常Java代码,没有任何注解):
package xmlConfigTest1;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
ConstructorArgReferenceTest-context.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 声明SgtPeppers bean,并且SgtPeppers类实现了CompacDisc接口,所以实际上已经有了一个可以注入到CDPlayer bean中的bean -->
<bean id="compactDisc" class="xmlConfigTest1.SgtPeppers" /> <!-- 1.当Spring遇到这个标签时,会创建一个CDPlayer实例。 -->
<!-- 2.<constructor-arg>元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。 -->
<bean id="cdPlayer" class="xmlConfigTest1.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean> </beans>
测试类ConstructorArgReferenceTest类(这里有一个疑问:测试类中没有指明xml配置文件的路径,那么是不是默认读取对应的“类名-context.xml”配置文件呢?通过通知台可以发现,是的!):
package xmlConfigTest1; import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ConstructorArgReferenceTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
2.使用Spring的c-命名空间实现依赖注入。
代码结构为(其中4个基础类不变):

CNamespaceReferenceTest-context.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="xmlConfigTest2.SgtPeppers" /> <!-- 1.使用c-命名空间来声明构造器参数 -->
<!-- 2.使用c-命名空间属性要比使用<constructor-arg>元素简练得多-->
<!-- 3.这里要注意的是“c:cd-ref”中cd是CDPlayer类的构造器中指明的CompactDisc类型的字段。 -->
<!-- 4.可以将参数名称替换为索引,即“c:_0-ref”表示的是第一个构造器参数 -->
<!-- 5.在只有一个构造器参数的情况下,根本不用去标示参数。 -->
<bean id="cdPlayer" class="xmlConfigTest2.CDPlayer" c:cd-ref="compactDisc" /> </beans>
测试类CNamespaceReferenceTest类(也没有写xml文件路径,但是可以自动发现):
package xmlConfigTest2; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CNamespaceReferenceTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
目前所做的DI通常指的都是类型的装配-也就是将对象的引用装配到依赖于它们的其他对象之中--而有时候,需要做的只是用一个字面量值来配置对象。
因此,需要增加一个实现了CompactDisc接口的新的唱片类,即BlankDisc类,这个类像空磁盘一样,可以设置成任意想要的艺术家和唱片名:
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
接下来,需要做的就是,如何设置title和artist这两个属性,即将给定的值以字面量的形式注入到构造器之中。
3.使用<constructor-arg>元素进行构造器参数的注入
代码结构为:

ConstructorArgValueTest-context.xml配置xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 使用value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。-->
<bean id="compactDisc" class="xmlConfigTest3.BlankDisc">
<constructor-arg value="You Don't Love Me, LaDao" />
<constructor-arg value="Jay Chou" />
</bean> <bean id="cdPlayer" class="xmlConfigTest3.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean> </beans>
测试类ConstructorArgValueTest:
package xmlConfigTest3; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ConstructorArgValueTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing You Don't Love Me, LaDao by Jay Chou\r\n", log.getLog());
} }
4.使用c-命名空间进行构造器参数的注入
代码结构为:

配置xml文件CNamespaceValueTest-context.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="xmlConfigTest4.BlankDisc"
c:_0="You Don't Love Me, LaDao"
c:_1="Jay Chou" /> <bean id="cdPlayer" class="xmlConfigTest4.CDPlayer" c:_-ref="compactDisc" /> </beans>
配置xml文件CNamespaceValueTest-context.xml中还可以换一种方案来写:
<bean id="compactDisc" class="xmlConfigTest4.BlankDisc"
c:_title="You Don't Love Me, LaDao"
c:_artist="Jay Chou" />
在装配bean引用和字面量值方面,<constructor-arg>元素和c-命名空间的功能是相同的。但是,有一种情况是<constructor-arg>元素能够实现,而c-命名空间却无法做到的,那就是将结合装配到构造器参数中。
5.使用<constructor-arg>将集合装配到构造器参数中
修改BlankDisc类为ListDisc类,增加CD中包含的所有歌曲列表,播放的时候,将每首歌都播放出来:
package xmlConfigTest5; import java.util.List;
public class ListDisc implements CompactDisc { private String title;
private String artist;
private List<String> tracks; public ListDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
} public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
} }
代码结构为:

配置xml文件ConstructorArgCollectionTest-context.xml(使用<list>元素表明一个包含值的列表将会传递到构造器中,<value>用来指定列表中的每个元素。也可以按照同样的方式使用<set>元素,但是要把ListDisc中引用import java.util.Set,使用Set的时候,所有重复的值都会被忽略掉,存放顺序也不会得以保证。):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="compactDisc" class="xmlConfigTest5.ListDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</constructor-arg>
</bean> <bean id="cdPlayer" class="xmlConfigTest5.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean> </beans>
测试类ConstructorArgCollectionTest:
package xmlConfigTest5; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ConstructorArgCollectionTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n" +
"-Track: With a Little Help from My Friends\r\n" +
"-Track: Lucy in the Sky with Diamonds\r\n" +
"-Track: Getting Better\r\n" +
"-Track: Fixing a Hole\r\n" +
"-Track: She's Leaving Home\r\n" +
"-Track: Being for the Benefit of Mr. Kite!\r\n" +
"-Track: Within You Without You\r\n" +
"-Track: When I'm Sixty-Four\r\n" +
"-Track: Lovely Rita\r\n" +
"-Track: Good Morning Good Morning\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise)\r\n" +
"-Track: A Day in the Life\r\n",
log.getLog());
} }
到目前为止,CDPlayer和BlankDisc(ListDisc)类完全是通过构造器注入的,没有使用属性的Setter方法。继续研究如何使用Spring XML实现属性注入。
首先有一个问题,那就是该选择构造器还是属性注入呢?通用的规则是,对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
修改CDPlayer类,为CompactDisc属性值增加Setter类并去掉CDPlayer类的构造器,现在CDPlayer没有任何的构造器(除了隐含的默认构造器),同时也没有任何的强依赖:
package xmlConfigTest6;
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public void play() {
compactDisc.play();
}
}
修改BlankDisc类成reallyBlankDisc类,reallyBlankDisc完全通过属性注入进行配置,而不是构造器注入:
package xmlConfigTest6;
import java.util.List;
public class reallyBlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
6.使用<property>元素装配bean引用与装配字面量(唯一的区别是是否带有“-ref”后缀,如果没有“-ref”后缀的话,所装配的就是字面量)
代码结构为:

PropertyRefTest-context.xml配置xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过 <property> 元素的value属性来设置title、artist和tracks属性-->
<bean id="compactDisc" class="xmlConfigTest6.reallyBlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
<property name="artist" value="The Beatles" />
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</property>
</bean> <!-- 1.<property>元素为属性的Setter方法所提供的功能与<contructor-arg>元素为构造器所提供的功能是一样的 -->
<!-- 2.通过ref属性引用了ID为compactDisc的bean,并将其通过setCompactDisc()方法注入到compactDisc属性中 -->
<bean id="cdPlayer" class="xmlConfigTest6.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean> </beans>
测试类PropertyRefAndValueTest(和之前的测试类并没有什么变化):
package xmlConfigTest6; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PropertyRefAndValueTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n" +
"-Track: With a Little Help from My Friends\r\n" +
"-Track: Lucy in the Sky with Diamonds\r\n" +
"-Track: Getting Better\r\n" +
"-Track: Fixing a Hole\r\n" +
"-Track: She's Leaving Home\r\n" +
"-Track: Being for the Benefit of Mr. Kite!\r\n" +
"-Track: Within You Without You\r\n" +
"-Track: When I'm Sixty-Four\r\n" +
"-Track: Lovely Rita\r\n" +
"-Track: Good Morning Good Morning\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise)\r\n" +
"-Track: A Day in the Life\r\n",
log.getLog());
} }
7.使用p-命名空间装配bean引用与装配字面量
代码结构为:

配置xml文件PNamespaceRefAndValueTest-context.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 使用p-命令空间来设置属性值,与c-命名空间一样, 不能使用p-命名空间来装配集合 -->
<bean id="compactDisc" class="xmlConfigTest7.reallyBlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club Band"
p:artist="The Beatles">
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</property>
</bean> <!-- 1.使用p-命名空间装配compactDisc属性-->
<!-- 2.通常的格式是p:属性名-ref="所注入bean的ID"-->
<bean id="cdPlayer" class="xmlConfigTest7.CDPlayer"
p:compactDisc-ref="compactDisc" /> </beans>
8.虽然不能使用p-命名空间来装配集合,但是可以使用Spring util-命名空间来简化reallyBlankDisc bean
代码结构为:

配置xml文件PNamespaceWithUtilNamespaceTest-context.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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 3.这样就能向使用其他的bean那样,将磁道列表bean注入到reallyBlankDisc bean中的tracks属性中 -->
<bean id="compactDisc" class="xmlConfigTest8.reallyBlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club Band"
p:artist="The Beatles"
p:tracks-ref="trackList" /> <!-- 1.util-命名空间的<util:list>元素,会创建一个列表的bean -->
<!-- 2.借助<util:list>元素,可以将磁道列表转移到reallyBlankDisc bean之外,并将其声明到单独的bean之中 -->
<util:list id="trackList">
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</util:list> <bean id="cdPlayer" class="xmlConfigTest8.CDPlayer"
p:compactDisc-ref="compactDisc" /> </beans>
五、导入和混合配置
混合配置的原理就是,Spring在自动装配时,并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
没有任何变化的几个类:
CompactDisc接口:
package mixedConfig1;
public interface CompactDisc {
void play();
}
MediaPlayer接口:
package mixedConfig1;
public interface MediaPlayer {
void play();
}
实现了MediaPlayer接口的CDPlayer类:
package mixedConfig1;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
实现了CompactDisc接口的第一个唱片类SgtPeppers 类:
package mixedConfig1;
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);
}
}
实现了CompactDisc接口,使用构造器来进行属性注入,并且拥有歌曲磁道列表的第二个唱片类ListBlankDisc类:
package mixedConfig2;
import java.util.List;
public class ListBlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public ListBlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
接着,梳理一下混合配置常见的几种情况:
1.使用@Import注解,将其中一个JavaConfig导入到另一个JavaConfig当中的第一种方法
代码结构为:

在之前的CDPlayConfig配置类中已经定义了两个bean,就姑且认为它很复杂,所以将其中的SgtPeppers bean分开独立到它自己的配置类CDConfig类中:
package mixedConfig1; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
由于已经移除了CompactDisc()方法,因此需要有一种方法将这两个类组合到一起,因此就在CDPlayerConfig类中使用@Import(CDConfig.class)来导入CDConfig配置类:
package mixedConfig1; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; @Configuration
@Import(CDConfig.class)
public class CDPlayerConfig { @Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
} }
测试类JavaImportJavaConfigTest 中,需要通过@ContextConfiguration(classes = CDPlayerConfig.class)来指明被导入CDConfig的CDPlayerConfig配置类:
package mixedConfig1; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class JavaImportJavaConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
2.不在其中一个配置类当中使用@Import注解,创建一个更高级别的配置类SoundSystemConfig类,在这个类中使用@Import将两个配置类组合在一起
代码结构为:

CDConfig配置类:
package mixedConfig2; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDConfig { @Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
没有加@Import注解的CDPlayerConfig配置类:
package mixedConfig2; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDPlayerConfig { @Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
} }
将两个配置类组合在一起的更高级别的配置类SoundSystemConfig类:
package mixedConfig2; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; @Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig { }
测试类JavaImportJavaConfigTest类,通过@ContextConfiguration(classes = SoundSystemConfig.class)将读取最高级别的配置类SoundSystemConfig类:
package mixedConfig2; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SoundSystemConfig.class)
public class JavaImportJavaConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
3.使用@ImportResource注解,将配置在XML中的ListBlankDisc bean注入到配置在JavaConfig中的CDPlayer bean中
代码结构为:

配置类CDPlayerConfig类:
package mixedConfig3; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDPlayerConfig { @Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
} }
配置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="mixedConfig3.ListBlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles">
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</constructor-arg>
</bean> </beans>
最高级别的配置类SoundSystemConfig类:
package mixedConfig3; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource; @Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:/mixedConfig3/cd-config.xml")
public class SoundSystemConfig { }
测试类JavaImportXmlConfigTest类,指明SoundSystemConfig为读取的配置类(这里遇到了一个问题,就是如果直接copy xml文档不注意去掉空格的话,可能会报错,所以要先对xml文件format一下):
package mixedConfig3; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SoundSystemConfig.class)
public class JavaImportXmlConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n"
+ "-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n"
+ "-Track: With a Little Help from My Friends\r\n"
+ "-Track: Lucy in the Sky with Diamonds\r\n" + "-Track: Getting Better\r\n"
+ "-Track: Fixing a Hole\r\n", log.getLog());
} }
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中:
<?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"> <import resource="cd-config.xml" />
<bean id="cdPlayer" class="mixedConfig4.CDPlayer" c:cd-ref="compactDisc" /> </beans>
测试类XMLImportXMLConfigTest类,给定最高级别的配置文件路径“/mixedConfig4/cdplayer-config.xml”
package mixedConfig4; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/mixedConfig4/cdplayer-config.xml")
public class XMLImportXMLConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
5.<bean>元素能够用来将JavaConfig配置导入到XML配置中
代码结构为:

使用JavaConfig配置的配置类CDConfig类:
package mixedConfig5; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDConfig { @Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
使用<bean>元素将CDConfig配置类导入到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 class="mixedConfig5.CDConfig" /> <bean id="cdPlayer" class="mixedConfig5.CDPlayer" c:cd-ref="compactDisc" /> </beans>
测试类XMLImportJavaConfigTest类,指明xml配置文件路径为/mixedConfig5/cdplayer-config.xml:
package mixedConfig5; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/mixedConfig5/cdplayer-config.xml")
public class XMLImportJavaConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
6.比较合理的做法是,创建一个更高层次的配置文件,这个文件不包含任何的bean,只是负责将两个或者更多的配置组合起来
代码结构为:

配置类CDConfig类:
package mixedConfig6; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CDConfig { @Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
配置xml文件cdplayer-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="cdPlayer" class="mixedConfig6.CDPlayer" c:cd-ref="compactDisc" /> </beans>
最高级别配置文件SoundSystemConfig.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 class="mixedConfig6.CDConfig" /> <import resource="cdplayer-config.xml" /> </beans>
指明最高级别配置文件路径的测试类SoundSystemConfigTest类:
package mixedConfig6; import static org.junit.Assert.assertEquals; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/mixedConfig6/SoundSystemConfig.xml")
public class SoundSystemConfigTest { @Rule
public final SystemOutRule log = new SystemOutRule().enableLog(); @Autowired
private MediaPlayer player; @Test
public void play() {
player.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n", log.getLog());
} }
六、其他问题
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也定义了按名称查找的方法,如果采用名称查找而不是按类型查找的方法,就可以消除歧义性了。
首先把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
Spring(二)装配Spring Bean的更多相关文章
- Spring(3)——装配 Spring Bean 详解
装配 Bean 的概述 前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中. 大部分场景下,我们都会使用 Appl ...
- spring IOC 装配一个bean
1.0属性注入 新建一个people类 package com.java.test3; /** * @author nidegui * @create 2019-06-22 14:45 */ publ ...
- Spring学习(二)--装配Bean
一.Spring装配机制 Spring提供了三种主要的装配机制: 1.在XML中进行显示配置 2.在Java中进行显示配置 3.隐式的bean发现机制和自动装配--自动化装配bean Spring可以 ...
- 二、Spring装配Bean
内容 声明bean 构造器注入和Setter方法注入 装配Bean 控制bean的创建和销毁 关键词 装配(wiring) 组件扫描(component scanning) 自动装配(AutoWiri ...
- Spring框架系列(二)--装配和注入Bean
企业日常开发中,几乎都是Spring系的框架,无论是SSM.还是现在大火的SpringBoot+JPA/MyBatis,使用最大的目的就是简化开发 基本模块: 核心容器:Beans.Core.Cont ...
- Spring基础学习(二)—详解Bean(上)
在Spring配置文件中,用户不但可以将String.int等字面值注入Bean中,还可以将集合.Map等类型注入Bean中,此外还可以注入配置文件中其他定义的Bean. 一.字面值 ...
- Spring高级装配bean
目录 spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表达式语言 一.环境与profile 配置profile bean 在软件开发的时候,有一个 ...
- spring(二、bean生命周期、用到的设计模式、常用注解)
spring(二.bean生命周期.用到的设计模式.常用注解) Spring作为当前Java最流行.最强大的轻量级框架,受到了程序员的热烈欢迎.准确的了解Spring Bean的生命周期是非常必要的. ...
- 品Spring:SpringBoot发起bean定义注册的“二次攻坚战”
上一篇文章整体非常轻松,因为在容器启动前,只注册了一个bean定义,就是SpringBoot的主类. OK,今天接着从容器的启动入手,找出剩余所有的bean定义的注册过程. 具体细节肯定会颇为复杂,同 ...
随机推荐
- Angular 自定义管道
管道的作用就是将原始值进行转化处理,转换为所需要的值: 1. 新建sex-reform.pipe.ts文件 ng g pipe sex-reform 2. 编辑sex-reform.pipe.ts文件 ...
- C++常用库函数(1)
Hello,疯狂的杰克由于大家见面了哦! 今天,给大家介绍一篇很有内涵的文章:C++常用库函数 1.缓冲区操作函数 函数名:memchr 函数原型:void *memchr(const void * ...
- php无限级分类实战——评论及回复功能
经常在各大论坛或新闻板块详情页面下边看到评论功能,当然不单单是直接发表评论内容那么简单,可以对别人的评论进行回复,别人又可以对你的回复再次评论或回复,如此反复,理论上可以说是没有休止,从技术角度分析很 ...
- poll(2) 源码分析
poll(2) poll(2) 系统调用的功能和 select(2) 类似:等待一个文件集合中的文件描述符就绪进行I/O操作. 使用 实现 select(2) 的局限性: 关注的文件描述符集合大小最大 ...
- springmvc Controller接收前端参数的几种方式总结
(1) 普通方式-请求参数名和Controller方法的参数一致 @Controller @RequestMapping("/param") public class TestPa ...
- Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker
今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...
- hibernate 搭建框架
需要用的包 Hibernate的日志记录: * Hibernate日志记录使用了一个slf4j: * SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具 ...
- Mariadb 基于Mycat实现读写分离
环境:Mariadb主:192.168.200.129 Mariadb从:192.168.200.114 Mycat :192.168.200.112 (1) 安装jdk,先查看本机是 ...
- C#中的等值判断1
目录 简介 值类型和引用类型的相等比较 和相等比较相关的函数 string 和 System.Uri 的等值比较 泛型接口 IEquatable<T> 自定义比较方法 举例 总结 简介 最 ...
- 我最推荐的一张Java后端学习路线图,Java工程师必备
前言 学习路线图往往是学习一样技术的入门指南.网上搜到的Java学习路线图也是一抓一大把. 今天我只选一张图,仅此一图,足以包罗Java后端技术的知识点.所谓不求最好,但求最全,学习Java后端的同学 ...