SpringBean作用域——基本作用域與web作用域
Spring的 bean有5種作用域分別是:singleton、prototype、request、session和globalSession(不常用)。
其中後三種request、session、global session專用於Web應用程式。
1、singleton 單例
在Spring裡,通過容器建立的物件預設是singleton單例(這裡要注意的是singleton作用域和GOF設計模式中的單例是不同的)
單例就是在整個容器的生命週期,只會存在一個共享的bean例項。
下面是文件上的圖:
可如下配置例項:
<bean id="order" class="twm.spring.start.Order" scope="singleton" />
或者
<bean id="order" class="twm.spring.start.Order" singleton="true" />
或者什麼都不寫,預設就是單例
<bean id="order" class="twm.spring.start.Order" />
2、prototype 多例
如果某個bean被標記為多例,則每次請求使用該物件時,都會建立一個新的bean例項。比如將這個bean注入到另一個bean中,或者在程式中呼叫getBean(“beanid”)方法,都會觸發生成一個新的bean例項,相當於new的操作。
下面是文件上的圖:
Spring容器會對bean 的生命週期負責。容器會呼叫建構函式初始化bean,呼叫解構函式清理釋放bean。
但是對於prototype作用域的bean,Spring容器就不會整個生命週期負責了。在初始化完成後,就交給呼叫它的程式,析構釋放都由呼叫程式負責了。
配置例項:
<bean id="order" class="twm.spring.start.Order" scope="prototype" />
或者
<bean id="order" class="twm.spring.start.Order" singleton="false" />
要注意singleton 依賴prototype的情況
singleton 類依賴了prototype類,容器會在singleton 類初始化就會根據依賴關係將prototype類注入。以後的每一次呼叫singleton bean都是同一個物件,裡面的prototype bean也是最初注入的那個,容器再也不會為singleton bean產生新的prototype bean。
要想在singleton bean中每次呼叫時都產生新的prototype bean,可使用代理。
如果想把一個web作用域的Bean注入到另一個週期長的作用域的Bean中(比如單例的bean),就需要選擇注入一個AOP代理來替換這個web作用域Bean。在定義時,web作用域的Bean都需要宣告使用代理模式:配置中加上<aop:scoped-proxy/>元素
官方文件的例子:
<!-- 一個HTTP session作用域的Bean 作為代理暴露出去 -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!--指示容器代理這個Bean -->
<aop:scoped-proxy/>
</bean>
<!--一個單例Bean注入一個代理Bean -->
<bean id="userManager" class="com.foo.UserManager">
<!-- 實際使用的是userPreferences的代理物件 -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
為什麼要這樣做?
上例中,HTTP Session作用域的userPreferences bean注入給單例userManger bean。因為userManager bean是單例的,即每個容器只會例項化一個,所以它的依賴物件userPreferences bean也僅會注入一次。這樣下來,userManagerbean只能操作相同的userPreferences物件,就是初始化時注入的那一個。
將一個短生命週期作用域bean注入給長生命週期作用域bean都會存在此類問題。
我們期望的是userManager物件中的userPreferences beans與session同生命週期和同作用域。
在userPreferences bean配置中加入<aop:scoped-proxy/>後,容器將建立一個代理物件,該物件擁有和UserPreferences完全相同的public介面並暴露。代理物件每次呼叫時會從 Session範圍內獲取真正的UserPreferences物件,而userManager類卻不知道。
3、Request
對於每次HTTP請求,使用request定義的Bean都將產生一個新例項,每個HTTP request中的例項都是獨立互不影響的的。
比如下面bean定義:
<bean id="userrole" class="twm.demo.UserRole" scope="request" />
針對每次HTTP請求,Spring容器會建立一個全新的twm.demo.UserRole bean例項userrole bean, 且userrole bean僅在當前HTTP request內有效。
因此當request A請求建立並更改了userrole bean的內部狀態,request B請求中建立的userrole bean,並不會有相應的這些狀態變化。
當處理請求結束,request作用域的bean例項將被銷燬。
4、Session
和Request很類似,對於每個HTTP Session,使用session定義的Bean都將產生一個新例項。每個HTTP Session中的例項都是獨立互不影響的的。當HTTP Session最終被廢棄的時候,在該HTTP Session作用域內的bean也會被廢棄掉。
這裡重點要明白request 作用域 和session作用域的差別:
一個http session中可以有多個request
簡單說就是,使用者A訪問同一網站的PageA和PageB,這就有兩個http request,但是隻有一個http session
使用者A,B,C三人同時訪問這個網站的PageA,這就有三個http request,同時也有三個 http session
5、globalSession
global session作用域類似於標準的HTTP Session作用域,不過它僅僅在基於portlet的web應用中才有意義。
以上web作用域只有在spring web ApplicationContext的實現中(比如XmlWebApplicationContext)才會起作用,若在常規Spring IoC容器中使用,比如ClassPathXmlApplicationContext中,就會收到一個異常IllegalStateException來告訴你不能識別的bean作用域
初始化web配置
為了支援request,sesssion,global session這種級別bean的作用域(web作用域bean),在定義bean之前需要一些初始化的小配置。
目的是為了使Spring捕獲到相應的事件(如request請求開始,request請求結束,session會話開始,sesssion會話結束等。)
這裡分兩種情況:
1、 SpringMVC:
若使用 SpringMVC訪問這些作用域bean,實際上是使用Srping DispatcherServlet類或者DispatcherPortlet類處理request,則無需特別配置。因為DispatcherServlet 和 DispatcherPortlet已經暴露了所有的相關狀態。
2、非Spring的DispacherServlet
若使用了非Spring的DispacherServlet處理請求,比如Struts,則需要註冊:
2.1、Servlet 2.4及以上的web容器,需要在web.xml中增加監聽器:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
2.2、Servlet2.4以前的web容器,在web.xml中增加過濾器:
<web-app>
..
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet,RequestContextListener,RequestContextFilter都是做相同的事兒,也就是繫結HTTPrequest物件到服務的Thread執行緒中,並開啟接下來 用到的session-scoped功能。