Spring 獲取 request 的幾種方法及其線程安全性分析

在使用Spring MVC開發Web系統時,經常需要在處理請求時使用request對象,比如獲取客戶端IP地址、請求的URL、header中的屬性(如cookie、授權信息)、body中的數據等。由於在Spring MVC中,處理請求的Controller、Service等對象都是單例的,因此獲取request對象時最需要注意的問題,便是request對象是否是線程安全的:當有大量併發請求時,能否保證不同請求/線程中使用不同的request對象。

這裡還有一個問題需要注意:前面所說的“在處理請求時”使用request對象,究竟是在哪裡使用呢?考慮到獲取request對象的方法有微小的不同,大體可以分為兩類:

1、在Spring的Bean中使用request對象:既包括Controller、Service、Repository等MVC的Bean,也包括了Component等普通的Spring Bean。為了方便說明,後文中Spring中的Bean一律簡稱為Bean。

2、在非Bean中使用request對象:如普通的Java對象的方法中使用,或在類的靜態方法中使用。

此外,本文討論是圍繞代表請求的request對象展開的,但所用方法同樣適用於response對象、InputStream/Reader、OutputStream/ Writer等;其中InputStream/Reader可以讀取請求中的數據,OutputStream/Writer可以向響應寫入數據。

最後,獲取request對象的方法與Spring及MVC的版本也有關係;本文基於Spring4進行討論,且所做的實驗都是使用4.1.1版本。

二、如何測試線程安全性

既然request對象的線程安全問題需要特別關注,為了便於後面的討論,下面先說明如何測試request對象是否是線程安全的。

測試的基本思路,是模擬客戶端大量併發請求,然後在服務器判斷這些請求是否使用了相同的request對象。

判斷request對象是否相同,最直觀的方式是打印出request對象的地址,如果相同則說明使用了相同的對象。然而,在幾乎所有web服務器的實現中,都使用了線程池,這樣就導致先後到達的兩個請求,可能由同一個線程處理:在前一個請求處理完成後,線程池收回該線程,並將該線程重新分配給了後面的請求。而在同一線程中,使用的request對象很可能是同一個(地址相同,屬性不同)。因此即便是對於線程安全的方法,不同的請求使用的request對象地址也可能相同。

為了避免這個問題,一種方法是在請求處理過程中使線程休眠幾秒,這樣可以讓每個線程工作的時間足夠長,從而避免同一個線程分配給不同的請求;另一種方法,是使用request的其他屬性(如參數、header、body等)作為request是否線程安全的依據,因為即便不同的請求先後使用了同一個線程(request對象地址也相同),只要使用不同的屬性分別構造了兩次request對象,那麼request對象的使用就是線程安全的。本文使用第二種方法進行測試。

客戶端測試代碼如下(創建1000個線程分別發送請求):

Spring 獲取 request 的幾種方法及其線程安全性分析

服務器中Controller代碼如下(暫時省略了獲取Request對象的代碼):

Spring 獲取 request 的幾種方法及其線程安全性分析

如果request對象線程安全,服務器中打印結果如下所示:

Spring 獲取 request 的幾種方法及其線程安全性分析

如果存在線程安全問題,服務器中打印結果可能如下所示:

Spring 獲取 request 的幾種方法及其線程安全性分析

如無特殊說明,本文後面的代碼中將省略掉測試代碼。

三、方法1:Controller中加參數

1、代碼示例

這種方法實現最簡單,直接上Controller代碼:

該方法實現的原理是,在Controller方法開始處理請求時,Spring會將request對象賦值到方法參數中。除了request對象,可以通過這種方法獲取的參數還有很多,具體可以參見:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods

Controller中獲取request對象後,如果要在其他方法中(如service方法、工具類方法等)使用request對象,需要在調用這些方法時將request對象作為參數傳入。

2、線程安全性

測試結果:線程安全

分析:此時request對象是方法參數,相當於局部變量,毫無疑問是線程安全的。線程安全的 Map 可以點此查看這篇文章。

3、優缺點

這種方法的主要缺點是request對象寫起來冗餘太多,主要體現在兩點:

(1)如果多個controller方法中都需要request對象,那麼在每個方法中都需要添加一遍request參數

(2) request對象的獲取只能從controller開始,如果使用request對象的地方在函數調用層級比較深的地方,那麼整個調用鏈上的所有方法都需要添加request參數

實際上,在整個請求處理的過程中,request對象是貫穿始終的;也就是說,除了定時器等特殊情況,request對象相當於線程內部的一個全局變量。而該方法,相當於將這個全局變量,傳來傳去.


分享到:


相關文章: