Spring-装配Bean
- 创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
- Spring提供三种主要的装配机制:
1.在XML中进行显式配置;
2.在java中进行显式配置;
3.隐式的bean发现机制和自动装配。
注:这三种机制可以自由搭配,但是建议尽可能地使用自动配置的机制,显式配置越少越好,并且尽量使用java进行配置,即使用注解的方式进行配置。
- Spring从两个角度实现自动化装配:
1.组件扫描(component scanning):Spring会自动发现上应用上下文中所创建的bean。
2.自动装配(autowiring):Spring自动满足bean之间的依赖。
注:组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将显式配置降低到最少。
package soundSystem; /** * 创建CD接口,只有一个cdInfo方法来在控制台显示CD相关信息 * @author yan */ public interface CompactDisc { void cdInfo(); }
package soundSystem; /** * CompactDisc接口的实现类,此类中定义了CD的名字和作者以及打印相关信息的方法 * @author yan */ public class SgtPeppers implements CompactDisc{ private String title="K歌之王"; private String artist="陈奕迅"; @Override public void cdInfo() { System.out.print("This CD's title:"+title+";artist:"+artist); } }
package soundSystem; /** * 定义多媒体播放器接口,包含play方法 * @author yan */ public interface MediaPlayer { void play(); }
1 package soundSystem; 2 /** 3 * 定义多媒体播放器的实现类,并使用有参构造关联了CompactDisc接口,重写了play方法 4 * @author yan 5 */ 6 public class CDPlayer implements MediaPlayer{ 7 8 private CompactDisc cd; 9 10 public CDPlayer(CompactDisc cd) { 11 super(); 12 this.cd = cd; 13 } 14 public void setCd(CompactDisc cd) { 15 this.cd = cd; 16 } 17 @Override 18 public void play() { 19 System.out.print("Info of CD playing :"); 20 cd.cdInfo(); 21 } 22 23 }
以上四段代码仅仅列举出了CD和播放器的接口、实现类以及它们之间的依赖关系,比如,CDPlayer的构造方法中传入了CD类型的参数。
Spring如何实现依赖注入呢?我们以隐式配置为例:
首先,在同一包下(soundSystem)创建配置类CDPlayerConfig,效果如下
1 package soundSystem; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 6 @Configuration 7 @ComponentScan//如果此处不加任何参数,默认扫描与配置类相同的包 8 //@ComponentScan("soundSystem")//指定单独的包,这个包内主要放置bean配置 9 //@ComponentScan(basePackages={"soundSystem","video"})//允许指定多个包,但是,这种用字符串表示的包不安全,可以尝试使用class方式 10 //@ComponentScan(basePackageClasses={CompactDisc.class,SgrPeppers.class}) 11 public class CDPlayerConfig {}
@Configuration表明这是一个配置类,@ComponentScan表示自动扫描,其不同用法见代码注释。
此时,只是扫描包,但并无作用,因为没有相关标志表明它是扫描的目标,即所要生成的bean,而Spring之所以存在是因为解耦和,即不用传统方法来new一个新的实例,因此在实现类中使用@Component标明,即可达到效果,代码如下:
package soundSystem; import org.springframework.stereotype.Component; /** * CompactDisc接口的实现类,此类中定义了CD的名字和作者以及打印相关信息的方法 * @author yan */ @Component("compactd")//用于扫描来生成bean,并且提供了初始化所用的值 public class SgtPeppers implements CompactDisc{ private String title="K歌之王"; private String artist="陈奕迅"; @Override public void cdInfo() { System.out.print("This CD's title:"+title+";artist:"+artist); } }
Component不加参数时,默认id为第一个字母小写的类名:sgtPeppers,有参数则id为参数。
注:这里CompactDisc是独立存在的,不依赖任何其他类的,因此不需要装配,对于MediaPlayer则需要,因为其实现类中明确定义了CompactDisc类型的有参构造方法,所以,仍然需要标明依赖关系,使用@AutoWired可以达到目的,代码如下:
1 package soundSystem; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Component; 5 /** 6 * 定义多媒体播放器的实现类,并使用有参构造关联了CompactDisc接口,重写了play方法 7 * @author yan 8 */ 9 @Component 10 public class CDPlayer implements MediaPlayer{ 11 private CompactDisc cd; 12 @Autowired 13 public CDPlayer(CompactDisc cd) { 14 super(); 15 this.cd = cd; 16 } 17 @Override 18 public void play() { 19 System.out.print("Info of CD playing :"); 20 cd.cdInfo(); 21 } 22 23 }
这表明不但创造了一个叫cDPlayer的bean,而且还在bean中引用(或者说自动装配)了名叫compactd的bean为参数。
注意:此处使用的自动装配的对象是有参构造方法,而Spring还提供了Setter方法的自动装配方式,用法与此相同。
package soundSystem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 定义多媒体播放器的实现类,并使用有参构造关联了CompactDisc接口,重写了play方法 * @author yan */ @Component public class CDPlayer implements MediaPlayer{ private CompactDisc cd; public CDPlayer(CompactDisc cd) { super(); this.cd = cd; } @Autowired public void setCd(CompactDisc cd) { this.cd = cd; } @Override public void play() { System.out.print("Info of CD playing :"); cd.cdInfo(); } }
写一个测试类CDPlayerTest:
1 package soundSystem; 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.StandardOutputStreamLog; 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 @RunWith(SpringJUnit4ClassRunner.class) 13 @ContextConfiguration(classes=CDPlayerConfig.class) 14 public class CDPlayerTest { 15 16 @Rule 17 public final StandardOutputStreamLog log=new StandardOutputStreamLog();//需要引入system-rules-1.3.0.jar 18 19 @Autowired 20 private MediaPlayer player; 21 22 @Autowired 23 private CompactDisc cd; 24 25 @Test 26 public void cdShouldNotBeNull() { 27 assertNotNull(cd);//如果测试正常,说名cd已被初始化,Spring依赖注入有效发挥作用了 28 } 29 30 @Test 31 public void play(){ 32 player.play(); 33 assertEquals("Info of CD playing :This CD's title:K歌之王;artist:陈奕迅", log.getLog()); 34 } 35 }
需要注意的是,在声明MediaPlayer和CompactDisc时,需要加上自动装配的标志@Autowired,否则无法通过测试。
此时我们使用扫描的方式实现依赖注入的,也可以不用扫描(将@ComponentScan注释掉,保留@AutoWired),直接在javaConfig中定义bean
1 package soundSystem; 2 3 import org.springframework.context.annotation.Bean; 4 //import org.springframework.context.annotation.ComponentScan; 5 import org.springframework.context.annotation.Configuration; 6 7 @Configuration 8 //@ComponentScan//如果此处不加任何参数,默认扫描与配置类相同的包 9 //@ComponentScan("soundSystem")//指定单独的包,这个包内主要放置bean配置 10 //@ComponentScan(basePackages={"soundSystem","video"})//允许指定多个包,但是,这种用字符串表示的包不安全,可以尝试使用class方式 11 //@ComponentScan(basePackageClasses={CompactDisc.class,SgrPeppers.class}) 12 public class CDPlayerConfig { 13 @Bean 14 public CompactDisc sgtPeppers(){ 15 return new SgtPeppers(); 16 } 17 @Bean 18 public CDPlayer cdPlayer(){ 19 return new CDPlayer(sgtPeppers()); 20 } 21 }
此时bean缺省id为方法名,也可以自定义(name="***")。
之所以建议多用javaConfig方式而不是xml方式,主要原因:javaConfig更为强大、类型安全并且对重构友好,因为它就是java代码,就像应用程序中的其他Java代码一样。因此我们可以发挥java提供的所有功能,只要最终得到一个所需实例即可。--《Spring实战》中举例为:定义多个同为CD实现类的bean,然后可以通过生产随机数和if判断来控制CDPlayer随机播放。
以上是使用javaConfig或说注解的方式来实现bean的创建和管理,下面看看XML方式的配置,因为我们已经写了注解,所以直接删除javaConfig类,然后在根目录下创建一个xml文件applicationContext.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: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="soundSystem"></context:component-scan> </beans>
这样就实现了和javaConfig类同样的功能。如果删除注解,只用xml来管理则可配置如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans 3 xmlns="http://www.springframework.org/schema/beans" 4 xmlns:c="http://www.springframework.org/schema/c" 5 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 6 xmlns:p="http://www.springframework.org/schema/p" 7 xmlns:context="http://www.springframework.org/schema/context" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd 12 "> 13 <bean id="compactd" class="soundSystem.SgtPeppers"/> 14 <bean id="cDPlayer" class="soundSystem.CDPlayer" > 15 <constructor-arg ref="compactd"></constructor-arg> 16 </bean> 17 </beans>
同时在jUnit测试中将@ContextConfiguration(classes=CDPlayerConfig.class)改为:@ContextConfiguration(locations="/applicationContext.xml")即可实现同样的效果。
此时我们使用引用的方式将id为compactd的bean引为cDPlayer的构造方法的参数,而compactd的初始值是在java文件中定义的,那么如何通过xml文件来定义初始值呢?
首先我们删除java文件SgtPeppers.java(CompactDisc接口的实现类)中的属性赋值,即只定义title和artist,并且补充set方法即可,xml中可配置如下:
1 <bean id="compactd" class="soundSystem.SgtPeppers"> 2 <property name="title" value="K歌之王"></property> 3 <property name="artist" value="陈奕迅"></property> 4 </bean>
这样也可以通过测试。
其实我们这里是用了setter注入方式,它对应的是(默认的)无参构造,所以会出现一种疏忽,即类中有有参构造而忘了写无参构造时,使用setter注入会报错。
言归正传,在xml中,还可以将其调整为构造器注入,并赋予初始值,仍然以compactd举例:
首先在SgtPeppers.java中补充有参构造方法:
1 public SgtPeppers(String title, String artist) { 2 super(); 3 this.title = title; 4 this.artist = artist; 5 }
修改bean为:
<bean id="compactd" class="soundSystem.SgtPeppers"> <constructor-arg value="K歌之王"></constructor-arg> <constructor-arg value="陈奕迅"></constructor-arg><!-- <property name="title" value="K歌之王"></property> <property name="artist" value="陈奕迅"></property> --> </bean>
这里的参数顺序必须与定义参数的顺序一致,否则会错,这样不够灵活,不安全,我们可以通过name属性来定义属性名而不受顺序的限制:
1 <bean id="compactd" class="soundSystem.SgtPeppers"> 2 <constructor-arg name="artist" value="陈奕迅"></constructor-arg> 3 <constructor-arg name="title" value="K歌之王"></constructor-arg><!-- 4 <property name="title" value="K歌之王"></property> 5 <property name="artist" value="陈奕迅"></property> --> 6 </bean>
另外还有c-命名方式,此处不赘述。
下面看看bean中如何放置集合:
先写一个含有集合参数的类:
1 package soundSystem.collections; 2 3 import java.util.List; 4 5 import soundSystem.CompactDisc; 6 7 public class BlankDisc implements CompactDisc{ 8 private String title; 9 private String artist; 10 private List<String> tracks; 11 /** 12 * setter方法,对应无参构造,是唯一的, 13 * 如果有有参构造而没明确标明无参构造,则此处使用setter注入会报错 14 * @param title 15 */ 16 public void setTitle(String title) { 17 this.title = title; 18 } 19 public void setArtist(String artist) { 20 this.artist = artist; 21 } 22 23 /******************************************************** 24 * 有参构造,不唯一,可以是数量不唯一,也可以是参数类型不唯一, 25 * 但为保证能够自动装配,此处只定义一种 26 ********************************************************/ 27 public BlankDisc(String title, String artist, List<String> tracks) { 28 super(); 29 this.title = title; 30 this.artist = artist; 31 this.tracks = tracks; 32 } 33 /*重写接口中的方法*/ 34 @Override 35 public void cdInfo() { 36 System.out.print("This CD's title:"+title+";artist:"+artist+";tracks:"+tracks); 37 } 38 39 }
此类继承CompactDisc接口,并定义了List类型的参数tracks,那么在applicationContext.xml中如何配置:
1 <bean id="compactd" class="soundSystem.collections.BlankDisc"> 2 <constructor-arg name="artist" value="陈奕迅"></constructor-arg> 3 <constructor-arg name="title" value="K歌之王"></constructor-arg> 4 <constructor-arg name="tracks"> 5 <list> 6 <value>1</value> 7 <value>2</value> 8 <value>3</value> 9 </list> 10 </constructor-arg> 11 </bean>
当然,list子元素中还可以使用ref,如
1 <list> 2 <ref bean="#ID1"/> 3 <ref bean="#ID2"/> 4 <ref bean="#ID3"/> 5 </list>
当参数类型是List时,我们可以用<list>,同样也可以使用<set>,区别与list和set的区别相同,即不保证顺序,去掉重复值。
另外一种方法是将list直接放在另外一个bean中,然后在构造参数中引用即可,这里涉及到util:list,需要现在头部文件中加入声明:
1 <beans 2 xmlns="http://www.springframework.org/schema/beans" 3 xmlns:c="http://www.springframework.org/schema/c" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xmlns:p="http://www.springframework.org/schema/p" 6 xmlns:context="http://www.springframework.org/schema/context" 7 xmlns:util="http://www.springframework.org/schema/util" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd 12 http://www.springframework.org/schema/util 13 http://www.springframework.org/schema/util/spring-util.xsd 14 ">
第7,12,13行中已声明,配置bean如下:
1 <util:list id="ul"> 2 <value>1</value> 3 <value>2</value> 4 <value>3</value> 5 </util:list> 6 <bean id="compactd" class="soundSystem.collections.BlankDisc"> 7 <constructor-arg name="artist" value="陈奕迅"></constructor-arg> 8 <constructor-arg name="title" value="K歌之王"></constructor-arg> 9 <constructor-arg name="tracks"> 10 <ref bean="ul"/> 11 </constructor-arg> 12 </bean>
这样就可以起到同样的作用了。