• 创建应用对象之间协作关系的行为通常称为装配(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>



这样就可以起到同样的作用了。