1. 程式人生 > >SpringBean作用域——基本作用域與web作用域

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功能。