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)

 

posted @ 2017-03-09 10:35  mslog  阅读(1189)  评论(0编辑  收藏  举报