天天看點

spring:Bean作用域

在配置檔案中定義Bean時,使用者不但可以配置Bean的屬性值及互相之間的依賴關系,還可以定義Bean的作用域。作用域将對Bean的生命周期和建立方式産生影響。

spring 4.0中所支援的作用域:

① singleton:

在Spring IoC容器中僅存在一個Bean執行個體,Bean以單例的方式存在。

② prototype:

每次從容器中調用Bean時,都傳回一個新的執行個體,即每次調用getBean()時相當于執行new xxxBean()操作。

③ request:

每次HTTP請求都會建立一個新的Bean。該作用域僅适用于WebApplicationContext環境。

④ session:

同一個HTTP session共享一個Bean,該作用域僅适用于WebApplicationContext環境。

⑤ globalSession:

同一個全局session共享一個Bean,一般用于Portlet應用環境。該作用域僅适用于WebApplicationContext環境。

在低版本的Spring中,僅支援兩個Bean的作用域,是以采用singleton="true|false"的配置方式。Spring為了向後相容,依然支援這種方式。不過Spring推薦采用新的配置方式<bean id="xxx" scope="作用域類型">。

除了以上5中預定義的Bean作用域以外,Spring還允許使用者自定義Bean的作用域。可以通過org.springframework.beans.factory.config.CustomScopeConfigurer這個BeanFactoryPostProcessor注冊自定義的Bean作用域。感興趣的可以自行閱讀Scope接口的javadoc文檔。

1 singleton作用域

單例模式是最重要的設計模式之一。在傳統的應用開發中,需要手工為每個單執行個體類編寫特定代碼,在這種情況下,類的業務邏輯代碼和模式代碼緊密耦合在一起。Spring以容器的方式提供天然的單例模式功能,任何POJO無需編寫特殊的代碼,僅通過配置就可以使用單例模式。

一般情況下,無狀态或者狀态不可變的類适合使用單例模式,不過Spring對此實作了超越。在傳統開發中,由于DAO類持有Connection這個非線程安全的變量,是以往往未采用單例模式。而在Spring中,所有的DAO類都可以采用單例模式,因為Spring利用AOP和LocalThread功能,對非線程安全的變量進行了特殊處理,使這些非線程安全的類變成了線程安全的類。

因為Spring的這一超越,是以在實際應用中大部分Bean都能以單例的方式運作,這也是為什麼Spring将Bean的預設作用域定位singleton的原因。

singleton的Bean在同一Spring IoC容器中隻有一個執行個體。

不但在配置檔案中通過bean的屬性注入的bean引用是單例的,任何通過容器的getBean("beanId")方法擷取的執行個體也指向同一個Bean。

在預設的情況下,Spring的ApplicationContext容器在啟動時,自動執行個體化所有singleton的Bean并緩存于容器中。雖然啟動時會花費一些時間,但它帶來兩個好處:首先,對Bean提前進行執行個體化操作會及早發現一些潛在的配置問題;其次,Bean以緩存的方式儲存,當運作時用到該Bean時就無需在執行個體化了,提高了運作的效率。如果使用者不希望在容器啟動時提前執行個體化singleton的Bean,則可以通過lazy-init屬性進行控制。

lazy-init="true" 的Bean在某些情況下依然會提前執行個體化:如果該Bean被其他需要提前執行個體化的Bean所引用,那麼Spring将會忽略延遲執行個體化的設定。

2 prototype作用域

采用scope="prototype"指定非單例作用域的Bean,每次通過容器的getBean("beanId")方法傳回的都是一個新的執行個體。

在預設情況下,Spring容器在啟動時不執行個體化prototype的Bean。此外,Spring容器将prototype的Bean交給調用者後,就不在管理它的生命周期。

3 與Web應用環境相關的Bean作用域

如果使用者使用Spring的WebApplicationContext,則可以使用另外的3種Bean的作用域:request、session、globalSession。不過在使用這些作用域之前,首先要在Web容器中進行一些額外的配置。

① 在web.xml中添加HTTP請求監聽器進行配置:

<web-app>
	<listener>
		<listener-class>
			org.springframework.web.context.request.RequestContextListener
		</listener-class>
	</listener>
</web-app>
           

大家可能會發現之前WebApplicationContext初始化時,已經通過ContextLoaderListener或ContextLoaderServlet将Web容器與Spring容器進行了整合,為什麼在這裡又要額外的引入RequestContextListener以支援Bean的另外3個作用域呢?

在整合Spring容器時使用ContextLoaderListener,它實作了ServletContextListener監聽器接口,ServletContextListener隻負責監聽Web容器啟動和關閉事件。而RequestContextListener實作了ServletRequestListener監聽器接口,該監聽器監聽了HTTP請求事件,Web伺服器接收的每一次請求都會通知該監聽器。

Spring容器啟動和關閉操作由Web容器的啟動和關閉事件觸發,但如果Spring容器中的Bean需要request、session和globaalSession作用域的支援,Spring容器本身就必須獲得Web容器的HTTP請求事件,以HTTP請求事件驅動Bean作用域的控制邏輯。也就是說通過配置RequestContextListener,Spring容器和Web容器的結合更加密切,Spring容器對Web容器就可以實施相應的Bean作用域控制了。

② request作用域

request作用域的Bean對應一個HTTP請求和聲明周期:scope="request" 。

這樣,每次HTTP請求調用的Bean,Spring容器都會建立一個新的Bean,請求處理完畢後,就會銷毀這個Bean。

③ session作用域

當Bean的作用域為session作用域時,此Bean的作用域在整個HTTP Session中的所有HTTP請求都共享一個Bean。當HTTP Session結束後,Bean被銷毀:scope="session"。

④ globalSession作用域

globalSession作用域類似于session作用域,不過僅在Portlet的Web應用中使用。Portlet規範定義了全局Session的概念,它被組成PortletWeb應用的所有子Portlet共享。如果不在PortletWeb應用華靜霞,那麼globalSession作用域就等價于session作用域。

4 作用域依賴問題

假設将Web相關作用域的Bean注入singleton或prototype的Bean中,我們當然希望它能夠按照預定的方式工作,即引用者應該從指定的域中取得它的引用。但如果沒有進行一些額外的配置,那麼它的運作不會按照我們所期望的步驟進行:

① 引入aop schema  在beans标簽中添加:

xmlns:aop="http://www.springframework.org/schema/aop"
           

② 聲明一個request作用域的bean,并在bean中建立代理

<bean name="xxx" class="xxx.xxx" scope="request">
    <aop:scoped-proxy/>
</bean>
           

③ 引用request作用域的bean:

<bean id="x" class="x.x">
    <property name="xxx" ref="xxx" />
</bean>
           

在上述例子中一個singleton作用域的bean引用了一個request作用域的bean。為了singleton的作用域的bean能擷取到request作用域的bean,需要使用Spring AOP的文法為request作用域的bean配置一個代理類,為了能夠在配置檔案中使用AOP的配置标簽,則需要在beans中聲明中定義aop命名空間。

當singleton作用域的Bean在Web環境下調用request作用域的Bean時,Spring AOP将啟動動态代理判斷目前Bean位于哪個HTTP請求線程中,并從對應的HTTP請求線程域中擷取對應的request作用域的Bean。

反過來說,在配置檔案中添加<aop:scoped-proxy/>後,注入的Bean已經不是在配置檔案中聲明的Bean對象了,而是通過動态代理擷取的Bean的執行個體。

Spring在動态代理類中加入一段邏輯,以判斷目前的request作用域的Bean需要取得那個HTTP請求相關的Bean,首先判斷目前request作用域的Bean在哪個線程中,然後根據這個線程擷取對應的HttpRequest對象,在使用HttpRequest域中擷取對應的request作用域的bean。因為Web容器的特性,一般情況下,一個HTTP請求對應一個獨立的線程。

Java語言隻能對接口提供自動代理,是以,如果需要對類提供代理,則需要在類路徑中加入CGLib的類庫,這時Spring将使用CGLib為類生成動态代理的子類。