- 创建应用对象之间协作关系的行为通常称为装配(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如何实现依赖注入呢?我们以隐式配置为例:
)创建配置类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>
这样就可以起到同样的作用了。