Spring-bean作用域scope詳解
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情形下有效。
1.作用域
當一個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運算子的替代者。任何遲於該時間點的生命週期事宜都得交由客戶端來處理。在一節中會進一步講述Spring IoC容器中的bean生命週期。
向後相容性:在XML中指定生命週期作用域 | |
如果你在bean定義檔案中引用'spring-beans.dtd' DTD,要顯式說明bean的生命週期作用域你必須使用"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即可
如果你用的是早期版本的web容器(Servlet 2.4以前),那麼你要使用一個javax.servlet.Filter的實現。請看下面的web.xml配置片段:
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)獲取到真實的目標物件,並把方法呼叫委派給實際的物件。
讓我們看一下將相關作用域bean作為依賴的配置,配置並不複雜(只有一行),但是理解“為何這麼做”以及“如何做”是很重要的。
在XML配置檔案中,要建立一個作用域bean的代理,只需要在作用域bean定義裡插入一個
從上述配置中可以很明顯的看到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容器的基本方法之一,在本章的中將會介紹。
使用CustomScopeConfigurer,以宣告方式註冊自定義Scope的方法如下所示:
Java程式碼既允許你指定實際的Class例項作為entry的值,也可以指定實際的Scope實現類例項;詳情請參見CustomScopeConfigurer類的JavaDoc。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25261409/viewspace-1055868/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Maven中的dependency的scope作用域詳解Maven
- JavaScript作用域詳解JavaScript
- Python 作用域(scope) 和 LEGBPython
- Vue作用域插槽 :slot-scope 例項Vue
- SQL @@Identity ,Scope_identity() 作用域SQLIDE
- angular中$scope作用域和繼承關係解析Angular繼承
- 【Spring註解驅動開發】使用@Scope註解設定元件的作用域Spring元件
- angularjs的ng-repeat指令下的scope作用域AngularJS
- Python作用域詳述Python
- 詳解 JS 變數、作用域及記憶體JS變數記憶體
- JavaScript 變數、作用域及記憶體詳解JavaScript變數記憶體
- Spring:Bean的scope作用域案例講解以及Bean之間的依賴和繼承(3)SpringBean繼承
- Maven依賴scope範圍詳解Maven
- Maven依賴中的scope詳解Maven
- 圖解javascript作用域圖解JavaScript
- 擒賊先擒王,簡單談一下JavaScript作用域鏈(Scope Chain)JavaScriptAI
- JavaScript變數作用域(Variable Scope)和閉包(closure)的基礎知識JavaScript變數
- ES6深入學習(一)塊級作用域詳解
- 詳解Spring中Bean的作用域與生命週期SpringBean
- JavaScript:Scope(域)的基本指南JavaScript
- extern作用詳解
- Go 程式碼塊與作用域,變數遮蔽問題詳解Go變數
- maven中的scope標籤類別詳解Maven
- JavaScript 作用域 與 作用域鏈JavaScript
- js 作用域和作用域鏈JS
- js的作用域、作用域鏈JS
- js的作用域和作用域鏈JS
- javascript之作用域與作用域鏈JavaScript
- js的作用域與作用域鏈JS
- 作用域
- 詞法作用域和動態作用域
- java中static作用詳解Java
- ContextLoaderListener作用詳解Context
- [Vue] slot詳解,slot、slot-scope和v-slotVue
- 原型、原型鏈、作用域、作用域鏈、閉包原型
- 深入理解JavaScript作用域和作用域鏈JavaScript
- JS語法作用域與詞法作用域JS
- http跨域詳解HTTP跨域