Spring-bean作用域scope詳解

livedba發表於2011-10-22

Spring Framework支援五種作用域(其中有三種只能用在基於web的Spring ApplicationContext)。

在每個Spring IoC容器中一個bean定義對應一個物件例項。

一個bean定義對應多個物件例項。

在一次HTTP請求中,一個bean定義對應一個例項;即每次HTTP請求將會有各自的bean例項,它們依據某個bean定義建立而成。該作用域僅在基於web的Spring ApplicationContext情形下有效。

在一個HTTP Session中,一個bean定義對應一個例項。該作用域僅在基於web的Spring ApplicationContext情形下有效。

在一個全域性的HTTP Session中,一個bean定義對應一個例項。典型情況下,僅在使用portlet context的時候有效。該作用域僅在基於web的Spring ApplicationContext情形下有效。

[@more@]

1.作用域

當一個bean的作用域為singleton, 那麼Spring IoC容器中只會存在一個共享的bean例項,並且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一例項。

換言之,當把一個bean定義設定為singlton作用域時,Spring IoC容器只會建立該bean定義的唯一例項。這個單一例項會被儲存到單例快取(singleton cache)中,並且所有針對該bean的後續請求和引用都將返回被快取的物件例項。

下圖演示了Springsingleton作用域。

請注意Springsingleton bean概念與“四人幫”(GoF)模式一書中定義的Singleton模式是完全不同的。經典的GoF Singleton模式中所謂的物件範圍是指在每一個ClassLoader中指定class建立的例項有且僅有一個。把Springsingleton作用域描述成一個container對應一個bean例項最為貼切。亦即,假如在單個Spring容器內定義了某個指定classbean,那麼Spring容器將會建立一個且僅有一個由該bean定義指定的類例項。

Singleton作用域是Spring中的預設作用域。

2.Prototype

Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程式的方式呼叫容器的getBean()方法)時都會建立一個新的bean例項。根據經驗,對所有有狀態的bean應該使用prototype作用域,而對無狀態的bean則應該使用singleton作用域。

下圖演示了Springprototype作用域。請注意,典型情況下,DAO不會被配置成prototype,因為一個典型的DAO不會持有任何會話狀態,因此應該使用singleton作用域。

對於prototype作用域的bean,有一點非常重要,那就是Spring不能對一個prototype bean的整個生命週期負責:容器在初始化、配置、裝飾或者是裝配完一個prototype例項後,將它交給客戶端,隨後就對該prototype例項不聞不問了。不管何種作用域,容器都會呼叫所有物件的初始化生命週期回撥方法,而對prototype而言,任何配置好的析構生命週期回撥方法都將不會被呼叫。清除prototype作用域的物件並釋放任何prototype bean所持有的昂貴資源,都是客戶端程式碼的職責。(讓Spring容器釋放被singleton作用域bean佔用資源的一種可行方式是,透過使用bean的後置處理器,該處理器持有要被清除的bean的引用。)

談及prototype作用域的bean時,在某些方面你可以將Spring容器的角色看作是Java new運算子的替代者。任何遲於該時間點的生命週期事宜都得交由客戶端來處理。在一節中會進一步講述Spring IoC容器中的bean生命週期。

向後相容性:在XML中指定生命週期作用域

如果你在bean定義檔案中引用'spring-beans.dtd' DTD,要顯式說明bean的生命週期作用域你必須使用"singleton"屬性(記住是預設的)。 如果引用的是'spring-beans-2.0.dtd' DTD或者是Spring 2.0 XSD schema,那麼需要使用"scope"屬性(因為"singleton"屬性被刪除了,新的DTDXSD檔案使用"scope"屬性)

簡單地說,如果你用"singleton"屬性那麼就必須在那個檔案裡引用'spring-beans.dtd' DTD。 如果你用"scope"屬性那麼必須 在那個檔案裡引用'spring-beans-2.0.dtd' DTD 'spring-beans-2.0.xsd' XSD

3. 其他作用域

其他作用域,即requestsession以及global session僅在基於web的應用中使用(不必關心你所採用的是什麼web應用框架)。

Note

下面介紹的作用域僅僅在使用基於webSpring ApplicationContext實現(如XmlWebApplicationContext)時有用。如果在普通的Spring IoC容器中,比如像XmlBeanFactoryClassPathXmlApplicationContext,嘗試使用這些作用域,你將會得到一個IllegalStateException異常(未知的bean作用域)。

3.1. 初始化web配置

要使用requestsessionglobal session作用域的bean(即具有web作用域的bean),在開始設定bean定義之前,還要做少量的初始配置。請注意,假如你只想要“常規的”作用域,也就是singletonprototype,就不需要這一額外的設定。

在目前的情況下,根據你的特定servlet環境,有多種方法來完成這一初始設定。如果你使用的是Servlet 2.4及以上的web容器,那麼你僅需要在web應用的XML宣告檔案web.xml中增加下述ContextListener即可

org.springframework.web.context.request.RequestContextListener

如果你用的是早期版本的web容器(Servlet 2.4以前),那麼你要使用一個javax.servlet.Filter的實現。請看下面的web.xml配置片段:

requestContextFilter

org.springframework.web.filter.RequestContextFilter

requestContextFilter

/*

3.2. Request作用域

考慮下面bean定義:

針對每次HTTP請求,Spring容器會根據loginAction bean定義建立一個全新的LoginAction bean例項,且該loginAction bean例項僅在當前HTTP request內有效,因此可以根據需要放心的更改所建例項的內部狀態,而其他請求中根據loginAction bean定義建立的例項,將不會看到這些特定於某個請求的狀態變化。當處理請求結束,request作用域的bean例項將被銷燬。

3.3. Session作用域

考慮下面bean定義:

針對某個HTTP SessionSpring容器會根據userPreferences bean定義建立一個全新的userPreferences bean例項,且該userPreferences bean僅在當前HTTP Session內有效。與request作用域一樣,你可以根據需要放心的更改所建立例項的內部狀態,而別的HTTP Session中根據userPreferences建立的例項,將不會看到這些特定於某個HTTP Session的狀態變化。當HTTP Session最終被廢棄的時候,在該HTTP Session作用域內的bean也會被廢棄掉。

3.4. global session作用域

考慮下面bean定義:

global session作用域類似於標準的HTTP Session作用域,不過它僅僅在基於portletweb應用中才有意義。Portlet規範定義了全域性Session的概念,它被所有構成某個portlet web應用的各種不同的portlet所共享。在global session作用域中定義的bean被限定於全域性portlet Session的生命週期範圍內。

請注意,假如你在編寫一個標準的基於Servletweb應用,並且定義了一個或多個具有global session作用域的bean,系統會使用標準的HTTP Session作用域,並且不會引起任何錯誤。

3.5. 作用域bean與依賴

能夠在HTTP request或者Session(甚至自定義)作用域中定義bean固然很好,但是Spring IoC容器除了管理物件(bean)的例項化,同時還負責協作者(或者叫依賴)的例項化。如果你打算將一個Http request範圍的bean注入到另一個bean中,那麼需要注入一個AOP代理來替代被注入的作用域bean。也就是說,你需要注入一個代理物件,該物件具有與被代理物件一樣的公共介面,而容器則可以足夠智慧的從相關作用域中(比如一個HTTP request)獲取到真實的目標物件,並把方法呼叫委派給實際的物件。

不能和作用域為singletonprototypebean一起使用。為singleton bean建立一個scoped proxy將丟擲BeanCreationException異常。

讓我們看一下將相關作用域bean作為依賴的配置,配置並不複雜(只有一行),但是理解“為何這麼做”以及“如何做”是很重要的。

XML配置檔案中,要建立一個作用域bean的代理,只需要在作用域bean定義裡插入一個子元素即可(你可能還需要在classpath裡包含CGLIB庫,這樣容器就能夠實現基於class的代理;還可能要使用基於XSD的配置)。上述XML配置展示了“如何做”,現在討論“為何這麼做”。在作用域為requestsession以及globalSessionbean定義裡,為什麼需要這個元素呢?下面我們從去掉元素的XML配置開始說起:

從上述配置中可以很明顯的看到singleton bean userManager被注入了一個指向HTTP Session作用域bean userPreferences的引用。singleton userManager bean會被容器僅例項化一次,並且其依賴(userPreferences bean)也僅被注入一次。這意味著,userManager在理論上只會操作同一個userPreferences物件,即原先被注入的那個bean。而注入一個HTTP Session作用域的bean作為依賴,有違我們的初衷。因為我們想要的只是一個userManager物件,在它進入一個HTTP Session生命週期時,我們希望去使用一個HTTP SessionuserPreferences物件。

當注入某種型別物件時,該物件實現了和UserPreferences類一樣的公共介面(即UserPreferences例項)。並且不論我們底層選擇了何種作用域機制(HTTP requestSession等等),容器都會足夠智慧的獲取到真正的UserPreferences物件,因此我們需要將該物件的代理注入到userManager bean, userManager bean並不會意識到它所持有的是一個指向UserPreferences引用的代理。在本例中,當UserManager例項呼叫了一個使用UserPreferences物件的方法時,實際呼叫的是代理物件的方法。隨後代理物件會從HTTP Session獲取真正的UserPreferences物件,並將方法呼叫委派給獲取到的實際的UserPreferences物件。

這就是為什麼當你將requestsession以及globalSession作用域bean注入到協作物件中時需要如下正確而完整的配置:Java程式碼

4自定義作用域

Spring 2.0中,Springbean作用域機制是可以擴充套件的。這意味著,你不僅可以使用Spring提供的預定義bean作用域; 還可以定義自己的作用域,甚至重新定義現有的作用域(不提倡這麼做,而且你不能覆蓋內建的singletonprototype作用域)。

作用域由介面org.springframework.beans.factory.config.Scope定義。要將你自己的自定義作用域整合到Spring容器中,需要實現該介面。它本身非常簡單,只有兩個方法,分別用於底層儲存機制獲取和刪除物件。自定義作用域可能超出了本參考手冊的討論範圍,但你可以參考一下Spring提供的Scope實現,以便於去如何著手編寫自己的Scope實現。

在實現一個或多個自定義Scope並測試透過之後,接下來就是如何讓Spring容器識別你的新作用域。ConfigurableBeanFactory介面宣告瞭給Spring容器註冊新Scope的主要方法。(大部分隨Spring一起釋出的BeanFactory具體實現類都實現了該介面);該介面的主要方法如下所示:

void registerScope(String scopeName, Scope scope);

registerScope(..)方法的第一個引數是與作用域相關的全域性唯一名稱;Spring容器中該名稱的範例有singletonprototyperegisterScope(..)方法的第二個引數是你打算註冊和使用的自定義Scope實現的一個例項。

假設你已經寫好了自己的自定義Scope實現,並且已經將其進行了註冊:

// note: the ThreadScope class does not exist; I made it up for the sake of this example

Scope customScope = new ThreadScope();

beanFactory.registerScope("thread", scope);

然後你就可以像下面這樣建立與自定義Scope的作用域規則相吻合的bean定義了:

如果你有自己的自定義Scope實現,你不僅可以採用程式設計的方式註冊自定義作用域,還可以使用BeanFactoryPostProcessor實現:CustomScopeConfigurer類,以宣告的方式註冊ScopeBeanFactoryPostProcessor介面是擴充套件Spring IoC容器的基本方法之一,在本章的中將會介紹。

使用CustomScopeConfigurer,以宣告方式註冊自定義Scope的方法如下所示:

Java程式碼既允許你指定實際的Class例項作為entry的值,也可以指定實際的Scope實現類例項;詳情請參見CustomScopeConfigurer類的JavaDoc

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25261409/viewspace-1055868/,如需轉載,請註明出處,否則將追究法律責任。

相關文章