1. 程式人生 > >Springmvc獲取request物件&執行緒安全

Springmvc獲取request物件&執行緒安全

概述:
在使用Springmvc開發web系統時,經常要用到request物件來處理請求,比如獲取客戶端IP地址、請求的url、header中的屬性(cookie、授權資訊等)、body中的資料等。由於Springmvc中的Controller、Service等都是單例的,因此就需要關注request物件是否是執行緒安全的:當有大量併發請求時,能否保證不同請求/執行緒中使用不同的request物件。
(本文對request的討論,同樣適用於response物件、InputStream/Reader、OutputStream/Writer等)

測試執行緒安全性的方法:
基本思路:模擬客戶端大量併發請求,然後在伺服器判斷這些請求是否使用了相同的request物件。
判斷request物件是否相同,最直觀的方式是打印出request物件的地址。然而,幾乎所有的web伺服器實現中都使用了執行緒池,這就導致了先後到達的兩個請求,可能由同一個執行緒處理:在前一個請求處理完成後,執行緒池回收該執行緒,並將該執行緒重新分配給後面的請求;而在同一執行緒中,使用的request物件很可能是同一個(地址相同,屬性不同),因此,即便是對於執行緒安全的方法,不同的請求使用的request物件地址也可能相同。在這裡,使用request中的請求引數作為request是否相同的依據。

獲取request的方法:
方法1,Controller方法中加引數:
分析:在Controller方法開始處理請求時,Spring會將request物件賦值到方法引數中。此時request物件是方法引數,相當於是區域性變數,一定是執行緒安全的。
缺點:
(1)如果多個controller方法中都需要request物件,那麼在每個方法中都需要新增一遍request引數;
(2)request物件的獲取只能從controller開始,如果使用request物件的地方在函式呼叫層級比較深的地方,那麼整個呼叫鏈上的所有方法都需要新增request引數。
(實際上,除了定時器等特殊情況,request相當於執行緒內部的一個全域性變數,而該方法相當於將一個全域性變數傳來傳去)

方法2,自動注入:
@Autowired
private HttpServletRequest request;
分析:在Springmvc中,Controller是單例的,但是其中注入的request卻是執行緒安全的,因為在這種方式中,Controller初始化時並沒有注入一個request物件,而是注入了一個代理proxy,當Controller中需要使用request物件時,通過代理獲取request物件(url中有原始碼解釋,最終request是執行緒區域性變數(ThreadLocal),是執行緒安全的)。
優點:
(1)注入不侷限於Controller中,在Service、Repository、普通Bean中都可以注入;
(2)注入物件不侷限於request,並保證執行緒安全;
(3)減少程式碼冗餘。
(不過,如果web系統中有很多controller,每個controller中都會使用request物件(實際上這很常見),這時就需要寫很多次注入request的程式碼,如果再加上response,程式碼就更繁瑣了)

方法3,基類中自動注入:
分析:當建立不同的派生類物件時,基類中的域在不同的派生類物件中會佔據不同的記憶體空間,也就是說將注入request的程式碼放在基類中對執行緒安全沒有任何影響。
優缺點:
(1)與方法2相比,避免了在不同的Controller中重複注入request,但考慮到Java只允許繼承一個基類,所以如果Controller需要繼承其它類時,該方法不再好用;
(2)無論方法2還是方法3,都只能在Bean中注入request,如果其它方法(如工具類中的static方法)需要使用request物件,依然需要在呼叫這些方法時將request引數傳進去。

方法4:手動呼叫:
HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getRequest();
分析:該方法與方法2類似,只不過方法2通過自動注入實現,本方法使用手動呼叫,執行緒安全。
優點:可以在非Bean中直接獲取;
缺點:程式碼繁瑣,可以與其它方法配合使用

方法5:@ModelAttribute方法:
@ModelAttribute
public void bindRequest(HttpServletRequest request) {
    this.request = request;
}
分析:雖然request本身是執行緒安全的,但是Controller是單例的,request作為單例的一個域,無法保證執行緒安全。

總結:
除了方法5,方法1-4都是執行緒安全的。如果系統中request物件使用較少,哪種方式均可;如果使用較多,建議使用方法2/3來減少程式碼冗餘,如果需要在非Bean物件中使用request,既可以通過引數傳入,也可以直接在方法中通過手動呼叫獲得。