Spring Framework支持五種作用域(其中有三種只能用在基于web的SpringApplicationContext)。
在每個Spring IoC容器中一個bean定義對應一個對象實例倦青。
一個bean定義對應多個對象實例瓮床。
在一次HTTP請求中,一個bean定義對應一個實例姨夹;即每次HTTP請求將會有各自的bean實例纤垂,它們依據某個bean定義創(chuàng)建而成矾策。該作用域僅在基于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容器只會創(chuàng)建該bean定義的唯一實例旺聚。這個單一實例會被存儲到單例緩存(singleton cache)中,并且所有針對該bean的后續(xù)請求和引用都將返回被緩存的對象實例眶蕉。
下圖演示了Spring的singleton作用域砰粹。
請注意Spring的singleton bean概念與“四人幫”(GoF)模式一書中定義的Singleton模式是完全不同的。經典的GoF Singleton模式中所謂的對象范圍是指在每一個ClassLoader中指定class創(chuàng)建的實例有且僅有一個造挽。把Spring的singleton作用域描述成一個container對應一個bean實例最為貼切碱璃。亦即弄痹,假如在單個Spring容器內定義了某個指定class的bean,那么Spring容器將會創(chuàng)建一個且僅有一個由該bean定義指定的類實例嵌器。
Singleton作用域是Spring中的缺省作用域界酒。
2.Prototype
Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程序的方式調用容器的getBean()方法)時都會創(chuàng)建一個新的bean實例嘴秸。根據經驗毁欣,對所有有狀態(tài)的bean應該使用prototype作用域,而對無狀態(tài)的bean則應該使用singleton作用域岳掐。
下圖演示了Spring的prototype作用域凭疮。請注意,典型情況下串述,DAO不會被配置成prototype执解,因為一個典型的DAO不會持有任何會話狀態(tài),因此應該使用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接口”一節(jié)中會進一步講述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定義之前,還要做少量的初始配置衔掸。請注意烫幕,假如你只想要“常規(guī)的”作用域,也就是singleton和prototype敞映,就不需要這一額外的設置较曼。
在目前的情況下,根據你的特定servlet環(huán)境驱显,有多種方法來完成這一初始設置诗芜。如果你使用的是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定義創(chuàng)建一個全新的LoginAction bean實例栓霜,且該loginAction bean實例僅在當前HTTP request內有效,因此可以根據需要放心的更改所建實例的內部狀態(tài)横蜒,而其他請求中根據loginAction bean定義創(chuàng)建的實例胳蛮,將不會看到這些特定于某個請求的狀態(tài)變化。當處理請求結束丛晌,request作用域的bean實例將被銷毀仅炊。
3.3. Session作用域
考慮下面bean定義:
針對某個HTTP Session,Spring容器會根據userPreferences bean定義創(chuàng)建一個全新的userPreferences bean實例澎蛛,且該userPreferences bean僅在當前HTTP Session內有效抚垄。與request作用域一樣,你可以根據需要放心的更改所創(chuàng)建實例的內部狀態(tài),而別的HTTP Session中根據userPreferences創(chuàng)建的實例呆馁,將不會看到這些特定于某個HTTP Session的狀態(tài)變化桐经。當HTTP Session最終被廢棄的時候,在該HTTP Session作用域內的bean也會被廢棄掉浙滤。
3.4. global session作用域
考慮下面bean定義:
global session作用域類似于標準的HTTP Session作用域阴挣,不過它僅僅在基于portlet的web應用中才有意義。Portlet規(guī)范定義了全局Session的概念纺腊,它被所有構成某個portlet web應用的各種不同的portlet所共享畔咧。在global session作用域中定義的bean被限定于全局portlet Session的生命周期范圍內。
請注意揖膜,假如你在編寫一個標準的基于Servlet的web應用盒卸,并且定義了一個或多個具有global session作用域的bean,系統(tǒng)會使用標準的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創(chuàng)建一個scoped proxy將拋出BeanCreationException異常炭菌。
讓我們看一下將相關作用域bean作為依賴的配置,配置并不復雜(只有一行)逛漫,但是理解“為何這么做”以及“如何做”是很重要的黑低。
在XML配置文件中,要創(chuàng)建一個作用域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一起發(fā)布的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);
然后你就可以像下面這樣創(chuàng)建與自定義Scope的作用域規(guī)則相吻合的bean定義了:
如果你有自己的自定義Scope實現欺缘,你不僅可以采用編程的方式注冊自定義作用域,還可以使用BeanFactoryPostProcessor實現:CustomScopeConfigurer類挤安,以聲明的方式注冊Scope谚殊。BeanFactoryPostProcessor接口是擴展Spring IoC容器的基本方法之一,在本章的BeanFactoryPostProcessor中將會介紹蛤铜。
使用CustomScopeConfigurer嫩絮,以聲明方式注冊自定義Scope的方法如下所示:
Java代碼既允許你指定實際的Class實例作為entry的值,也可以指定實際的Scope實現類實例围肥;詳情請參見CustomScopeConfigurer類的JavaDoc