10、高級(jí)裝配2(spring筆記)

四、bean的作用域

默認(rèn)情況下,spring應(yīng)用上下文所有bean都是作為以單例的形式創(chuàng)建的。但是有時(shí)候,我們所使用的的類是易變的,因此重用是不安全的。spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:

  • 單例(Singleton):在整個(gè)應(yīng)用中,只創(chuàng)建bean的一個(gè)實(shí)例
  • 原型(Prototype):每次注入或者通過Spring應(yīng)用上下文獲取的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。
  • 會(huì)話(Session):在Web應(yīng)用中,為每個(gè)會(huì)話創(chuàng)建一個(gè)bean實(shí)例。
  • 請(qǐng)求(Request):在Web應(yīng)用中,為每個(gè)請(qǐng)求創(chuàng)建一個(gè)bean實(shí)例。

單例是默認(rèn)的作用域,但是正如之前所述,對(duì)于易變類型,這并不合適,如果要選擇其他作用域,可以使用@Scope注解,可以和@Component@Bean一起使用。

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

說明:當(dāng)然我們可以這樣使用@Scope("prototype"),但是上面那樣更為安全且不易出錯(cuò)。如果要在XML中配置的話,可以這樣:

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

說明:不管使用哪種方式來聲明原型作用域,每次注入或從spring上下文中檢索該bean的時(shí)候,都會(huì)創(chuàng)建新的實(shí)例,于是每次操作都有自己獨(dú)有的Notepad實(shí)例。

4.1 使用會(huì)話和請(qǐng)求作用域

Web應(yīng)用中,如果能夠?qū)嵗跁?huì)話和請(qǐng)求范圍內(nèi)共享的bean,那將是非常有價(jià)值的事。比如在電商網(wǎng)站上購買商品,就希望購物車在此會(huì)話中是共享的:

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

說明:

  • 上述作用域是會(huì)話,于是在一個(gè)購買商品的會(huì)話中,購物車是共享的。而@Scope同時(shí)還有一個(gè)proxyMode屬性,其值為ScopedProxyMode.INTERFACES。這個(gè)屬性解決了將會(huì)話或請(qǐng)求作用域的bean注入到單例bean中所遇到的問題。

  • 假設(shè)要將ShoppingCart bean注入到單例StoreService beanSetter方法中:

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

因?yàn)?code>StoreService是一個(gè)單例的bean,會(huì)在Spring應(yīng)用上下文加載的時(shí)候創(chuàng)建,此時(shí),Spring會(huì)試圖將ShoppingCart bean注入到setShoppingCart()方法中去,但是ShoppingCart bean是會(huì)話作用域,此時(shí)并不存在(此時(shí)沒有發(fā)起會(huì)話)。同時(shí),系統(tǒng)中每次會(huì)話就有一個(gè)ShoppingCart實(shí)例,我們也并不希望注入某個(gè)固定的ShoppingCart實(shí)例到StoreService中。我們希望的是當(dāng)StoreService處理購物車功能時(shí),它所使用的ShoppingCart實(shí)例恰好是當(dāng)前會(huì)話所對(duì)應(yīng)的那一個(gè)。

  • 實(shí)際上Spring并不會(huì)將實(shí)際的ShoppingCart bean注入到StoreService中,而是會(huì)注入一個(gè)到ShoppingCart bean的代理,如圖所示。這個(gè)代理會(huì)暴露與ShoppingCart相同的方法,所以StoreService會(huì)認(rèn)為它就是一個(gè)購物車。但是,當(dāng)StoreService調(diào)用ShoppingCart的方法時(shí),代理會(huì)對(duì)其進(jìn)行懶解析并調(diào)用委托給會(huì)話作用域內(nèi)真正的ShoppingCart bean。

    1

  • 如果ShoppingCart是接口的話,這是可以的。但是如果是一個(gè)具體的類,Spring就沒有辦法創(chuàng)建基于接口的代理了,需要使用CGLib來生成基于類的代理。所以,如果bean類型是具體類的話,必須要將proxyMode屬性設(shè)置為ScopedProxyMode.TARGET_CLASS,以此來標(biāo)明要以生成目標(biāo)擴(kuò)展的方式創(chuàng)建代理(而不是基于接口的代理擴(kuò)展)。

4.2 在 XML 中聲明作用域代理

XML中要設(shè)置代理模式,需要使用Spring aop命名空間的一個(gè)新元素:

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

說明:這種命名方式和@Scope注解的proxyMode屬性功能相同的Spring XML配置元素。默認(rèn)情況下,它會(huì)使用CGLib創(chuàng)建目標(biāo)類的代理。但是我們也可以將proxy-target-class屬性設(shè)置為false,進(jìn)而要求它生成基于接口的代理:

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

當(dāng)然要使用這種配置,必須在頭部聲明:

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

五、運(yùn)行時(shí)值注入

前面也講過,有時(shí)候需要將具體的值注入。但是有時(shí)候硬編碼是可以的,但是有時(shí)候卻又希望這些值在運(yùn)行再確定。為了實(shí)現(xiàn)這些功能,Spring提供了兩種在運(yùn)行時(shí)求值的方式:

  • 屬性占位符(Property placeholder
  • Spring表達(dá)式語言(SpEL

5.1 注入外部的值

Spring中,處理外部值的最簡單方式就是聲明屬性源并通過SpringEnvironment來檢索屬性。下面看一個(gè)例子:

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

這個(gè)屬性文件會(huì)加載到SpringEnvironment中,稍后可以從這里檢索屬性。于是就可以從中取得相關(guān)的 值了。

5.1.1 深入學(xué)習(xí) Spring 的 Environment

Environment類中,上述的getProperty()方法并不是獲取屬性值的唯一方法,還有另外三個(gè):

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

使用第二個(gè)方法可以為相關(guān)屬性設(shè)置一個(gè)默認(rèn)值,如果在配置文件中取不到相關(guān)值,則可以使用默認(rèn)值:

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

后面兩種方法與前面兩種類似,但是不會(huì)將所有的值都視為String類型。假如你想要獲取的值所代表的含義是連接池中所維持的連接數(shù)量。如果從屬性配置文件中得到的是一個(gè)String類型的值,那么在使用之前還要將其轉(zhuǎn)換為Integer類型:

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

Environment還提供了幾個(gè)與屬性相關(guān)的方法,如果在使用getProperty()方法時(shí)還沒有指定默認(rèn)值,并且這個(gè)屬性沒有定義,獲取到的值就是null。可以使用getRequiredProperty()方法指定某個(gè)屬性必須要定義:

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

如果上述兩個(gè)屬性值沒有定義的話將會(huì)拋出IllegalStateException異常。如果想檢查某個(gè)屬性是否存在的話可以:

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

最后,如果想將屬性解析為類的話,可以這樣:

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

除了上述的這些方法,Environment還提供了一些方法來檢查哪些profile處于激活狀態(tài):

String[] getActiveProfiles():返回激活profile名稱的數(shù)組
String[] getDefaultProfiles():返回默認(rèn)profile名稱的數(shù)組
boolean acceptsProfiles(String ... profiles):如果Environment支持給定的profile,返回true

5.1.2 解析屬性占位符

可以在XML中使用占位符按照如下方式解析BlankDisc構(gòu)造函數(shù)參數(shù):

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

稍后會(huì)討論這些屬性是如何解析的。如果我們依賴組件掃描和自動(dòng)裝配來創(chuàng)建和初始化應(yīng)用組件,那么就不需要指定占位符的配置文件或類了。

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

為了使用占位符,必須配置一個(gè)PropertyPlaceholderConfigurer beanPropertySourcePlaceholderConfigurer bean。從Spring 3.1開始,推薦使用后者,因?yàn)樗軌蚧?code>Spring Environment及其屬性源來解析占位符。

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

而在XML文件應(yīng)這樣配置:

<context:property-palceholder />

當(dāng)然必須在頭中配置contextschema。以上就是兩種解析占位符的方式。

5.2 使用 Spring 表達(dá)式語言進(jìn)行裝配

Spring表達(dá)式語言SpEL中有很多特性:

  • 使用beanID來引用bean
  • 調(diào)用方法和訪問對(duì)象的屬性
  • 對(duì)值進(jìn)行算術(shù)、關(guān)系和邏輯運(yùn)算
  • 正則表達(dá)式匹配
  • 集合操作

5.2.1 SpEL 樣例

需要了解的第一件事就是SpEL表達(dá)式要放到"#{...}"之中,這和屬性占位符類似。下面直接看例子:

#{1}

這就表示數(shù)字1

#{T(System).currentTimeMillis()}

最終結(jié)果是當(dāng)前時(shí)刻的毫秒數(shù)。T()表示會(huì)將java.lang.System視為Java中對(duì)應(yīng)的類型,因此可以調(diào)用其static修飾的方法。我們還可以引用其他的bean或其他bean的屬性:

#{sgtPeppers.artist}

這表示引用IDsgtPeppers bean的屬性。還可以通過systemProperties對(duì)象引用系統(tǒng)屬性:

#{systemProperties['disc.title']}

如果將之前配置注入相關(guān)屬性值應(yīng)用上SpEL表達(dá)式,則就是這樣:

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}//浮點(diǎn)值
#{9.87E4}//科學(xué)計(jì)數(shù)法
#{'Hello'}//字符串
#{false}//布爾值

5.2.3 引用bean、屬性和方法

#{sgtPeppers}//使用bean ID作為SpEL表達(dá)式
#{sgtPeppers.artist}//引用bean的屬性artist
#{artistSelector.selectArtist()}//引用bean中的方法
#{artistSelector.selectArtist().toUpperCase()}//前一個(gè)方法返回String,則還可以繼續(xù)調(diào)用方法
#{artistSelector.selectArtist()?.toUpperCase()}//先判斷前一個(gè)方法是否返回String,如果是,則調(diào)用方法,否則返回null,不調(diào)用后續(xù)方法

5.2.4 在表達(dá)式中使用類型

如果要在SpEL中訪問類作用域的方法和常量的話,要依賴T()這個(gè)關(guān)鍵的運(yùn)算符:

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

5.2.5 SpEL運(yùn)算符

運(yùn)算符類型 運(yùn)算符
算術(shù)運(yùn)算 +、-、*、/、%、^
比較運(yùn)算 <、>、==、<=、>=、lt、gt、eq、le、ge
邏輯運(yùn)算 and、or、not、邏輯或
條件運(yùn)算 ?:(ternary)、?:(Elvis)
正則表達(dá)式 matches

5.2.6 計(jì)算正則表達(dá)式

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

5.2.7 計(jì)算集合

#{jukebox.songs[4].title}//引用集合中的一個(gè)元素
#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}//隨機(jī)選取集合中的一個(gè)元素
#{'This is a test'[3]}//返回第四個(gè)位置的字符's'

5.2.8 查詢運(yùn)算符

#{jukebox.songs.?[artist eq 'Aerosmith']}//查詢名字為Aerosmith的歌曲,返回一個(gè)集合
#{jukebox.songs.^[artist eq 'Aerosmith']}//查詢集合中第一個(gè)artist屬性為Aerosmith的歌曲
#{jukebox.songs.$[artist eq 'Aerosmith']}//查詢集合中最后一個(gè)artist屬性為Aerosmith的歌曲
#{jukebox.songs.![title]}//不想要歌曲對(duì)象的集合,而是所有歌曲名稱的集合(沒有artist屬性的集合),返回一個(gè)新集合

注意:以上都是相關(guān)的值注入,而對(duì)于相關(guān)屬性的注入是不同的,spring是沒法自動(dòng)解析如Date這種類型的,所以我們需要編寫相關(guān)自定義的屬性編輯器,請(qǐng)參看前面小節(jié):《2.spring的IOC容器注入(spring筆記)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,563評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評(píng)論 6 342
  • 本章內(nèi)容: Spring profile 條件化的bean聲明 自動(dòng)裝配與歧義性 bean的作用域 Spring表...
    謝隨安閱讀 1,259評(píng)論 0 5
  • 文章作者:Tyan博客:noahsnail.com 3.4 依賴 標(biāo)準(zhǔn)企業(yè)應(yīng)用不會(huì)由一個(gè)對(duì)象(或Spring用語中...
    SnailTyan閱讀 1,266評(píng)論 0 1
  • 在我老家的房子左邊有一雜草叢生的小山坡。山坡不高,僅有兩三百米,坡上除了雜草和樹木就是一條只可供一人行走的狹窄小路...
    ld熊壯壯閱讀 296評(píng)論 0 0

友情鏈接更多精彩內(nèi)容