《Spring实战》学习笔记-第二章:装配Bean

创建应用对象之间协作关系的行为通常被称作装配(Wiring),这也是依赖注入的本质。

声明Bean

创建Spring配置

Spring容器提供了两种配置Bean的方式,其一是使用XML文件作为配置文件,其二是基于Java注解的配置方式。
以下是一个典型的Spring 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 此处声明各个Bean -->

</beans>

在<beans>标签内可以放置相关的Spring配置信息,另外,Spring的核心框架自带了10个命名空间的配置:

命名空间 用途
aop 为声明切面以及将@AspectJ注解的类代理为Spring切面提供了配置元素
beans beans支持声明Bean和装配Bean,是Spring最核心也是最原始的命名空间
context 为配置Spring应用上下文提供了配置元素,包括自动检测和自动装配Bean、注入非Spring直接管理的对象
jee 提供了与Java EE API 的集成,例如JNDI和EJB
jms 为声明消息驱动的POJO提供了配置元素
lang 支持配置由Groovy、JRuby或BeanShell等脚本实现的Bean
mvc 启用Spring MVC的能力,例如面向注解的控制器、视图控制器和拦截器
oxm 支持Spring 的对象到XML映射配置
tx 提供声明式事务配置
util 提供各种各样的工具类元素,包括把集合配置为Bean、支持属性占位符元素

声明一个简单的Bean

package com.springinaction.springidol;

public class Juggler implements Performer {

    private int beanBags = 3;

    public Juggler() {
    }

    public Juggler(int beanBags) {
        this.beanBags = beanBags;
    }

    @Override
    public void perform() throws PerformanceException {
        System.out.println("JUGGLING " + beanBags + " BEANBAGS");
    }

}
<bean id="duke" class="com.springinaction.springidol.Juggler"></bean>

<bean>元素是Spring中最基本的配置单元,通过该元素Spring将创建一个对象。当Spring容器加载该Bean时,Spring将使用默认的构造器来实例化该Bean,实际上,duke会使用如下代码来创建:
new com.springinaction.springidol.Juggler();

通过构造器注入

让bean使用另外一个构造方法:

<bean id="duke" class="com.springinaction.springidol.Juggler">
    <constructor-arg value="15"></constructor-arg>
</bean>

在构造Bean的时候,可以使用<constructor-arg>标签来告诉Spring额外的信息,这样Spring就不再会使用默认的构造器来实例化该Bean。
测试代码:

package com.springinaction.springidol;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {

    @Test
    public void testDuke() throws PerformanceException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("duke");
        performer.perform();
    }
}

通过运行结果JUGGLING 15 BEANBAGS可以15被注入到了构造器中。

通过构造器注入对象引用

现在需要一个新的类PoeticJuggler,该类需要Poem类作为其参数并通过构造器注入。

package com.springinaction.springidol;

public class PoeticJuggler extends Juggler {
    private Poem poem;

    // 注入Poem
    public PoeticJuggler(Poem poem) {
        super();
        this.poem = poem;
    }

    // 注入豆袋子数量和Poem
    public PoeticJuggler(int beanBags, Poem poem) {
        super(beanBags);
        this.poem = poem;
    }

    @Override
    public void perform() throws PerformanceException {
        super.perform();
        System.out.println("While reciting...");
        poem.recite();
    }
}

在配置文件中就需要声明一个 Poem的类,并将其注入到PoeticJuggler中:

<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29"></bean>

<bean id="poeticDuke" class="com.springinaction.springidol.PoeticJuggler">
    <constructor-arg value="15" />
    <constructor-arg ref="sonnet29" />
</bean>

这里使用ref属性来将Id为sonnet29的Bean引用传递给构造器,当Spring遇到sonnet29和poeticDuke的bean声明时,所执行的逻辑脚本将是:

Poem sonnet29 = new Sonnet29();
Performer poeticDuke = new PoeticJuggler(15, sonnet29 );

通过工厂方法创建Bean

在没有公开的构造方法时,可以通过工厂方法来创建Bean,及<bean>元素的factory-method属性来装配工厂创建的Bean。
比如Stage是一个没有公开构造方法的类,但是可以通过getInstance获取其实例,那么可以通过下面的配置方式:

<bean id="theStage" class="com.springinaction.springidol.Stage"
    factory-method="getInstance" />

Bean的作用域

作用域 定义
singleton(默认) 在每一个Spring容器中,一个Bean定义只有一个对象实例
prototype 允许Bean的定义可以被实例化任意次(每次调用都创建一个实例)
request 在一次HTTP请求中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文(例如SpringMVC)中才有效
session 在一个HTTP Sesion中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文(例如SpringMVC)中才有效
global-session 在一个全局HTTP Sesion中,每个Bean定义对应一个实例。该作用域仅在Portlet上下文中才有效

配置方法,设置<bean>标签的scope属性:

<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29" scope="prototype"/>

Spring的单例只能保证在每个应用上下文中只有一个Bean的实例,你也可以通过定义多个<bean>的方式来实例化同一个Bean。

初始化和销毁Bean

可以为Bean定义初始化和销毁操作,只需使用init-methoddestroy-method参数来配置<bean>标签即可。

比如,舞台(Auditorium)需要在表演开始前开灯(turnOnLights),在结束时关灯(turnOffLights),那么就可以做下面的声明:

<bean id="auditorium" class="com.springinaction.springidol.Auditorium"
    init-method="turnOnLights" destroy-method="turnOffLights" />

默认的init-method和destroy-method

可以使用<beans>的default-init-methoddefault-destroy-method为上下文中所有的Bean设置共同的初始化和销毁方法。

注入Bean属性

Spring可以借助属性的set方法来配置属性的值,以实现setter方式的注入。

下面是一个音乐家(Instrumentalist)类,它演奏时需要歌曲(song)和乐器(instrument)两个属性。

package com.springinaction.springidol;

public class Instrumentalist implements Performer {

    private String song;

    private Instrument instrument;

    public Instrumentalist() {
    }

    public void perform() throws PerformanceException {
        System.out.print("Playing " + song + " : ");
        instrument.play();
    }

    public void setSong(String song) { // 注入歌曲
        this.song = song;
    }

    public String getSong() {
        return song;
    }

    public String screamSong() {
        return song;
    }

    public void setInstrument(Instrument instrument) { // 注入乐器
        this.instrument = instrument;
    }
}

配置文件中需要为Instrumentalist 注入这两个属性的值。

注入简单值

使用<property>标签可以通过调用属性的setter方法为Bean注入属性,而类似的<constructor-arg>是通过构造函数注入的。

<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
</bean>

<property>元素会指示Spring调用setSong()方法将song属性的值设置为"Happy"。

注意value的属性值可以指定数值型(int、float、Double等)以及boolean等,Spring在调用set方法前会自动根据类型进行转换。

引用其他Bean

在演奏家kenny中需要一个乐器,那么我们就可以为其引用一个实现了Instrument接口的乐器。

<bean id="saxphone" class="com.springinaction.springidol.Saxophone" />
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
    <property name="instrument" ref="saxphone"></property>
</bean>

通过Performer接口引用一个参赛者,就可以产生任意类型的参赛者进行表演,面向接口编程依赖注入实现了松耦合。

注入内部Bean

前文中,演奏家使用的instrument是其他Bean都可以进行共用的,若要独用一个类,那么可以声明一个类为内部Bean

<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
    <property name="instrument">
        <bean class="com.springinaction.springidol.Saxophone" />
    </property>
</bean>

如上面的代码,通过直接声明一个<bean>元素作为<property>元素的子节点。内部Bean不限于setter注入,也可以通过构造方法注入。

内部Bean没有Id属性,它们不能被复用,内部Bean仅适用于一次注入,而不能被其他Bean引用。

装配集合

集合元素 用途
<list> 装配list类型的数据,允许重复
<set> 装配set类型的数据,不允许重复
<map> 装配map类型的数据,key和value可以是任意类型
<props> 装配properties类型的数据,key和value必须是String类型

下面一个演奏家可以演奏多种乐器:

package com.springinaction.springidol;

import java.util.Collection;

public class OneManBand implements Performer {
    public OneManBand() {
    }

    public void perform() throws PerformanceException {
        // 遍历演奏各个乐器
        for (Instrument instrument : instruments) {
            instrument.play();
        }
    }

    private Collection<Instrument> instruments;

    public void setInstruments(Collection<Instrument> instruments) {// 注入instruments集合
        this.instruments = instruments;
    }
}

装配List、Set和Array

可以使用下面的方式装配List,也可以使用<set>来装配:

<bean id="hank" class="com.springinaction.springidol.OneManBand">
    <property name="instruments">
        <list>
            <ref bean="guitar"/>
            <ref bean="cymbal"/>
            <ref bean="harmonica"/>
        </list>
    </property>
</bean>

装配Map

当OneManBand表演时,perform()方法可以把乐器(instrument)的音符打印出来,我们还想知道每个音符是由哪个乐器产生的,因此需要做以下改变:

package com.springinaction.springidol;

import java.util.Map;

public class OneManBandMap implements Performer {
    public OneManBandMap() {
    }

    public void perform() throws PerformanceException {
        for (String key : instruments.keySet()) {
            System.out.print(key + " : ");
            Instrument instrument = instruments.get(key);
            instrument.play();
        }
    }

    private Map<String, Instrument> instruments;

    public void setInstruments(Map<String, Instrument> instruments) {// 以map类型注入instruments
        this.instruments = instruments;
    }
}

Spring配置map注入:

<bean id="hankk" class="com.springinaction.springidol.OneManBandMap">
    <property name="instruments">
        <map>
            <entry key="GUITAR" value-ref="guitar" />
            <entry key="CYMBAL" value-ref="cymbal" />
            <entry key="HARMONICA" value-ref="harmonica" />
        </map>
    </property>
</bean>

<map>中的<entry>元素由一个键和一个值组成,键和值可以是简单类型,也可以是其他Bean的引用:

属性 用途
key 指定map中entry的为String
key-ref 指定map中entry的为Spring山下文中其他Bean的引用
value 指定map中entry的为String
value-ref 指定map中entry的为Spring山下文中其他Bean的引用

装配Properties集合

若OneManBandMap中的Instrument属性所配置的Map的每一个entry的键和值都是String类型,可以使用java.util.Properties来代替Map:

<bean id="hank" class="com.springinaction.springidol.OneManBand">
    <property name="instruments">
        <props>
            <prop key="GUITAR">Strum Strum Strum</prop>
            <prop key="CYMBAL">Crush Crush Crush</prop>
            <prop key="HARMONICA">Hum Hum Hum</prop>
        </props>
    </property>
</bean>
  • <property>元素用于把值或者Bean引用注入到Bean的属性中;
  • <props>用于定义一个java.util.Properties类型的集合值;
  • <prop>用于定义<props>集合的一个成员

装配空值

<property name="someNonNullProperty"><null/></property>

使用表达式装配

如果为属性装配的值只有在运行期间才能获取,那该如何实现?
Spring表达式语言( Spring Expression Language , SpEL),可以通过运行期执行的表达式将值装配到Bean的属性或者构造器参数中,其特性有:

  • 使用Bean的id来引用Bean;
  • 调用方法和访问对象的属性;
  • 对值进行算术、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作。

SpEL基本用法

字面值

比如<property name="count" value="#{5}"/>#{ }标记会提示Spring这是个SpEL表达式。

可以与非SpEL表达式混用:<property name="message" value="The value is #{5}"/>

另外,浮点型、科学计数法、布尔型(true和false)也可以直接使用。

字符串使用时,需要用单引号或者双引号括起。

引用Bean、Properties和方法(避免空指针)

新声明一个id为carl的模仿者,Kenny唱什么他就唱什么:

<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="#{kenny.song}" />
</bean>

注入到Carl的song属性的表达式是由两部分组成的,第一部分(kenny)指向了kenny的Bean,第二部分(song)指向了kenny Bean的song属性,其实等价于下面的代码:

Instrumentalist carl = new Instrumentalist();
carl.setSong(kenny.getSong());

还可以调用其他Bean的方法:
<property name="song" value="#{songSelector.selectSong()}"/>
<property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>

如果selectSong()返回一个null,那么SpEL会抛出空指针异常,可以采用下面的方法避免:
<property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>
使用?.来代替.来访问toUpperCase()方法,访问之前会确保左边项不为null,若为null就不会再继续调用。

操作类

可以使用T()运算符调用类作用域的方法和常量。比如:
<property name="multiplier" value="#{T(java.lang.Math).PI}"/>
<property name="randomNumber" value="#{T(java.lang.Math).random()}"/>

在SpEL值上进行操作

运算符类型 运算符 例子
算术运算 +, -, *, /, %, ^ #{T(java.lang.Math).PI * circle.radius ^ 2}
关系运算 <, >, ==, <=, >=, lt,gt, eq, le, ge #{counter.total == 100}
逻辑运算 and, or, not(或!) #{!product.available}
条件运算 ?: (ternary), ?: (Elvis) #{m>=n?m:n}
正则表达式 matches #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.com'}

如果觉得有用,欢迎关注我的微信,有问题可以直接交流:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,803评论 6 342
  • 本章内容: 声明Bean 构造器注入和Setter方法注入 装配Bean 控制bean的创建和销毁 任何一个成功的...
    谢随安阅读 1,642评论 0 9
  • 文章作者:Tyan博客:noahsnail.com 3.4 依赖 标准企业应用不会由一个对象(或Spring用语中...
    SnailTyan阅读 1,179评论 0 1
  • 对你动了真情, 我的心湖再也无法平静。 欣赏着你美丽的姿容, 感受着你美好的心灵, 只想与你一起一生一世一心一意相...
    写给远方的她阅读 235评论 0 0