Spring Framework支持五種作用域(其中有三種只能用在基于web的SpringApplicationContext)。
在每個Spring IoC容器中一個bean定義對應一個對象實例。
一個bean定義對應多個對象實例。
在一次HTTP請求中,一個bean定義對應一個實例;即每次HTTP請求將會有各自的bean實例,它們依據某個bean定義創建而成。該作用域僅在基于web的SpringApplicationContext情形下有效。
在一個HTTPSession中,一個bean定義對應一個實例。該作用域僅在基于web的SpringApplicationContext情形下有效。
在一個全局的HTTPSession中,一個bean定義對應一個實例。典型情況下,僅在使用portlet context的時候有效。該作用域僅在基于web的SpringApplicationContext情形下有效。
[@more@]
1.Singleton作用域
當一個bean的作用域為singleton,那么Spring IoC容器中只會存在一個共享的bean實例,并且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。
換言之,當把一個bean定義設置為singlton作用域時,Spring IoC容器只會創建該bean定義的唯一實例。這個單一實例會被存儲到單例緩存(singleton cache)中,并且所有針對該bean的后續請求和引用都將返回被緩存的對象實例。
下圖演示了Spring的singleton作用域。
請注意Spring的singleton bean概念與“四人幫”(GoF)模式一書中定義的Singleton模式是完全不同的。經典的GoF Singleton模式中所謂的對象范圍是指在每一個ClassLoader中指定class創建的實例有且僅有一個。把Spring的singleton作用域描述成一個container對應一個bean實例最為貼切。亦即,假如在單個Spring容器內定義了某個指定class的bean,那么Spring容器將會創建一個且僅有一個由該bean定義指定的類實例。
Singleton作用域是Spring中的缺省作用域。
2.Prototype
Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程序的方式調用容器的getBean()方法)時都會創建一個新的bean實例。根據經驗,對所有有狀態的bean應該使用prototype作用域,而對無狀態的bean則應該使用singleton作用域。
下圖演示了Spring的prototype作用域。請注意,典型情況下,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操作符的替代者。任何遲于該時間點的生命周期事宜都得交由客戶端來處理。在Section 3.5.1,“Lifecycle接口”一節中會進一步講述Spring IoC容器中的bean生命周期。
向后兼容性:在XML中指定生命周期作用域
如果你在bean定義文件中引用'spring-beans.dtd' DTD,要顯式說明bean的生命周期作用域你必須使用"singleton"屬性(記住singleton生命周期作用域是默認的)。 如果引用的是'spring-beans-2.0.dtd' DTD或者是Spring 2.0 XSD schema,那么需要使用"scope"屬性(因為"singleton"屬性被刪除了,新的DTD和XSD文件使用"scope"屬性)。
簡單地說,如果你用"singleton"屬性那么就必須在那個文件里引用'spring-beans.dtd' DTD。 如果你用"scope"屬性那么必須 在那個文件里引用'spring-beans-2.0.dtd' DTD或'spring-beans-2.0.xsd' XSD。
3.其他作用域
其他作用域,即request、session以及global session僅在基于web的應用中使用(不必關心你所采用的是什么web應用框架)。
Note
下面介紹的作用域僅僅在使用基于web的Spring ApplicationContext實現(如XmlWebApplicationContext)時有用。如果在普通的Spring IoC容器中,比如像XmlBeanFactory或ClassPathXmlApplicationContext,嘗試使用這些作用域,你將會得到一個IllegalStateException異常(未知的bean作用域)。
3.1.初始化web配置
要使用request、session和global session作用域的bean(即具有web作用域的bean),在開始設置bean定義之前,還要做少量的初始配置。請注意,假如你只想要“常規的”作用域,也就是singleton和prototype,就不需要這一額外的設置。
在目前的情況下,根據你的特定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 Session,Spring容器會根據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作用域,不過它僅僅在基于portlet的web應用中才有意義。Portlet規范定義了全局Session的概念,它被所有構成某個portlet web應用的各種不同的portlet所共享。在global session作用域中定義的bean被限定于全局portlet Session的生命周期范圍內。
請注意,假如你在編寫一個標準的基于Servlet的web應用,并且定義了一個或多個具有global session作用域的bean,系統會使用標準的HTTP Session作用域,并且不會引起任何錯誤。
3.5.作用域bean與依賴
能夠在HTTP request或者Session(甚至自定義)作用域中定義bean固然很好,但是Spring IoC容器除了管理對象(bean)的實例化,同時還負責協作者(或者叫依賴)的實例化。如果你打算將一個Http request范圍的bean注入到另一個bean中,那么需要注入一個AOP代理來替代被注入的作用域bean。也就是說,你需要注入一個代理對象,該對象具有與被代理對象一樣的公共接口,而容器則可以足夠智能的從相關作用域中(比如一個HTTP request)獲取到真實的目標對象,并把方法調用委派給實際的對象。
不能和作用域為singleton或prototype的bean一起使用。為singleton bean創建一個scoped proxy將拋出BeanCreationException異常。
讓我們看一下將相關作用域bean作為依賴的配置,配置并不復雜(只有一行),但是理解“為何這么做”以及“如何做”是很重要的。
在XML配置文件中,要創建一個作用域bean的代理,只需要在作用域bean定義里插入一個子元素即可(你可能還需要在classpath里包含CGLIB庫,這樣容器就能夠實現基于class的代理;還可能要使用基于XSD的配置)。上述XML配置展示了“如何做”,現在討論“為何這么做”。在作用域為request、session以及globalSession的bean定義里,為什么需要這個元素呢?下面我們從去掉元素的XML配置開始說起:
從上述配置中可以很明顯的看到singleton bean userManager被注入了一個指向HTTP Session作用域bean userPreferences的引用。singleton userManager bean會被容器僅實例化一次,并且其依賴(userPreferences bean)也僅被注入一次。這意味著,userManager在理論上只會操作同一個userPreferences對象,即原先被注入的那個bean。而注入一個HTTP Session作用域的bean作為依賴,有違我們的初衷。因為我們想要的只是一個userManager對象,在它進入一個HTTP Session生命周期時,我們希望去使用一個HTTP Session的userPreferences對象。
當注入某種類型對象時,該對象實現了和UserPreferences類一樣的公共接口(即UserPreferences實例)。并且不論我們底層選擇了何種作用域機制(HTTP request、Session等等),容器都會足夠智能的獲取到真正的UserPreferences對象,因此我們需要將該對象的代理注入到userManager bean中,而userManager bean并不會意識到它所持有的是一個指向UserPreferences引用的代理。在本例中,當UserManager實例調用了一個使用UserPreferences對象的方法時,實際調用的是代理對象的方法。隨后代理對象會從HTTP Session獲取真正的UserPreferences對象,并將方法調用委派給獲取到的實際的UserPreferences對象。
這就是為什么當你將request、session以及globalSession作用域bean注入到協作對象中時需要如下正確而完整的配置:Java代碼
4自定義作用域
在Spring 2.0中,Spring的bean作用域機制是可以擴展的。這意味著,你不僅可以使用Spring提供的預定義bean作用域; 還可以定義自己的作用域,甚至重新定義現有的作用域(不提倡這么做,而且你不能覆蓋內置的singleton和prototype作用域)。
作用域由接口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容器中該名稱的范例有singleton和prototype。registerScope(..)方法的第二個參數是你打算注冊和使用的自定義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類,以聲明的方式注冊Scope。BeanFactoryPostProcessor接口是擴展Spring IoC容器的基本方法之一,在本章的BeanFactoryPostProcessor中將會介紹。
使用CustomScopeConfigurer,以聲明方式注冊自定義Scope的方法如下所示:
Java代碼既允許你指定實際的Class實例作為entry的值,也可以指定實際的Scope實現類實例;詳情請參見CustomScopeConfigurer類的JavaDoc