Spring Scope:Web項目中如何安全使用有狀態的Bean對象?


  Web系統是最常見的Java應用系統之一,現在流行的Web項目多使用ssm或ssh框架,使用spring進行bean的管理,這為我們編寫web項目帶來了很多方便,通常,我們的controler層使用注入的service層的bean對象,service層使用注入的dao層的bean對象。但是大家在使用的時候有沒有思考這樣一個問題:如果有兩個一樣的請求進入我們的系統,那么他們使用的spring注入的service層的bean對象和dao層的bean對象,是同一個對象嗎?

  這個問題的答案就要講到bean的作用域了,在spring2.0之前bean只有2種作用域即:singleton(單例)、non-singleton(也稱 prototype), Spring2.0以后,增加了session、request、global session三種專用於Web應用程序上下文的Bean。singleton是默認的作用域,當一個bean的 作用域設置為singleton, 那么Spring IOC容器中只會存在一個共享的bean實例,並且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。而prototype作用域部署的bean,每一次請求(將其注入到另一個bean中,或者以程序的方式調用容器的 getBean()方法)都會產生一個新的bean實例,相當與一個new的操作。我以前沒有關注過這個問題,是因為我在項目中通常使用的bean,無論是service層的,還是dao層的,都是沒有狀態的bean,里面只有方法,沒有成員變量。在使用這樣的bean的時候,多個線程訪問同一個bean不會產生線程安全問題。

  而當我們需要使用有狀態的bean的時候,這個bean中具有可變的成員變量,不同的特定用戶使用的bean具有不同的狀態(成員變量的值),這些狀態不在整個Web工程中通用。當多線程訪問bean修改成員變量時,就會產生與預期不符的結果。

  舉個例子,下面自定義了一個bean,使用注解的方式進行注冊,其中只有一個String的param參數:  

@Component
public
class RequestInfo { private String param; public String getParam() { return param; } public void setParam(String param) { this.param = param; } }

  然后定義兩個java類(我使用的webx框架進行的測試,功能類似與struts2,讀者可自行編寫類似的兩個請求及對應java類),一個將url中攜帶的param參數值存放到RequestInfo對象中:

public class SetParamValve implements Valve {
    
    @Autowired
    private HttpServletRequest  request;
    
    @Autowired
    private RequestInfo requestInfo;

    @Override
    public void invoke(PipelineContext pipelineContext) throws Exception {
        
        System.out.println("SetParamValve--now param is:" + requestInfo.getParam());
        
        //獲取請求中的參數值
        String param = HttpUtil.getRequestParameter(request, "param");
        
     //存放參數 requestInfo.setParam(param);
//睡眠10s TimeUnit.SECONDS.sleep(10); pipelineContext.invokeNext(); } }

  一個取出RequestInfo對象中存放的參數值:

public class GetParamValve implements Valve {
        
    @Autowired
    private RequestInfo requestInfo;

    @Override
    public void invoke(PipelineContext pipelineContext) throws Exception {
        
        System.out.println("GetParamValve--now param is:" + requestInfo.getParam());
        
        pipelineContext.invokeNext();
        
    }

}

  

  下面開始實驗:當一個請求url:”http://localhost:8080/index.htm?param=a“進行訪問時,控制台打印:

  

  這是符合預期輸出的,我們再使用兩個url:”http://localhost:8080/index.htm?param=a“ 和 ”http://localhost:8080/index.htm?param=b“ 在同一瀏覽器先后進行請求,打印:

  

  可以看到,GetParamValve輸出了兩個”b“,而我們的預期是:”http://localhost:8080/index.htm?param=a“的請求打印a,”http://localhost:8080/index.htm?param=b“的請求打印b,與預期不一致的原因是因為兩個請求共用一個”RequestInfo“bean對象,param的值只能保存一份。

  解決這個問題的一個方式就是使用spring的scope作用域,scope的取值及區別如下:

singleton

在每個Spring IoC容器中一個bean定義對應一個對象實例。

prototype

一個bean定義對應多個對象實例。

request

在一次HTTP請求中,一個bean定義對應一個實例;即每次HTTP請求將會有各自的bean實例, 它們依據某個bean定義創建而成。該作用域僅在基於web的Spring ApplicationContext 情形下有效。

session

在一個HTTP Session 中,一個bean定義對應一個實例。該作用域僅在基於web的SpringApplicationContext 情形下有效。

global session

在一個全局的HTTP Session 中,一個bean定義對應一個實例。典型情況下,僅在使用portlet context的時候有效。該作用域僅在基於web的Spring ApplicationContext 情形下有效。

  我們使用”requset“范圍的scope,使用注解進行配置:

@Component
@Scope(value="request")

  當使用request和session范圍時,需要在web.xml中加入如下配置,

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

  啟動項目,卻發現項目報錯如下:

  大意是一個singleton類型的bean對象如果使用我們的bean,我們需要提供一個代理,解決方法如下:

@Component
@Scope(value="request",proxyMode=ScopedProxyMode.TARGET_CLASS)

  這時還有另一個問題,當沒有requset請求時(在非web環境中使用),我們調用這個bean對象失敗怎么辦?解決方法是我們為每一個使用這個bean對象的線程提供一個默認的request上下文:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="session">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

  做好這些,我們啟動項目,使用”http://localhost:8080/index.htm?param=a“和”http://localhost:8080/index.htm?param=b“在同一瀏覽器先后進行請求時,打印如下:

  

  可以發現兩次請求分別擁有獨自的RequestInfo bean對象,它們的param值分別是我們期望的a和b。

  小結:本文主要說了在多線程環境下使用spring的有狀態bean對象可能會發生的線程安全問題以及解決方法,並舉了一個小例子進行說明,可能會有大牛覺得我又把簡單的問題說復雜了。。。但是如果有讀者原來不了解spring的scope,看了這篇文章后感覺有一點收獲,我就很開心啦,文中有說的不好的地方歡迎大家指出~

 

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM