Spring-2.装配

Spring提供了三种装配的机制:

  1. 自动化装配Bean
  2. 通过Java进行显式配置
  3. 在XML中进行配置

1.自动化装配bean

Spring从两个方面来实现自动化装配:

  1. 组件扫描(component scanning):Spring会自动发现应用上下文中创建的bean
  2. 自动装配(auto wiring):Spring自动满足bean之间的依赖

以下以一个CD播放器的概念展现自动化装配bean的过程

1.1 创建可被发现的bean

用CompactDisc接口定义CD的概念

package com.junzerg.player_autoconfig;

public interface CompactDisc {
    void play();
}

1.2 带有@Component注解的CompactDisc实现类SgtPeppers

package com.junzerg.player_autoconfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;

    @Autowired
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

    public void play() {
        cd.play();
    }

}
  1. @Component注解

    • @Component注解表明该类会作为组件类,并告知Spring要为这个类创建bean
    • 默认bean的ID名为类名首字母变成小写,可以通过@Component为bean设置自定义ID: @Component("theId")
  2. @Autowired注解声明自动装配,该注解可以用在类的任何方法,主要是构造器方法,和Setter方法上。Spring都会尝试满足方法参数上所声明的依赖。

    • 如果有一个bean匹配依赖需求,这个bean会被装配进来
    • 如果没有匹配的bean,在应用文创建的时候,Spring会抛出一个异常。可以通过设置@AutoWired注解的required属性值为false来避免异常的出现。这时,Spring尝试执行自动装配,但是如果没有匹配的bean,Spring会让这个bean处于未装配的状态,有可能出现NullPointerException异常。
    • 如果有多个bean能满足依赖关系,Spring会抛出一个异常,这里可以添加一个自动装配的选择范围。
    • @Autowired是Spring特有的注解,可以替换为Java注解中的@Injection。

1.3 配置Spring,使其寻找带有@Component注解的类,并为其创建bean。

  1. 通过Java代码中使用@ComponentScan注解,启动组件扫描
    • 默认用配置类所在的包作为基础包(base package)来扫描组件
    • 用value属性来指定不同的基础包:@ComponentScan("soundsystem")
    • 通过basePackages属性更加清晰地指明基础包:@Component(basePackages="soundsystem")
    • 将basePackages属性设置为一个数组来设置多个基础包:@Component(basePackages={"soundsystem", "video"})
    • 可以指定为包中所含的类或者接口:@Component(basePackagesClasses=CDPlayer.class. DVDPlayer.class)
package com.junzerg.player_autoconfig.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.junzerg.player_autoconfig")
public class CDPlayerConfig {
}
  1. 在XML配置方法中,元素可以有对应的属性和子元素。



    


1.4 测试

  1. 测试通过Java配置的
package com.junzerg.player_autoconfig;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
//import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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;

import com.junzerg.player_autoconfig.config.CDPlayerConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {

    @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\n", log.getLog());
    }

}
  1. 测试通过XML配置的
package com.junzerg.player_autoconfig;

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(locations = "classpath:META-INF/spring/soundsystem.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\n", log.getLog());
    }

}
  1. 测试结果

Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles

2. 通过Java代码配置bean

  • 和xml相比,JavaConfig是更好的方案
  • JavaConfig是配置代码,不应该包含任何的业务逻辑,也不应该侵入到业务代码中
  • 通常会把JavaConfig放到单独的包中,使它与其他的业务逻辑分开

2.1 创建配置类

通过@Configuration租借表明类是配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
注意和上一节相比,移除了@ComponentScan注解。

package com.junzerg.player_javaconfig.config;

import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
}

2.2 声明简单的bean

在JavaConfig中编写方法,创建所需类型的实例,为方法添加@Bean注解。将会把方法返回的对象注册为Spring应用上下文中的bean。
如下代码声明了CompactDisc bean:

@Bean
public CompactDisc sgtPeppers() {
    return new SgtPeppers();
}

@Bean表示这个方法将返回一个要注册为Spring应用上下文的bean。方法体中是产生这个bean实例的逻辑。

默认情况下 ,bean的ID和@Bean注解的方法名相同。如下代码可以指定一个不同的名字:

@Bean(name="theSgtPeppers"
public CompactDisc sgtPeppers(){
    return new SgtPeppers();
}

在Java描述中,可以发挥Java的所有功能,只要最终产生的是一个CompactDisc实例。如下代码可以随机选择一个CompactDisc播放:

@Bean
public CompactDisc randomBeatlesCD() {
    int choice = (int) Math.floor(Math.random() * 4);
    if(choice == 0) {
        return new SgtPeppers();
    }
    else if(choice == 1) {
        return new WhiteAlbum();
    }
    else if(choice == 2) {
        return new HardDaysNight();
    }
    else {
        return new Revolver();
    }
}

2.3 用JavaConfig实现注入

在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法。如下方法声明一个CDPlayer

@Bean
public CDPlayer cdPlayer() {
    return new CDPlayer(sgtPeppers());
}

在使用了@Bean注解后,表明这个方法创建一个ID为cdPlayer的bean实例并注册到Spring应用上下文中。
但是cdPlayer()方法和前文sgtPepper()方法区别在于没有使用默认的构造器实例,而是需要传入CompactDisc对象的构造器来创建CDPlayer实例。添加@Bean之后,Spring会拦截所有对它的调用,并确保该方法所创建的bean,而不是每次都对其进行直接的调用。
例如如下代码会得到相同的SgtPepper实例:

@Bean
public CDPlayer cdPlayer(){
    return new CDPlayer(sgtPepper());
}
@Bean
public CDPlayer anotherCDPlayer(){
    return new CDPlayer(sgtPepper());
}

通过如下方法是引用其他bean的最佳选择:

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
    return new CDPlayer(compactDisc);
}

在Sring调用cdPlayer()创建CDPlayer bean 的时候,它会自动装配一个CompactDisc到装配方法中,然后方法体可以按照合适的方法来使用它。它可以通过组件扫描功能或XML来配置,只要功能健全,不管CompactDisc用什么方式创建的,Spring都会将其传入到配置方法中,来创建CDPlayer bean。

因此注意:带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。

2.4 测试

  1. 装配文件为:
package com.junzerg.javaconfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {

    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }

    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
    }

}
  1. SgtPeppers.java
package com.junzerg.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);
    }

}
  1. 测试类
package com.junzerg.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\n", log.getLog());
    }

}
  1. 测试结果

Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles

3. 通过XML装配

3.1 创建XML配置规范

类似于使用JavaConfig要创建一个嗲有@Configuration注解的类,在XML配置中,要创建一个以元素为根的XML文件。
其基本结构如下:







3.2 声明简单的

类似于JavaConfig中的@Bean注解,在XML配置模式中要使用元素声明一个bean。如下代码声明了一个CompactDisc bean:


创建这个bean的类通过class属性来指定,并且要使用全限定类名。

如果没有明确指定ID,bean会根据全限定类名来进行命名,上面代码中的bean的ID将会是"com.junzerg.xmlconfig.SgtPeppers#0"。#0用来区分相同类型的其他bean,如果声明另外一个没有明确标识的SgtPeppers,其ID将会是"com.junzerg.xmlconfig.SgtPeppoers#1"。

如下代码为bean指定ID:


通过xml配置bean的一些特征:

  • 不需要直接负责创建SgtPeppers,Spring发现的元素会调用默认构造器。
  • bean类型是以字符串的形式设置在class属性中的。
    • 该值的正确性需要保证
    • 重命名类之后字符串需要修改

3.3 借助构造器注入初始化的bean

在XML中声明DI时,有两种配置方案可以选择:

  1. 元素,会显得XML更冗长,但是能完成一些c-命名空间不能完成的工作。
  2. c-命名空间,简洁一些,但是功能有限

3.3.1 通过构造器注入bean引用

如下代码用元素的方式声明CDPlayer并通过ID引用StgPeppers:


    

如下代码用命名空间的方式声明:

  • 在XML顶部声明其模式
  • 标签中使用属性


    

c-命名空间的属性名组成含义为:


Spring-2.装配_第1张图片
属性名组成含义

还可以在使用参数在整个参数列表中的位置信息:


如果只有一个构造器参数,还可以不用标示参数:


3.3.2 将字面量注入到构造器中

可以用一个字面量值来配置对象。
例如如下所示的CompactDisc的实现:

package com.junzerg.xmlconfig;

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);
    }

}

这时需要同时修改xml配置:

  • 如果使用元素,使用value属性表明给定值用字面量的形式注入到构造器中:

    
    

  • 如果使用c-命名空间:
    1. 引用构造器参数的名字,去掉-ref后缀。

  1. 使用参数索引

  1. 如果只有一个构造器参数,可以使用如下方式:

3.3.3 装配集合

如果构造器参数中有列表或者集合, 也需要装配进去,例如如下实现:

package com.junzerg.xmlconfig.collections;

import java.util.List;
import com.junzerg.xmlconfig.CompactDisc;

public class BlankDisc implements CompactDisc {

    private String title;
    private String artist;
    private List tracks;

    public BlankDisc(String title, String artist, List 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);
        }
    }

}

为了提供列表作为构造器参数,有以下方法:

  • 最简单的方式就是使用null作为列表参数,注意需要使用元素

    
    
    

  • 更好的方法是提供列表或集合参数
    1. 使用元素作为的子元素,元素来制定列表列表中的每个元素:

    
    
        
            Sgt. Pepper's Lonely Hearts Club Band
            With a Little Help from My Friends
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            
        
    

  1. 使用ref元素代替实现。

    
    
        
          
          
          
          
        
    

  1. 元素同样能被用来设置集合元素

    
    
        
            Sgt. Pepper's Lonely Hearts Club Band
            With a Little Help from My Friends
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            
        
    

注意:

  1. 使用Set元素的话,所有重复的值都会被忽略,存放顺序也不会保证。
  2. c-命名空间不能 实现装配集合的功能。

3.3.4 设置属性

对于属性注入的如下类:

package com.junzerg.xmlconfig;

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();
    }

}

p.s:对强依赖使用构造器注入,对于可选依赖使用属性注入。

接下来对CDPlayer声明为Spring bean(需要property元素应用bean并通过setCompactDisc()方法注入到compactDisc属性中):


    

元素可以用c-命名空间作为替代方案一样,property元素可以用p-命名空间作为替代方案:

  1. 在XML文件中对命名空间进行声明
  2. 使用p-命名空间对属性进行装配




Spring-2.装配_第2张图片
p-命名空间属性的组成
  1. 属性名称用p:前缀,表明所设置的是一个属性
  2. 要注入的属性
  3. 属性名以-ref结尾,提示Spring要装配的是引用,而不是字面量

将字面量注入属性中

属性也可以注入字面量。例如如下Blank Disc类,不要求装配任何属性:

package com.junzerg.xmlconfig;

import java.util.List;

public class BlankDisc implements CompactDisc {

    private String title;
    private String artist;
    private List tracks;
    
    public void setTitle(String title) {
        this.title = title;
    }
    
    public void setArtist(String artist) {
        this.artist = artist;
    }
    
    public void setTracks(List tracks) {
        this.tracks = tracks;
    }
    public void play() {
        System.out.println("Playing " + title + " by " + artist);
        for( String track : tracks) {
            System.out.println("-Track: " + track);
        }
    }

}

然后再借助元素的value属性来进行属性装配:


    
    
    
        
            Sgt. Pepper's Lonely Hearts Club Band
            With a Little Help from My Friends
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            She's Leaving Home
            Being for the Benefit of Mr. Kite!
            Within You Without You
            When I'm Sixty-Four
            Lovely Rita
            Good Morning Good Morning
            Sgt. Pepper's Lonely Hearts Club Band Reprise)
            A Day in the Life
        
    

同样,也可以通过p-命名空间的属性来讲字面量注入,和c-命名空间一样的是,装备bean应用与装配字面量的唯一区别在于是否带有"-ref"后缀:


    
        
            Sgt. Pepper's Lonely Hearts Club Band
            With a Little Help from My Friends
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            She's Leaving Home
            Being for the Benefit of Mr. Kite!
            Within You Without You
            When I'm Sixty-Four
            Lovely Rita
            Good Morning Good Morning
            Sgt. Pepper's Lonely Hearts Club Band (Reprise)
            A Day in the Life
        
    

注意,不能使用p-命名空间来装配集合,没有便利方式使用p-命名空间来指定一个值(或者bean应用)的列表。但是可以使用-util-命名空间的一些功能来简化bean:

  1. 先在XML中声明util-命名空间及其模式:


    ...

  1. 使用元素,创建一个立标bean:

    Sgt. Pepper's Lonely Hearts Club Band
    With a Little Help from My Friends
    Lucy in the Sky with Diamonds
    Getting Better
    Fixing a Hole
    She's Leaving Home
    Being for the Benefit of Mr. Kite!
    Within You Without You
    When I'm Sixty-Four
    Lovely Rita
    Good Morning Good Morning
    Sgt. Pepper's Lonely Hearts Club Band (Reprise)
    A Day in the Life

  1. 再将bean注入到需要引入的bean的tracks属性中:

p.s.:还有可用的util-命名空间的成员如下:

元素 描述
引用某个类型的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,其中包含值或者引用

4 导入和混合配置

4.1 在JavaConfig中引入XML配置

在JavaConfig中可以使用@import注解导入其他的JavaConfig,例如:

package com.junzerg.mixedconfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Bean;

@Configuration
@Import(CDPlayerConfig.class)
public class SoundSystemConfig {
     @Bean
    public CDPlayer cdPlayer(CompactDisc cd){
        return new CDPlayer(cd);
    }
}

对于XML配置的BlankDisc:
cd-config.xml


    
        
            Sgt. Pepper's Lonely Hearts Club Band
            With a Little Help from My Friends
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            
        
    

以及JavaConfig注解的CDPlayerConfig
那么可以用@ImportResource注解,将他们引入同一个JavaConfig:

package com.junzerg.mixedconfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {

}

4.2 在XML配置中引用JavaConfig

可以用元素在XML中引入另外的xml配置文件:




  

  
        

要将JavaConfig文件引入xml文件,可以直接使用元素:




  

  
        

5. 其他

5.1 本节使用了Spring的测试功能,因此要在Maven中添加Spring-test依赖


    org.springframework
    spring-test
    4.3.10.RELEASE
    test

5.2 本节原例中的org.junit.contrib.java.lang.system.StandardOutputStreamLog需要在Maven中引入以下依赖


    com.github.stefanbirkner
    system-rules
    1.16.1
    test

另:原例中使用的StandardOutputStreamLog 包在最新的Junit中已经没有了,根据网上修改成以下,并修改相关用法:

import org.junit.contrib.java.lang.system.SystemOutRule;

@Rule
public final SystemOutRule log = new SystemOutRule().enableLog();

你可能感兴趣的:(Spring-2.装配)