8、装配Bean(spring笔记)

声明:后面的章节是看了《Spring实战》所做的笔记,相关内容也是摘抄下来,这里只是自己做个记录。

一、Spring配置的可选方案

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

  • XML中进行显示配置
  • Java中进行显示配置
  • 隐式的bean发现机制和自动装配

建议是尽可能地使用自动配置的机制,显示配置越少越好。当必须要显示配置的时候,推荐使用类型安全并且比XML更加强大的JavaConfig。只有当想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML

二、自动化装配bean

Spring从两个角度来实现自动化装配:

  • 组件扫描:Spring会自动发现应用上下文中所创建的bean
  • 自动装配:Sping自动满足bean之间的依赖

2.1 创建可被发现的bean

下面使用例子说明:
CompactDisc.java

package soundsystem;
//这是一个CD接口,表示CD
public interface CompactDisc {
  void play();
}

SgPeppers.java

package soundsystem;
import org.springframework.stereotype.Component;
//这是CD接口的一个实现类,其中包含CD名字和艺术家的名字
@Component
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);
  }
}

说明:@Component表示这个类是一个组件类,在装配过程中要将其创建为一个bean。下面配置自动扫面:
CDPlayerConfig

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

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

说明:这里通过这个类定义了Spring装配的规则,其中@Configuration表明这是一个装配规则,而虽然这里没有显示的声明要装配哪些bean,但是使用@ComponentScan就表示默认扫面本包中的所有类,如果发现某个类中配置了@Component注解,那么就将那些类装配为bean。当然也可以显示注明扫面哪个包,下面先看使用XML的方式,之后会说明使用Java的方式:

<context:component-scan base-package="soundsystem" />

说明:<context:component-scan>元素还有一些属性和子元素,这里不细说,加入我们将之前的@ComponentScan去掉,而是使用上面的XML配置,那如何才能让CDPlayerConfig知道呢?这需要使用后面要讲到的@Import注解。下面看一个测试:

package soundsystem;
import static org.junit.Assert.*;
import ......

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
  
  @Autowired
  private CompactDisc cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);//断言cd不为null
  }
}

说明:这里@RunWith(SpringJUnit4ClassRunner.class)表明让Spring自动创建上下文。而@ContextConfiguration(classes=CDPlayerConfig.class)表明告诉此类要在CDPlayerConfig类中加载相关的配置,@Autowired表示自动注入实现了CompactDisc接口的实例。

2.1.1 为组件扫面的bean命名

在创建一个bean时,默认使用组件类的类名为ID(但是将首字母小写),但是我们也可以自己显示定义ID

@Component("longelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}

说明:此时,这个类在被创建为bean的时候的ID就为longelyHeartsClub

2.1.2 设置组件扫描的基础包

之前我们没有为@ComponentScan配置任何参数,于是其默认扫描的是类的包,同时也可以使用XML方式显示的指明要扫描的包,下面我们为其配置相关的属性:

@Configuration
@ComponentScan("soundsystem")
public class CSPlayerConfig(){}

说明:这里就是配置了一个自动扫面的基础包,当然我们可以更清晰的指明这是一个扫描基础包:

@ComponentScan(basePackeges="soundsystem")

说明:当然这里也可以同时指定多个扫描的基础包:

@ComponentScan(basePackeges={"soundsystem","vedio"})

说明:但是这里使用字符串的方式不够安全,我们推荐类的方式,就是将其指定为包中所含类或接口:

@ComponentScan(basePackegeClasses={CDPlayer.class, DVDPlayer.class})

说明:此时在扫描的时候就会扫描这两个类或接口所在包的所有类和接口。即这些类或接口所在的包将被指定为扫描基础包。我们还可以考虑在包中创建一个用来进行扫描的空标记接口,这样不会影响业务类今后的重构工作。

2.2.3 通过为bean添加注解实现自动装配

有些组件类在被创建为bean的过程中可能依赖其他的bean,可以通过注解的方式让Spring自动帮我们注入进来:

package soundsystem;
public interface MediaPlayer {
  void play();
}
package soundsystem;
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();
  }
}

说明:这里CDPlayer类在被创建为bean时依赖一个CompactDisc的实现类,于是使用@Autowired将这个bean注入进来。如果没有匹配的bean或者有多个匹配的bean,将会产生异常。

三、通过Java代码装配bean

前面说的是一种自动装配和自动注入的方式,但是有时候是没办法使用这种自动方式的,比如向将第三方库中的组件装配到你的应用中。此时必须使用显示的方式,即JavaXML配置方式。而对于Java方式配置bean则可以直接在CDPlayerConfig.java中配置,而对于XML方式的bean,可以使用@Import引用。

3.1 创建配置类

首先修改之前的配置类,让其不要自动扫描装配了:

package soundsystem;
@Configuration
public class CDPlayerConfig { 
}

说明:此时没有配置@ComponentScan则就不会自动创建相关的bean了。

3.2 声明简单的bean

要在JavaConfig中声明bean,我们需要编写一个方法,这个方法创建所需类型的实例,然后给这个方法添加@Bean注解:

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

说明:这里注解会告诉Spring这个方法会返回一个对象,该对象要注册为Spring易用上下文中的bean。默认的ID和带有@Bean注解的方法名一样,当然也可以自己指定:

@Bean(name="lonelyHeartsClubBand")

3.3 借助JavaConfig实现注入

有些bean的创建可能依赖于其他bean的创建,我们需要将多个bean装配在一起,在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法,如:

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

说明:这里可以看到CDPlayer bean的创建依赖于CompactDisc bean,于是我们调用了能够产生CompactDisc bean的方法。但是这里注意,看起来,CompactDisc是通过sgtPeppers()得到的,但是并不是如此,因为这个方法上添加了@Bean注解,Spring会拦截所有对它的实际调用,确保直接返回该方法所创建的bean,比如此时还有另一个bean的创建依赖CompactDisc bean

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

说明:如果每次创建bean都是实际调用sgtPeppers()方法,那么每个bean都拥有自己特有的CompactDisc bean,但其实不是,默认情况下,Spring中的bean都是单例的。当然还有一种更为简单的方式:

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

说明:这种方式可能更容易理解,这里不用明确引用CompactDisc@Bean方法,通过这种方式引用其他的bean通常是最佳的选择。此时,不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。这里我们使用的是CDPlayer的构造器实现了DI功能,但是还有其他方式可以实现,比如通过Setter方式:

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
  CDPlayer cdPlayer = new CDPlayer(compactDisc);
  cdPlayer.setCompactDisc(compactDisc);//这里实现了DI功能
  return cdPlayer;
}

说明:我们可以采用任何必要的Java功能来产生bean实例,仅仅收到Java语言的限制。

四、通过XML装配bean

4.1 声明一个简单的<bean>

这里我们需要添加一个<bean>元素,其类似于JavaConfig中的@Bean

<bean class="soundsystem.SgtPeppers" />

说明:这里声明了一个简单的bean,创建这个bean的类通过class属性来指定,并且要使用全限定的类型。而这里没有明确指定ID,所以这个bean将会根据全限定类名来进行命名,这里即为"soundsystem.SgtPeppers#0",其中"#0"是一个计数的形式,用来区分相同类型的其他bean,如果另外声明一个SgtPeppers,并且没有明确指定ID,那么其ID即为"soundsystem.SgtPeppers#1"

4.2 借助构造器注入初始化bean

XML中声明DI时,会有多种可选的配置方案和风格,具体到构造器注入,有两种基本的配置方案:

  • <constructor-age>元素
  • 使用spring3.0所引入的c-命名空间

4.2.1 构造器注入bean引用

之前我们已经声明了一个SgtPeppersbean,并且这个类实现了CompactDisc接口,所以实际上我们已经有了一个可以注入到CDPlayer bean中的bean,所以现在要做的就是通过DI引用SgtPeppers

<bean id="cdPlayer" class="soundsystem.CDPlayer">
  <constructor-age ref="compactDisc"/>
</bean>

说明:spring会创建一个CDPlayer实例,同时 <constructor-age>会告知spring要将一个IDcompactDiscbean引用传递到CDPlayer的构造器中。
当然作为替代方案,可以使用c-命名空间,只是需要在XML配置的顶部声明其模式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:c="http://www.springframework.org/schema/c" 
    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    ......
</beans>

说明:于是可以将之前的配置方法改为如下:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />

说明:属性名以"c-"开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是"-ref",它会告诉spring,正在装配的是一个bean的引用,这个bean名字是comapactDisc,而不是字面量"comapactDisc"
注意:这里的"cd"是构造器参数名,但是直接使用参数名可能不太好,我们也可以这样:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" />

说明:这里使用数字表示构造器参数的位置,也就是第几个参数,由于在XML中不允许数字作为属性的第一个字符,所以在前面加了一个下划线。当然如果只有一个构造器参数,可以将数字拿掉也可以。

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />

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

这里先给出一个CompactDisc的新实例:

package soundsystem.properties;
import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;

  public BlankDisc (String title,String artist) {
    this.artist = artist;
    this.title = title;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
}

说明:在之前的SgtPeppers类中,唱片名称和艺术家的名字都是硬编码的,这里我们让其更加灵活:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt.Peppers's Lonely Hearts Club Band"/>
  <constructor-arg value="The Beatles"/>
</bean>

说明:这里使用value属性,就是表示给定的值要以字面量的行驶证注入到构造器中。如果要使用"c-"命名空间,则配置如下:

<bean id="compactDisc" class="soundsystem.BlankDisc"
  c:_title="Sgt.Peppers's Lonely Hearts Club Band"
  c:_artist="The Beatles"/>

当然亦可以这样:

<bean id="compactDisc" class="soundsystem.BlankDisc"
  c:_0="Sgt.Peppers's Lonely Hearts Club Band"
  c:_1="The Beatles"/>

4.2.3 装配集合

这里先将上面的BlankDisc类改动:

package soundsystem.properties;
import java.util.List;
import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public BlankDisc (String title,String artist,List<String> tracks) {
    this.artist = artist;
    this.title = title;
    this.tracks= tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }
}

说明:这里增加了一个磁道集合属性,这个属性在配置时必须配置,如果没有具体的值传递,可以配置为null,但是这在调用play()方法时会抛出空指针异常,于是我们需要配置一个List列表:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt.Peppers'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>Getting Better</value>
    ...
  </list>
  </constructor-arg>
</bean>

说明:当然集合列表也可以配置为bean引用:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt.Peppers's Lonely Hearts Club Band"/>
  <constructor-arg value="The Beatles"/>
  <constructor-arg>
  <list>
    <ref bean="sgtPeppers"/>
    <ref bean="whiteAlbum"/>
    <ref bean="revolver"/>
    ...
  </list>
  </constructor-arg>
</bean>

说明:也可以使用Set集合。而目前使用c-命名空间的属性无法实现装配集合的功能。

4.3 设置属性

在之前的注入中都是使用构造器注入的,没有使用Setter方法,这里看看如何使用XML配置实现属性注入:

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

说明:可能我们会觉得即使没有将CompactDisc装入进来,CDPlayer依然还能具备一些有限的功能,但是在测试相关功能时可能会出现空指针异常,使用XML配置实现属性配置的方式如下:

<bean id="cdPlayer" class="soundsystem.cdPlayer">
  <property name="compactDisc" ref="compactDisc"/>
</bean>

说明:<property> 元素为属性的Setter 方法所提供的功能与<constructor-arg> 元素为构造器提供的功能是一样的。使用命名空间的方式为:

<bean id="cdPlayer" class="soundsystem.cdPlayer"
  p:compactDisc-ref="compactDisc"/>

说明:当然和之前一样,也要在配置文件头部加上:

xmlns:p="http://www.springframework.org/schema/p"

相关内容和之前的c- 命名空间类似。

4.3.1 将字面量注入到属性中

相关配置基本上和c-命名空间一致,这里不再细说。虽然也不能使用p-命名空间来装配集合,但是可以使用spring util-命名空间中的一些功能类简化BlankDisc bean,首先在配置文件头部加上:

xmlns:util="http://www.springframework.org/schema/util"

说明:util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表bean。如下:

  <util:list id="trackList">
    <value>Sgt. Pepper's Lonely Hearts Club Band</value>
    <value>With a Little...</value>
    <value>Getting Better</value>
    ...
  </util:list>

于是我们就可以使用p-命名空间简化属性配置了:

<bean id="compactDisc" class="soundsystem.BlankDisc"
  p:title="Sgt.Peppers's Lonely Hearts Club Band"
  p:artist="The Beatles"
  p:tracks-ref="trackList"/>

说明: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,其中包含值或引用

五、导入和混合配置

5.1 在JavaConfig中引用XML配置

现在假设CDPlayerConfig已经变得很复杂,需要将其拆分为多个配置:

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

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

说明:此时compactDisc()方法已经从CDPlayerConfig中移除掉了,这里需要将两个配置组合在一起:

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

@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
  
  @Bean
  public CDPlayer cdPlayer() {
    return new CDPlayer(compactDisc());
  }
}

这样便使用@Import将两个配置组合在了一起,当然更好的方法是创建一个更高级别的SoundSystemConfig,在其中将两个配置组合在一起:

package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(CDPlayerConfig.class, CDConfig.class)
public class SoundSystemConfig{
}

说明:如果此时BlankDisc配置在了XML中(cd-config.xml),如何让spring同时加载它和其他基于Java的配置呢?如下:

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

5.2 在XMl中配置引用JavaConfig

XML导入XML配置如下:

<import resource="cd-config.xml">

XML中导入JavaConfig配置如下:

<bean class="soundsystem.CDConfig">
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容