成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發 后端
本文將介紹在Spring MVC開發的web系統中,獲取request對象的幾種方法,并討論其線程安全性。

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

前言

本文將介紹在Spring MVC開發的web系統中,獲取request對象的幾種方法,并討論其線程安全性。

目錄

  • 概述
  • 如何測試線程安全性
  • 方法1:Controller中加參數
  • 方法2:自動注入
  • 方法3:基類中自動注入
  • 方法4:手動調用
  • 方法5:@ModelAttribute方法
  • 總結

概述

在使用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個線程分別發送請求):

 

  1. public class Test {  
  2.    public static void main(String[] args) throws Exception {  
  3.        String prefix = UUID.randomUUID().toString().replaceAll("-""") + "::" 
  4.        for (int i = 0; i < 1000; i++) {  
  5.            final String value = prefix + i;  
  6.            new Thread() {  
  7.                @Override  
  8.                public void run() {  
  9.                    try {  
  10.                        CloseableHttpClient httpClient = HttpClients.createDefault();  
  11.                        HttpGet httpGet = new HttpGet("http://localhost:8080/test?key=" + value);  
  12.                        httpClient.execute(httpGet);  
  13.                        httpClient.close();  
  14.                    } catch (IOException e) {  
  15.                        e.printStackTrace();  
  16.                    } 
  17.                 } 
  18.  
  19.            }.start();  
  20.        }  
  21.    }  

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

 

  1. @Controller  
  2. public class TestController {  
  3.   // 存儲已有參數,用于判斷參數是否重復,從而判斷線程是否安全  
  4.    public static Set<String> set = new ConcurrentSkipListSet<>();  
  5.  
  6.    @RequestMapping("/test" 
  7.    public void test() throws InterruptedException {          
  8.  
  9.        // …………………………通過某種方式獲得了request對象………………………………  
  10.  
  11.        // 判斷線程安全  
  12.        String value = request.getParameter("key");  
  13.        if (set.contains(value)) {  
  14.            System.out.println(value + "\t重復出現,request并發不安全!");  
  15.        } else {  
  16.            System.out.println(value);  
  17.            set.add(value);  
  18.        }           
  19.  
  20.        // 模擬程序執行了一段時間  
  21.        Thread.sleep(1000);  
  22.    }  

補充:上述代碼原使用HashSet來判斷value是否重復,經網友批評指正,使用線程不安全的集合類驗證線程安全性是欠妥的,現已改為ConcurrentSkipListSet。

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

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

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

方法1:Controller中加參數

代碼示例

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

 

  1. @Controller  
  2. public class TestController {  
  3.    @RequestMapping("/test" 
  4.    public void test(HttpServletRequest request) throws InterruptedException {  
  5.        // 模擬程序執行了一段時間  
  6.        Thread.sleep(1000);  
  7.    }  

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

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

線程安全性

測試結果:線程安全

分析:此時request對象是方法參數,相當于局部變量,毫無疑問是線程安全的。

優缺點

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

  1. 如果多個controller方法中都需要request對象,那么在每個方法中都需要添加一遍request參數
  2. request對象的獲取只能從controller開始,如果使用request對象的地方在函數調用層級比較深的地方,那么整個調用鏈上的所有方法都需要添加request參數

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

方法2:自動注入

代碼示例

先上代碼:

 

  1. @Controller  
  2. public class TestController{      
  3.  
  4.    @Autowired  
  5.    private HttpServletRequest request; //自動注入request      
  6.  
  7.    @RequestMapping("/test" 
  8.    public void test() throws InterruptedException{  
  9.        //模擬程序執行了一段時間  
  10.        Thread.sleep(1000);  
  11.    }  

線程安全性

測試結果:線程安全

分析:在Spring中,Controller的scope是singleton(單例),也就是說在整個web系統中,只有一個TestController;但是其中注入的request卻是線程安全的,原因在于:

使用這種方式,當Bean(本例的TestController)初始化時,Spring并沒有注入一個request對象,而是注入了一個代理(proxy);當Bean中需要使用request對象時,通過該代理獲取request對象。 

下面通過具體的代碼對這一實現進行說明。

在上述代碼中加入斷點,查看request對象的屬性,如下圖所示:

在圖中可以看出,request實際上是一個代理:代理的實現參見AutowireUtils的內部類ObjectFactoryDelegatingInvocationHandler:

 

  1. /**  
  2. * Reflective InvocationHandler for lazy access to the current target object.  
  3. */  
  4. @SuppressWarnings("serial" 
  5. private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {  
  6.    private final ObjectFactory<?> objectFactory;  
  7.    public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {  
  8.        this.objectFactory = objectFactory;  
  9.    }  
  10.    @Override  
  11.    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  12.        // ……省略無關代碼  
  13.        try {  
  14.            return method.invoke(this.objectFactory.getObject(), args); // 代理實現核心代碼  
  15.        } 
  16.        catch (InvocationTargetException ex) {  
  17.            throw ex.getTargetException();  
  18.        }  
  19.    }  

也就是說,當我們調用request的方法method時,實際上是調用了由objectFactory.getObject()生成的對象的method方法;objectFactory.getObject()生成的對象才是真正的request對象。

繼續觀察上圖,發現objectFactory的類型為WebApplicationContextUtils的內部類RequestObjectFactory;而RequestObjectFactory代碼如下:

 

  1. /**  
  2. * Factory that exposes the current request object on demand.  
  3. */  
  4. @SuppressWarnings("serial" 
  5. private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {  
  6.    @Override  
  7.    public ServletRequest getObject() {  
  8.        return currentRequestAttributes().getRequest();  
  9.    } 
  10.    @Override  
  11.    public String toString() {  
  12.        return "Current HttpServletRequest" 
  13.    }  

其中,要獲得request對象需要先調用currentRequestAttributes()方法獲得RequestAttributes對象,該方法的實現如下:

 

  1. /**  
  2. Return the current RequestAttributes instance as ServletRequestAttributes.  
  3. */  
  4. private static ServletRequestAttributes currentRequestAttributes() {  
  5.    RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();  
  6.    if (!(requestAttr instanceof ServletRequestAttributes)) {  
  7.        throw new IllegalStateException("Current request is not a servlet request");  
  8.    }  
  9.    return (ServletRequestAttributes) requestAttr;  

生成RequestAttributes對象的核心代碼在類RequestContextHolder中,其中相關代碼如下(省略了該類中的無關代碼):

 

  1. public abstract class RequestContextHolder {  
  2.    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {  
  3.        RequestAttributes attributes = getRequestAttributes();  
  4.        // 此處省略不相關邏輯…………  
  5.        return attributes;  
  6.    }  
  7.    public static RequestAttributes getRequestAttributes() {  
  8.        RequestAttributes attributes = requestAttributesHolder.get(); 
  9.         if (attributes == null) {  
  10.            attributes = inheritableRequestAttributesHolder.get();  
  11.        }  
  12.        return attributes;  
  13.    }  
  14.    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =  
  15.            new NamedThreadLocal<RequestAttributes>("Request attributes");  
  16.    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =  
  17.            new NamedInheritableThreadLocal<RequestAttributes>("Request context");  

通過這段代碼可以看出,生成的RequestAttributes對象是線程局部變量(ThreadLocal),因此request對象也是線程局部變量;這就保證了request對象的線程安全性。

優缺點

該方法的主要優點:

  1. 注入不局限于Controller中:在方法1中,只能在Controller中加入request參數。而對于方法2,不僅可以在Controller中注入,還可以在任何Bean中注入,包括Service、Repository及普通的Bean。
  2. 注入的對象不限于request:除了注入request對象,該方法還可以注入其他scope為request或session的對象,如response對象、session對象等;并保證線程安全。
  3. 減少代碼冗余:只需要在需要request對象的Bean中注入request對象,便可以在該Bean的各個方法中使用,與方法1相比大大減少了代碼冗余。

但是,該方法也會存在代碼冗余。考慮這樣的場景:web系統中有很多controller,每個controller中都會使用request對象(這種場景實際上非常頻繁),這時就需要寫很多次注入request的代碼;如果還需要注入response,代碼就更繁瑣了。下面說明自動注入方法的改進方法,并分析其線程安全性及優缺點。

方法3:基類中自動注入

代碼示例

與方法2相比,將注入部分代碼放入到了基類中。

基類代碼:

 

  1. public class BaseController {  
  2.    @Autowired  
  3.    protected HttpServletRequest request;   

Controller代碼如下;這里列舉了BaseController的兩個派生類,由于此時測試代碼會有所不同,因此服務端測試代碼沒有省略;客戶端也需要進行相應的修改(同時向2個url發送大量并發請求)。

 

  1. @Controller  
  2. public class TestController extends BaseController {  
  3.  
  4.    // 存儲已有參數,用于判斷參數value是否重復,從而判斷線程是否安全  
  5.    public static Set<String> set = new ConcurrentSkipListSet<>();  
  6.  
  7.    @RequestMapping("/test" 
  8.    public void test() throws InterruptedException {  
  9.        String value = request.getParameter("key");  
  10.        // 判斷線程安全  
  11.        if (set.contains(value)) {  
  12.            System.out.println(value + "\t重復出現,request并發不安全!");  
  13.        } else {  
  14.            System.out.println(value);  
  15.            set.add(value);  
  16.        }  
  17.        // 模擬程序執行了一段時間  
  18.        Thread.sleep(1000);  
  19.    }  
  20.   
  21.  
  22. @Controller  
  23. public class Test2Controller extends BaseController {  
  24.    @RequestMapping("/test2" 
  25.    public void test2() throws InterruptedException {  
  26.        String value = request.getParameter("key");  
  27.        // 判斷線程安全(與TestController使用一個set進行判斷)  
  28.        if (TestController.set.contains(value)) {  
  29.            System.out.println(value + "\t重復出現,request并發不安全!");  
  30.        } else {  
  31.            System.out.println(value);  
  32.            TestController.set.add(value);  
  33.        }  
  34.        // 模擬程序執行了一段時間  
  35.        Thread.sleep(1000);  
  36.    } 

線程安全性

測試結果:線程安全

分析:在理解了方法2的線程安全性的基礎上,很容易理解方法3是線程安全的:當創建不同的派生類對象時,基類中的域(這里是注入的request)在不同的派生類對象中會占據不同的內存空間,也就是說將注入request的代碼放在基類中對線程安全性沒有任何影響;測試結果也證明了這一點。

優缺點

與方法2相比,避免了在不同的Controller中重復注入request;但是考慮到java只允許繼承一個基類,所以如果Controller需要繼承其他類時,該方法便不再好用。

無論是方法2和方法3,都只能在Bean中注入request;如果其他方法(如工具類中static方法)需要使用request對象,則需要在調用這些方法時將request參數傳遞進去。下面介紹的方法4,則可以直接在諸如工具類中的static方法中使用request對象(當然在各種Bean中也可以使用)。

方法4:手動調用

代碼示例

 

  1. @Controller  
  2. public class TestController {  
  3.    @RequestMapping("/test" 
  4.    public void test() throws InterruptedException {  
  5.        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();  
  6.        // 模擬程序執行了一段時間  
  7.        Thread.sleep(1000);  
  8.    }  

線程安全性

測試結果:線程安全

分析:該方法與方法2(自動注入)類似,只不過方法2中通過自動注入實現,本方法通過手動方法調用實現。因此本方法也是線程安全的。

優缺點

優點:可以在非Bean中直接獲取。缺點:如果使用的地方較多,代碼非常繁瑣;因此可以與其他方法配合使用。

方法5:@ModelAttribute方法

代碼示例

下面這種方法及其變種(變種:將request和bindRequest放在子類中)在網上經常見到:

 

  1. @Controller  
  2. public class TestController {  
  3.    private HttpServletRequest request;  
  4.    @ModelAttribute  
  5.    public void bindRequest(HttpServletRequest request) {  
  6.        this.request = request;  
  7.    }  
  8.    @RequestMapping("/test" 
  9.    public void test() throws InterruptedException {  
  10.        // 模擬程序執行了一段時間  
  11.        Thread.sleep(1000);  
  12.    }  

線程安全性

測試結果:線程不安全

分析:@ModelAttribute注解用在Controller中修飾方法時,其作用是Controller中的每個@RequestMapping方法執行前,該方法都會執行。因此在本例中,bindRequest()的作用是在test()執行前為request對象賦值。雖然bindRequest()中的參數request本身是線程安全的,但由于TestController是單例的,request作為TestController的一個域,無法保證線程安全。

總結

綜上所述,Controller中加參數(方法1)、自動注入(方法2和方法3)、手動調用(方法4)都是線程安全的,都可以用來獲取request對象。如果系統中request對象使用較少,則使用哪種方式均可;如果使用較多,建議使用自動注入(方法2 和方法3)來減少代碼冗余。如果需要在非Bean中使用request對象,既可以在上層調用時通過參數傳入,也可以直接在方法中通過手動調用(方法4)獲得。

此外,本文在討論獲取request對象的方法時,重點討論該方法的線程安全性、代碼的繁瑣程度等;在實際的開發過程中,還必須考慮所在項目的規范、代碼維護等問題(此處感謝網友的批評指正)

參考文獻

  • https://docs.spring.io/spring/docs/4.1.x/spring-framework-reference/html/beans.html#beans-factory-scopes-other-injection
  • https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
  • https://stackoverflow.com/questions/10541934/spring-aop-and-aspect-thread-safety-for-an-autowired-httpservletrequest-bean
  • http://www.phpchina.com/portal.php?mod=view&aid=40966
  • https://stackoverflow.com/questions/22674044/inject-httpservletrequest-into-controller
  • https://stackoverflow.com/questions/3320674/spring-how-do-i-inject-an-httpservletrequest-into-a-request-scoped-bean
  • https://my.oschina.net/sluggarddd/blog/678603?fromerr=XhvpvVTi
  • https://stackoverflow.com/questions/8504258/spring-3-mvc-accessing-httprequest-from-controller

 

責任編輯:龐桂玉 來源: Java知音
相關推薦

2021-03-23 14:30:34

物聯網IOT物聯網安全

2020-07-22 00:53:40

網絡安全網絡攻擊網絡威脅

2017-09-18 15:14:43

2021-02-07 09:29:39

數據安全網絡安全漏洞

2009-11-23 09:07:14

2009-10-15 10:28:42

2010-07-08 15:02:24

2017-08-07 09:24:43

云遷移安全方法

2010-05-17 16:26:36

IIS安全

2017-08-01 15:33:01

云遷移云安全數據泄露

2010-03-16 15:17:39

2020-08-24 08:05:47

JavaScriptJavaScript 頁面

2023-08-14 17:58:13

RequestHTTP請求

2015-06-15 10:48:25

2022-07-18 11:13:07

容器安全Docker

2009-11-30 09:41:38

2021-03-12 06:21:20

物聯網IoT

2021-01-12 07:39:48

線程線程安全

2021-05-16 17:14:30

線程安全性

2021-07-19 18:10:25

供應鏈安全惡意軟件網絡安全
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产日韩精品一区 | 久久亚| 久久日韩精品一区二区三区 | 黄色大片免费看 | 在线观看视频91 | 欧美日韩亚洲一区 | 看毛片的网站 | 国产视频久久 | 91在线色视频| 亚洲精品欧美精品 | 成人做爰www免费看 午夜精品久久久久久久久久久久 | 国产黄色一级电影 | 国产精品国产亚洲精品看不卡15 | 久久国产精品久久久久久久久久 | 中文字幕一区二区三区四区五区 | 久久综合狠狠综合久久综合88 | 日韩在线免费视频 | 一区二区三区小视频 | 综合一区二区三区 | 91 久久| 欧美13videosex性极品 | 久久精品小视频 | 免费在线视频一区二区 | 日韩一区二区精品 | www.日韩欧美 | 精品一区二区三区在线观看国产 | 欧美性久久 | 国产区第一页 | 黄色在线观看网址 | 一色桃子av一区二区 | 亚洲成人av | 久久综合九色综合欧美狠狠 | 午夜电影在线播放 | 亚洲欧美综合 | 91精品国产综合久久久久久 | 欧美激情综合色综合啪啪五月 | 中文av电影| 精品少妇v888av | 久久精品在线免费视频 | 国内精品视频免费观看 | 国产综合精品一区二区三区 |