10、高级装配2(spring笔记)

四、bean的作用域

默认情况下,spring应用上下文所有bean都是作为以单例的形式创建的。但是有时候,我们所使用的的类是易变的,因此重用是不安全的。spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

单例是默认的作用域,但是正如之前所述,对于易变类型,这并不合适,如果要选择其他作用域,可以使用@Scope注解,可以和@Component@Bean一起使用。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {...}

说明:当然我们可以这样使用@Scope("prototype"),但是上面那样更为安全且不易出错。如果要在XML中配置的话,可以这样:

<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />

说明:不管使用哪种方式来声明原型作用域,每次注入或从spring上下文中检索该bean的时候,都会创建新的实例,于是每次操作都有自己独有的Notepad实例。

4.1 使用会话和请求作用域

Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事。比如在电商网站上购买商品,就希望购物车在此会话中是共享的:

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, 
               proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){}

说明:

  • 上述作用域是会话,于是在一个购买商品的会话中,购物车是共享的。而@Scope同时还有一个proxyMode属性,其值为ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。

  • 假设要将ShoppingCart bean注入到单例StoreService beanSetter方法中:

public class StoreService {
    @Autowired
    public void setShoppingCart(ShoppingCart shoppingCart){
        this.shopingCart = shoppingCart;
    }
}

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建,此时,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中去,但是ShoppingCart bean是会话作用域,此时并不存在(此时没有发起会话)。同时,系统中每次会话就有一个ShoppingCart实例,我们也并不希望注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

  • 实际上Spring并不会将实际的ShoppingCart bean注入到StoreService中,而是会注入一个到ShoppingCart bean的代理,如图所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并调用委托给会话作用域内真正的ShoppingCart bean

    1

  • 如果ShoppingCart是接口的话,这是可以的。但是如果是一个具体的类,Spring就没有办法创建基于接口的代理了,需要使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来标明要以生成目标扩展的方式创建代理(而不是基于接口的代理扩展)。

4.2 在 XML 中声明作用域代理

XML中要设置代理模式,需要使用Spring aop命名空间的一个新元素:

<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
    <aop:scoped-proxy />
</bean>

说明:这种命名方式和@Scope注解的proxyMode属性功能相同的Spring XML配置元素。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
    <aop:scoped-proxy proxy-target-class="false" />
</bean>

当然要使用这种配置,必须在头部声明:

xmln:aop="http://www.springframework.org/shema/aop"

五、运行时值注入

前面也讲过,有时候需要将具体的值注入。但是有时候硬编码是可以的,但是有时候却又希望这些值在运行再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder
  • Spring表达式语言(SpEL

5.1 注入外部的值

Spring中,处理外部值的最简单方式就是声明属性源并通过SpringEnvironment来检索属性。下面看一个例子:

package com.soundsystem;
import ...;

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")//声明属性源
public class ExpressiveConfig {

    @Autowired
    Environment env;

    @Bean
    public BleankDisc disc(){
        return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));//检索属性值
    }
}

说明:@PropertySource中配置了名为app.properties的文件:

disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles

这个属性文件会加载到SpringEnvironment中,稍后可以从这里检索属性。于是就可以从中取得相关的 值了。

5.1.1 深入学习 Spring 的 Environment

Environment类中,上述的getProperty()方法并不是获取属性值的唯一方法,还有另外三个:

String getProperty(String key);
String getProperty(String key, String defaultValue);
T getProperty(String key, Class<T> type);
T getProperty(String key, Class<T> type, T defaultValue);

使用第二个方法可以为相关属性设置一个默认值,如果在配置文件中取不到相关值,则可以使用默认值:

@Bean
public BleankDisc disc(){
    return new BlankDisc(env.getProperty("disc.title", "Rattle and Hum"), 
                         env.getProperty("disc.artist", "U2"));//检索属性值
}

后面两种方法与前面两种类似,但是不会将所有的值都视为String类型。假如你想要获取的值所代表的含义是连接池中所维持的连接数量。如果从属性配置文件中得到的是一个String类型的值,那么在使用之前还要将其转换为Integer类型:

int connectCount = env.getProperty("db.connection.count", Integer.class, 30);

Environment还提供了几个与属性相关的方法,如果在使用getProperty()方法时还没有指定默认值,并且这个属性没有定义,获取到的值就是null。可以使用getRequiredProperty()方法指定某个属性必须要定义:

@Bean
public BleankDisc disc(){
    return new BlankDisc(env.getRequiredProperty("disc.title"), 
                         env.getRequiredProperty("disc.artist");//检索属性值
} 

如果上述两个属性值没有定义的话将会抛出IllegalStateException异常。如果想检查某个属性是否存在的话可以:

boolean titleExists = env.containsProperty("disc.title");

最后,如果想将属性解析为类的话,可以这样:

Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);

除了上述的这些方法,Environment还提供了一些方法来检查哪些profile处于激活状态:

String[] getActiveProfiles():返回激活profile名称的数组
String[] getDefaultProfiles():返回默认profile名称的数组
boolean acceptsProfiles(String ... profiles):如果Environment支持给定的profile,返回true

5.1.2 解析属性占位符

可以在XML中使用占位符按照如下方式解析BlankDisc构造函数参数:

<bean id="sgtPeppers" class="soundsystem.BlankDisc" 
        c:_title="${disc.title}"
        c:_artist="${disc.artist}" />

稍后会讨论这些属性是如何解析的。如果我们依赖组件扫描和自动装配来创建和初始化应用组件,那么就不需要指定占位符的配置文件或类了。

public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist){
    this.title = title;
    this.artist = artist;
}

为了使用占位符,必须配置一个PropertyPlaceholderConfigurer beanPropertySourcePlaceholderConfigurer bean。从Spring 3.1开始,推荐使用后者,因为它能够基于Spring Environment及其属性源来解析占位符。

@Bean
public static PropertySourcePlaceholderConfigurer placeholderConfigurer(){
    return new PropertySourcePlaceholderConfigurer();
}

而在XML文件应这样配置:

<context:property-palceholder />

当然必须在头中配置contextschema。以上就是两种解析占位符的方式。

5.2 使用 Spring 表达式语言进行装配

Spring表达式语言SpEL中有很多特性:

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

5.2.1 SpEL 样例

需要了解的第一件事就是SpEL表达式要放到"#{...}"之中,这和属性占位符类似。下面直接看例子:

#{1}

这就表示数字1

#{T(System).currentTimeMillis()}

最终结果是当前时刻的毫秒数。T()表示会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的方法。我们还可以引用其他的bean或其他bean的属性:

#{sgtPeppers.artist}

这表示引用IDsgtPeppers bean的属性。还可以通过systemProperties对象引用系统属性:

#{systemProperties['disc.title']}

如果将之前配置注入相关属性值应用上SpEL表达式,则就是这样:

public BlankDisc(@Value("#{systemProperties['disc.title']}") String title, 
                 @Value("#{systemProperties['disc.title']}") String artist){
    this.title = title;
    this.artist = artist;
}
<bean id="sgtPeppers" class="soundsystem.BlankDisc" 
        c:_title="#{systemProperties['disc.title']}"
        c:_artist="#{systemProperties['disc.title']}" />

5.2.2 表示字面值

#{3.14159}//浮点值
#{9.87E4}//科学计数法
#{'Hello'}//字符串
#{false}//布尔值

5.2.3 引用bean、属性和方法

#{sgtPeppers}//使用bean ID作为SpEL表达式
#{sgtPeppers.artist}//引用bean的属性artist
#{artistSelector.selectArtist()}//引用bean中的方法
#{artistSelector.selectArtist().toUpperCase()}//前一个方法返回String,则还可以继续调用方法
#{artistSelector.selectArtist()?.toUpperCase()}//先判断前一个方法是否返回String,如果是,则调用方法,否则返回null,不调用后续方法

5.2.4 在表达式中使用类型

如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符:

#{T(java.lang.Math)}
#{T(java.lang.Math).PI}
#{T(java.lang.Math).random()}

5.2.5 SpEL运算符

运算符类型 运算符
算术运算 +、-、*、/、%、^
比较运算 <、>、==、<=、>=、lt、gt、eq、le、ge
逻辑运算 and、or、not、逻辑或
条件运算 ?:(ternary)、?:(Elvis)
正则表达式 matches

5.2.6 计算正则表达式

#{admin.email matches '{[a-zA-Z0-9._%+-]+@[a-zA-Z0-9._]+\\.com}'}

5.2.7 计算集合

#{jukebox.songs[4].title}//引用集合中的一个元素
#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}//随机选取集合中的一个元素
#{'This is a test'[3]}//返回第四个位置的字符's'

5.2.8 查询运算符

#{jukebox.songs.?[artist eq 'Aerosmith']}//查询名字为Aerosmith的歌曲,返回一个集合
#{jukebox.songs.^[artist eq 'Aerosmith']}//查询集合中第一个artist属性为Aerosmith的歌曲
#{jukebox.songs.$[artist eq 'Aerosmith']}//查询集合中最后一个artist属性为Aerosmith的歌曲
#{jukebox.songs.![title]}//不想要歌曲对象的集合,而是所有歌曲名称的集合(没有artist属性的集合),返回一个新集合

注意:以上都是相关的值注入,而对于相关属性的注入是不同的,spring是没法自动解析如Date这种类型的,所以我们需要编写相关自定义的属性编辑器,请参看前面小节:《2.spring的IOC容器注入(spring笔记)》

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,796评论 6 342
  • 本章内容: Spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表...
    谢随安阅读 1,187评论 0 5
  • 文章作者:Tyan博客:noahsnail.com 3.4 依赖 标准企业应用不会由一个对象(或Spring用语中...
    SnailTyan阅读 1,175评论 0 1
  • 在我老家的房子左边有一杂草丛生的小山坡。山坡不高,仅有两三百米,坡上除了杂草和树木就是一条只可供一人行走的狭窄小路...
    ld熊壮壮阅读 259评论 0 0