Spring 中獲取 request 的幾種方法及其線程安全性分析
點擊上方“程序員大白”,選擇“星標”公眾號
重磅干貨,第一時間送達
0x01: 概述
在使用Spring MVC開發(fā)Web系統(tǒng)時,經(jīng)常需要在處理請求時使用request對象,比如獲取客戶端ip地址、請求的url、header中的屬性(如cookie、授權(quán)信息)、body中的數(shù)據(jù)等。由于在Spring MVC中,處理請求的Controller、Service等對象都是單例的,因此獲取request對象時最需要注意的問題,便是request對象是否是線程安全的:當有大量并發(fā)請求時,能否保證不同請求/線程中使用不同的request對象。
這里還有一個問題需要注意:前面所說的“在處理請求時”使用request對象,究竟是在哪里使用呢?考慮到獲取request對象的方法有微小的不同,大體可以分為兩類:
1) 在Spring的Bean中使用request對象:既包括Controller、Service、Repository等MVC的Bean,也包括了Component等普通的Spring Bean。為了方便說明,后文中Spring中的Bean一律簡稱為Bean。
2) 在非Bean中使用request對象:如普通的Java對象的方法中使用,或在類的靜態(tài)方法中使用。
此外,本文討論是圍繞代表請求的request對象展開的,但所用方法同樣適用于response對象、InputStream/Reader、OutputStream/ Writer等;其中InputStream/Reader可以讀取請求中的數(shù)據(jù),OutputStream/ Writer可以向響應(yīng)寫入數(shù)據(jù)。
0x02:Controller方法中加參數(shù)
代碼示例
這種方法實現(xiàn)最簡單,直接上Controller代碼:
@Controller
public class TestController {
@RequestMapping("/test")
public void test(HttpServletRequest request) throws InterruptedException {
// 模擬程序執(zhí)行了一段時間
Thread.sleep(1000);
}
}
該方法實現(xiàn)的原理是,在Controller方法開始處理請求時,Spring會將request對象賦值到方法參數(shù)中。除了request對象,可以通過這種方法獲取的參數(shù)還有很多,
線程安全性
測試結(jié)果:線程安全
分析:此時request對象是方法參數(shù),相當于局部變量,毫無疑問是線程安全的。
優(yōu)缺點
這種方法的主要缺點是request對象寫起來冗余太多,主要體現(xiàn)在兩點:
1) 如果多個controller方法中都需要request對象,那么在每個方法中都需要添加一遍request參數(shù)
2) request對象的獲取只能從controller開始,如果使用request對象的地方在函數(shù)調(diào)用層級比較深的地方,那么整個調(diào)用鏈上的所有方法都需要添加request參數(shù)
實際上,在整個請求處理的過程中,request對象是貫穿始終的;也就是說,除了定時器等特殊情況,request對象相當于線程內(nèi)部的一個全局變量。而該方法,相當于將這個全局變量,傳來傳去。
0x03:自動注入
代碼示例
@Controller
public class TestController{
@Autowired
private HttpServletRequest request; //自動注入request
@RequestMapping("/test")
public void test() throws InterruptedException{
//模擬程序執(zhí)行了一段時間
Thread.sleep(1000);
}
}
線程安全性
測試結(jié)果:線程安全
分析:在Spring中,Controller的scope是singleton(單例),也就是說在整個web系統(tǒng)中,只有一個TestController;但是其中注入的request卻是線程安全的,原因在于:使用這種方式,當Bean(本例的TestController)初始化時,Spring并沒有注入一個request對象,而是注入了一個代理(proxy);當Bean中需要使用request對象時,通過該代理獲取request對象。
代理對象中用到了 ThreadLocal , 因此request對象也是線程局部變量;這就保證了request對象的線程安全性。
優(yōu)缺點
該方法的主要優(yōu)點:
1) 注入不局限于Controller中:在方法1中,只能在Controller中加入request參數(shù)。而對于方法2,不僅可以在Controller中注入,還可以在任何Bean中注入,包括Service、Repository及普通的Bean。
2) 注入的對象不限于request:除了注入request對象,該方法還可以注入其他scope為request或session的對象,如response對象、session對象等;并保證線程安全。
3) 減少代碼冗余:只需要在需要request對象的Bean中注入request對象,便可以在該Bean的各個方法中使用,與方法1相比大大減少了代碼冗余。
但是,該方法也會存在代碼冗余??紤]這樣的場景:web系統(tǒng)中有很多controller,每個controller中都會使用request對象(這種場景實際上非常頻繁),這時就需要寫很多次注入request的代碼;如果還需要注入response,代碼就更繁瑣了。下面說明自動注入方法的改進方法,并分析其線程安全性及優(yōu)缺點。
0x04:基類中自動注入
代碼示例
基類代碼:
public class BaseController {
@Autowired
protected HttpServletRequest request;
}
線程安全性
測試結(jié)果:線程安全
分析:在理解了方法2的線程安全性的基礎(chǔ)上,很容易理解方法3是線程安全的:當創(chuàng)建不同的派生類對象時,基類中的域(這里是注入的request)在不同的派生類對象中會占據(jù)不同的內(nèi)存空間,也就是說將注入request的代碼放在基類中對線程安全性沒有任何影響;測試結(jié)果也證明了這一點。
優(yōu)缺點
與方法2相比,避免了在不同的Controller中重復(fù)注入request;但是考慮到j(luò)ava只允許繼承一個基類,所以如果Controller需要繼承其他類時,該方法便不再好用。
無論是方法2和方法3,都只能在Bean中注入request;如果其他方法(如工具類中static方法)需要使用request對象,則需要在調(diào)用這些方法時將request參數(shù)傳遞進去。下面介紹的方法4,則可以直接在諸如工具類中的static方法中使用request對象(當然在各種Bean中也可以使用)。
0x05:手動調(diào)用
代碼示例
@Controller
public class TestController {
@RequestMapping("/test")
public void test() throws InterruptedException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// 模擬程序執(zhí)行了一段時間
Thread.sleep(1000);
}
}
線程安全性
測試結(jié)果:線程安全
分析:該方法與方法2(自動注入)類似,只不過方法2中通過自動注入實現(xiàn),本方法通過手動方法調(diào)用實現(xiàn)。因此本方法也是線程安全的。
優(yōu)缺點
優(yōu)點:可以在非Bean中直接獲取。缺點:如果使用的地方較多,代碼非常繁瑣;因此可以與其他方法配合使用。
0x06:總結(jié)
綜上所述,Controller中加參數(shù)(方法1)、自動注入(方法2和方法3)、手動調(diào)用(方法4)都是線程安全的,都可以用來獲取request對象。如果系統(tǒng)中request對象使用較少,則使用哪種方式均可;如果使用較多,建議使用自動注入(方法2 和方法3)來減少代碼冗余。如果需要在非Bean中使用request對象,既可以在上層調(diào)用時通過參數(shù)傳入,也可以直接在方法中通過手動調(diào)用(方法4)獲得。
source:http://www.cnblogs.com/kismetv/p/8757260.html
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學,西湖大學和上海交通大學的碩士博士運營維護的號,大家樂于分享高質(zhì)量文章,喜歡總結(jié)知識,歡迎關(guān)注[程序員大白],大家一起學習進步!


