Spring实战第二章
一、在Spring之中提供了三种主要的装配机制:
1、在xml中进行显示装配
2、在Java中进行显示装配
3、隐式的bean发现机制和自动装配
二、自动装配bean
Spring在两个方面实现自动化装配:
1、组件扫描:Spring会自动发现应用上下文中所创建的bean
2、自动扫描:spring自动满足bean之间的依赖
2.1扫描组件
2.1.1实例
package com.wang.second; public interface CompactDisc { void play(); }
通过注解@Component声明组件
package com.wang.second; import org.springframework.stereotype.Component; @Component public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "The Beatles"; @Override public void play() { // TODO Auto-generated method stub System.out.println("Playing "+title+" by "+artist); } }
注意:SgtPeppers类上使用了@Component注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建 bean。
没有必要显式配置SgtPeppersbean,因为这个类使用了 @Component注解,所以Spring会为你把事情处理妥当。
声明JAVA配置类,启动自动扫描注解或通过xml启动自动扫描
package com.wang.second; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration //声明为配置类 @ComponentScan public class CDPlayerConfig { }
或
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.wang.second"></context:component-scan> </beans>
组件扫描默认是不启用的,还需要显式配置一下Spring, 从而命令它去寻找带有@Component注解的类,并为其创建bean。如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包
在xml配置了扫描的包
测试:
package com.wang.second; import static org.junit.Assert.*; import org.junit.Test; 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 { @Autowired private CompactDisc cd; @Test public void cdSholudNotBeNull(){ assertNotNull(cd); } }
注解:CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以 便在测试开始的时候自动创建Spring的应用上下文。注解 @ContextConfiguration会告诉它需要在CDPlayerConfig中加 载配置。因为CDPlayerConfig类中包含了@ComponentScan,因 此最终的应用上下文中应该包含CompactDiscbean。
2.1.2自动扫描组件的bean名字
Spring应用上下文中所有的bean都会给定一个ID。在前面的例子中, 尽管我们没有明确地为SgtPeppersbean设置ID,但Spring会根据类 名为其指定一个ID。具体来讲,这个bean所给定的ID 为sgtPeppers,也就是将类名的第一个字母变为小写。
指定名字时:
//@Component //@Component("lonelyHeartsClub")或 @Named("lonelyHeartsClub") public class SgtPeppers implements CompactDisc {
。。。。。。。。
}
Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换。
(@Named是Java依赖注入规范(Java Dependency Injection)中所提供注解)
2.1.3自动扫描包
为了指定不同的基础包, 所需要做的就是在@ComponentScan的value属性中指明包的名称;
或者通过basePackages属性进行配置可以设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可。
@Configuration //@ComponentScan //@ComponentScan("com.wang.second")或 //@ComponentScan(basePackages="com.wang.second") @ComponentScan(basePackages={"com.wang.second","com.wang.second222"}) public class CDPlayerConfig { }
也可以除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口 ,这样最好
basePackages属性被替换成了basePackageClasses。同时,我们不是再使用String类型的名 称来指定包,为basePackageClasses属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。
@ComponentScan(basePackageClasses={CompactDisc.class}) public class CDPlayerConfig { }
2.13bean的自动装配
的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayerbean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
还可以在属性的Setter方法上和用在类的任何方法之上
package com.wang.second; import org.springframework.beans.factory.annotation.Autowired;
@Named public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; }
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
@Autowired public void insertDisc(CompactDisc cd){ this.cd = cd; } @Override public void play() { cd.play(); } }
将required属性设置为false时,Spring会尝试执行自动装配,但 是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状 态。
但是,把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性 有可能会出现NullPointerException。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常, 表明没有明确指定要选择哪个bean进行自动装配。
@Autowired是Spring特有的注解。也可以考虑将其替换为@Inject,Spring同时支持@Inject和 @Autowired。尽管@Inject和@Autowired之间有着一些细微的 差别,但是在大多数场景下,它们都是可以互相替换的。
自动装配的验证:
package com.wang.second; import static org.junit.Assert.*; import javax.inject.Inject; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.StandardOutputStreamLog; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=CDPlayerConfig.class) public class CDPlayerTest { @Rule public final StandardOutputStreamLog log = new StandardOutputStreamLog(); @Autowired private CompactDisc cd; @Inject private MediaPlayer player; @Test public void cdSholudNotBeNull(){ assertNotNull(cd); } @Test public void play(){ player.play(); assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band"+ " by The Beatles\n", log.getLog()); } }
注意:StandardOutputStreamLog需要引入这个
</dependency> <dependency> <groupId>com.github.stefanbirkner</groupId> <artifactId>system-rules</artifactId> <version>1.16.0</version> </dependency>
三、通过Java代码装配bean(显示)
JavaConfig与其他的Java代码有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样 都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解,@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定
@Bean(name="lonelyheartsClubBand") public CompactDisc sgtPeppers(){ return new SgtPeppers(); }
对于bean之间的依赖的实现:
在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer的可行方案:
@Bean public CDPlayer cdPlay(){ return new CDPlayer(sgtPeppers()); }
@Bean
public CDPlayer anotherCDPlay(){
return new CDPlayer(sgtPeppers());
}
解释:cdPlayer()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用
默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。
通过参数构造器传入
@Bean public CDPlayer cdPlayPar(CompactDisc compactDisc){ return new CDPlayer(compactDisc); } @Bean public CDPlayer cdPlayer(CompactDisc compactDisc){ CDPlayer cdPlayer = new CDPlayer(compactDisc); cdPlayer.setCd(compactDisc); return cdPlayer; }
注解:在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。
可以采用其他风格的DI配置带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。
//Setter方法注入
@Bean public CDPlayer cdPlayer(CompactDisc compactDisc){ CDPlayer cdPlayer = new CDPlayer(compactDisc); cdPlayer.setCd(compactDisc); return cdPlayer; }
总的案例:
package com.wang.second; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configurationpublic class CDPlayerConfig { @Bean(name="lonelyheartsClubBand") public CompactDisc sgtPeppers(){ return new SgtPeppers(); } @Bean public CDPlayer cdPlay(){ return new CDPlayer(sgtPeppers()); } @Bean public CDPlayer anotherCDPlay(){ return new CDPlayer(sgtPeppers()); } @Bean public CDPlayer cdPlayPar(CompactDisc compactDisc){ return new CDPlayer(compactDisc); } @Bean public CDPlayer cdPlayer(CompactDisc compactDisc){ CDPlayer cdPlayer = new CDPlayer(compactDisc); cdPlayer.setCd(compactDisc); return cdPlayer; } /* @Bean public CompactDisc randomBeatlesCD(){ int choice =(int) Math.floor(Math.random()*4); if(choice ==0){ return new SgtPeppers(); }else if(choice==1){ return new SgtPeppers();//代表其他类型 }else if(choice==2){ return new SgtPeppers(); }else { return new SgtPeppers(); } } */ }
四、通过xml配置bean(显示)
4.1xml规范
在XML配置中,要创建一个XML文件,并且要以<beans>元素为根;在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。
简单的实例:
<bean>元素类似于JavaConfig中的@Bean注解
<bean class="com.wang.second.SgtPeppers" />
bean的类通过class属性来指定的,并且要使用全限定的类名。因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的ID将会是“com.wang.second.SgtPeppers#0”。其中,“#0”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个SgtPeppers,并且没有明确进行标识,那么它自动得到的ID将会是“com.wang.second.SgtPeppers#1”。
可借助id属性来设置bean的名字
<bean id="compactDisc" class="com.wang.second.SgtPeppers" />
向上面这样的设置,Spring在发现bean元素,在装载bean的时候,会调用默认的构造器创建bean实例
4.2自定义构造器注入
在XML中声明DI时,构造器注入,有两种基本的配置方案可供选择:
1、<constructor-arg>元素
2、使用Spring 3.0所引入的c-命名空间
4.2.1<constructor-arg>元素之引用传递
<bean id="sgtPeppers" class="com.wang.second.SgtPeppers"/> <bean id="cdPlayer" class="com.wang.second.CDPlayer"> <constructor-arg ref="sgtPeppers"/> </bean>
<constructor-arg>元素会告知Spring要将一个ID为cdPlayer的bean引用传递到CDPlayer的构造器中。
c-命名空间之引用传递
<bean id="cdPlayer" class="com.wang.second.CDPlayer" c:cd_-ref="sgtPeppers"/>
属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字
是sgtPeppers,而不是字面量“sgtPeppers”。
也可以简便化:使用参数在整个参数列表中的位置信息将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。
<bean id="cdPlayer" class="com.wang.second.CDPlayer" c:_0-ref="sgtPeppers"/>
只有一个构造器参数,所以我们还有另外一个方案——根本不用去标示参数
<bean id="cdPlayer" class="com.wang.second.CDPlayer" c:_-ref="sgtPeppers"/>
4.2.2字面值传递
引入新类:
package com.wang.second; public class BlankDisc implements CompactDisc { private String title; private String artist; public BlankDisc(String title,String artist){ this.title=title; this.artist=artist; } @Override public void play() { // TODO Auto-generated method stub System.out.println("Playing "+title + " by "+artist); } }
进行传递:
<bean id="blankDisc" class="com.wang.second.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles"/> </bean>
<bean id="blankDisc" class="com.wang.second.BlankDisc" c:_title="Sgt. Pepper's Lonely Hearts Club Band" c:_artist="The Beatles"/>
或
<bean id="blankDisc" class="com.wang.second.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles"/>
当构造器只有一个参数的时候,直接用一个下划线
<bean id="blankDisc" class="com.wang.second.BlankDisc" c:_="Sgt. Pepper's Lonely Hearts Club Band"/>
XML不允许某个元素的多个属性具有相同的名字
注意:<constructor-arg>能够实现,c-命名空间却无法做到的,即集合装配到构造器参数中。
当构造器包含集合的时候
public class BlankDisc implements CompactDisc { private String title; private String artist; private List<String> tracks; public BlankDisc(String title,String artist){ this.title=title; this.artist=artist; } public BlankDisc(String title,String artist,List<String> tracks){ this.title=title; this.artist=artist; this.tracks=tracks; }
配置如下:
<bean id="blankDisc" class="com.wang.second.BlankDisc"> <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</value> <value>Fixing the Hole</value> </list> </constructor-arg> </bean>
<list>元素是<constructor-arg>的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>元素用来指定列表中的每个元素。也可以使用<ref>元素替代<value>,实现bean引用
列表的装配。
当构造器参数的类型是java.util.List时,使用<list>元素是合情合理的。也可以按照同样的方式使用<set>元素。
如果是Set的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况下,<set>或<list>都可以用来装配List、Set甚至数组。
4.2.3使用Spring XML实现属性注入
该选择构造器注入还是属性注入呢?作为一个通用的规则,倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
( 可选择性:即便没有将CompactDisc装入进去,CDPlayer依然还能具备一些有限的功能,这样就是选择性)
在使用属性注入的时候,是调用属性相关的setter方法,所以必需有.
a、引用注入使用<property>元素
<property>元素为属性的Setter方法所提供的功能
<bean id="sgtPeppers" class="com.wang.second.SgtPeppers"/> <bean id="cdPlayer" class="com.wang.second.CDPlayer"> <property name="cd" ref="sgtPeppers"></property> </bean>
上面是使用的引用传递值,引用了ID为sgtPeppers的bean(通过ref属性),并将其注入到cd属性中(通过setCd()方法)
b、引用注入使用p命名空间
<bean id="cdPlayer" class="com.wang.second.CDPlayer" p:cd-ref="sgtPeppers"/>
c、使用<property>元素字面值注入
属性name标识相应setter方法的参数名
<bean id="blankDisc" class="com.wang.second.BlankDisc"> <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</value> <value>Fixing the Hole</value> </list> </property> </bean>
d、使用p命名空间字面值注入
没有了引用的-ref
<bean id="blankDisc" class="com.wang.second.BlankDisc" 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</value> <value>Fixing the Hole</value> </list> </property> </bean>
延深:使用Spring util-命名空间中的一些功能来简化。
元素 | 描述 |
<util:constant> | 引用某个类型的public static域,并将其暴露为bean |
util:list | 创建一个java.util.List类型的bean,其中包含值或引用 |
util:map | 创建一个java.util.Map类型的bean,其中包含值或引用 |
util:properties | 创建一个java.util.Properties类型的bean |
util:property-path | 引用一个bean的属性(或内嵌属性),并将其暴露为bean |
util:set | 创建一个java.util.Set类型的bean,其中包含值或引用 |
<util:list id="tracklist"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a little</value> <value>Fixing the Hole</value> </list> </util:list> <bean id="blankDisc" class="com.wang.second.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles" p:tracks-ref="tracklist"> </bean>
上述在xml中的声明命名空间及其模式
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
五、导入和混合配置
5.1在JavaConfig中引入xml配置
当把一个JavaConfig类拆成多个的时候,进行组合
例如拆分
@Configuration @ComponentScan(basePackageClasses={CompactDisc.class}) public class CDPlayerConfig { @Bean public CDPlayer cdPlayPar(CompactDisc compactDisc){ return new CDPlayer(compactDisc); } @Bean public CompactDisc sgtPeppers(){ return new SgtPeppers(); } }
拆分为CDConfig+CDPlayerConfig
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CDConfig { @Bean public CompactDisc sgtPeppers(){ return new SgtPeppers(); } }
在CDPlayerConfig引入
@Configuration @ComponentScan(basePackageClasses={CompactDisc.class}) @Import(CDConfig.class) public class CDPlayerConfig { @Bean public CDPlayer cdPlayPar(CompactDisc compactDisc){ return new CDPlayer(compactDisc); } }
一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig。
或者采用一个更好的办法,也就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类中使用@Import将两个配置类组合在一起。
一起加载在xml配置bean和JavaConfig中的bean
使用@ImportResource注解,假设BlankDisc定义在名为cdconfig.xml的文件中,该文件位于根类路径下,那么可以修改SoundSystemConfig,让它使用@ImportResource注解
5.2在中xml引入JavaConfig配置
在XML中,可以使用import元素来拆分XML配置。
比如,假设希望将BlankDisc bean拆分到自己的配置文件中,该文件名为cd-config.xml,在XML配置文件中使用<import>元素来引用该文件:
将Java配置导入到XML配置中:<bean>元素。为了将JavaConfig类导入到XML配置中:
总结:
不管使用JavaConfig还是使用XML进行装配,通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>或@ComponentScan)。