欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Spring中怎么獲取request

這篇文章將為大家詳細(xì)講解有關(guān)Spring中怎么獲取request,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

創(chuàng)新互聯(lián)建站-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比青神網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式青神網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋青神地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴。

在使用Spring MVC開發(fā)Web系統(tǒng)時(shí),經(jīng)常需要在處理請求時(shí)使用request對象,比如獲取客戶端ip地址、請求的url、header中的屬性(如cookie、授權(quán)信息)、body中的數(shù)據(jù)等。由于在Spring MVC中,處理請求的Controller、Service等對象都是單例的,因此獲取request對象時(shí)最需要注意的問題,便是request對象是否是線程安全的:當(dāng)有大量并發(fā)請求時(shí),能否保證不同請求/線程中使用不同的request對象。

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

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

  3. 在非Bean中使用request對象:如普通的Java對象的方法中使用,或在類的靜態(tài)方法中使用。

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

***,獲取request對象的方法與Spring及MVC的版本也有關(guān)系;本文基于Spring4進(jìn)行討論,且所做的實(shí)驗(yàn)都是使用4.1.1版本。

如何測試線程安全性

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

測試的基本思路,是模擬客戶端大量并發(fā)請求,然后在服務(wù)器判斷這些請求是否使用了相同的request對象。

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

為了避免這個(gè)問題,一種方法是在請求處理過程中使線程休眠幾秒,這樣可以讓每個(gè)線程工作的時(shí)間足夠長,從而避免同一個(gè)線程分配給不同的請求;另一種方法,是使用request的其他屬性(如參數(shù)、header、body等)作為request是否線程安全的依據(jù),因?yàn)榧幢悴煌恼埱笙群笫褂昧送粋€(gè)線程(request對象地址也相同),只要使用不同的屬性分別構(gòu)造了兩次request對象,那么request對象的使用就是線程安全的。本文使用第二種方法進(jìn)行測試。

客戶端測試代碼如下(創(chuàng)建1000個(gè)線程分別發(fā)送請求):

public class Test {     public static void main(String[] args) throws Exception {         String prefix = UUID.randomUUID().toString().replaceAll("-", "") + "::";         for (int i = 0; i < 1000; i++) {             final String value = prefix + i;             new Thread() {                 @Override                 public void run() {                     try {                         CloseableHttpClient httpClient = HttpClients.createDefault();                         HttpGet httpGet = new HttpGet("http://localhost:8080/test?key=" + value);                         httpClient.execute(httpGet);                         httpClient.close();                     } catch (IOException e) {                         e.printStackTrace();                     }                 }             }.start();         }     }  }

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

@Controller  public class TestController {    // 存儲已有參數(shù),用于判斷參數(shù)是否重復(fù),從而判斷線程是否安全     public static Set<String> set = new ConcurrentSkipListSet<>();      @RequestMapping("/test")     public void test() throws InterruptedException {                  // &hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;通過某種方式獲得了request對象&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;          // 判斷線程安全         String value = request.getParameter("key");         if (set.contains(value)) {             System.out.println(value + "\t重復(fù)出現(xiàn),request并發(fā)不安全!");         } else {             System.out.println(value);             set.add(value);         }                   // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }

補(bǔ)充:上述代碼原使用HashSet來判斷value是否重復(fù),經(jīng)網(wǎng)友批評指正,使用線程不安全的集合類驗(yàn)證線程安全性是欠妥的,現(xiàn)已改為ConcurrentSkipListSet。

如果request對象線程安全,服務(wù)器中打印結(jié)果如下所示:

Spring中怎么獲取request

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

Spring中怎么獲取request

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

方法1:Controller中加參數(shù)

代碼示例

這種方法實(shí)現(xiàn)最簡單,直接上Controller代碼:

@Controller  public class TestController {     @RequestMapping("/test")     public void test(HttpServletRequest request) throws InterruptedException {         // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }

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

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

線程安全性

測試結(jié)果:線程安全

分析:此時(shí)request對象是方法參數(shù),相當(dāng)于局部變量,毫無疑問是線程安全的。

優(yōu)缺點(diǎn)

這種方法的主要缺點(diǎn)是request對象寫起來冗余太多,主要體現(xiàn)在兩點(diǎn):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 如果多個(gè)controller方法中都需要request對象,那么在每個(gè)方法中都需要添加一遍request參數(shù)

  3. request對象的獲取只能從controller開始,如果使用request對象的地方在函數(shù)調(diào)用層級比較深的地方,那么整個(gè)調(diào)用鏈上的所有方法都需要添加request參數(shù)

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

方法2:自動(dòng)注入

代碼示例

先上代碼:

@Controller  public class TestController{          @Autowired     private HttpServletRequest request; //自動(dòng)注入request          @RequestMapping("/test")     public void test() throws InterruptedException{         //模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }

線程安全性

測試結(jié)果:線程安全

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

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

下面通過具體的代碼對這一實(shí)現(xiàn)進(jìn)行說明。

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

Spring中怎么獲取request

在圖中可以看出,request實(shí)際上是一個(gè)代理:代理的實(shí)現(xiàn)參見AutowireUtils的內(nèi)部類ObjectFactoryDelegatingInvocationHandler:

/**  * Reflective InvocationHandler for lazy access to the current target object.  */  @SuppressWarnings("serial")  private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {     private final ObjectFactory<?> objectFactory;     public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {         this.objectFactory = objectFactory;     }     @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         // &hellip;&hellip;省略無關(guān)代碼         try {             return method.invoke(this.objectFactory.getObject(), args); // 代理實(shí)現(xiàn)核心代碼         }        catch (InvocationTargetException ex) {             throw ex.getTargetException();         }     }  }

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

繼續(xù)觀察上圖,發(fā)現(xiàn)objectFactory的類型為WebApplicationContextUtils的內(nèi)部類RequestObjectFactory;而RequestObjectFactory代碼如下:

/**  * Factory that exposes the current request object on demand.  */  @SuppressWarnings("serial")  private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {     @Override     public ServletRequest getObject() {         return currentRequestAttributes().getRequest();     }    @Override     public String toString() {         return "Current HttpServletRequest";     }  }

其中,要獲得request對象需要先調(diào)用currentRequestAttributes()方法獲得RequestAttributes對象,該方法的實(shí)現(xiàn)如下:

/**  * Return the current RequestAttributes instance as ServletRequestAttributes.  */  private static ServletRequestAttributes currentRequestAttributes() {     RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();     if (!(requestAttr instanceof ServletRequestAttributes)) {         throw new IllegalStateException("Current request is not a servlet request");     }     return (ServletRequestAttributes) requestAttr;  }

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

public abstract class RequestContextHolder {     public static RequestAttributes currentRequestAttributes() throws IllegalStateException {         RequestAttributes attributes = getRequestAttributes();         // 此處省略不相關(guān)邏輯&hellip;&hellip;&hellip;&hellip;         return attributes;     }     public static RequestAttributes getRequestAttributes() {         RequestAttributes attributes = requestAttributesHolder.get();         if (attributes == null) {             attributes = inheritableRequestAttributesHolder.get();         }         return attributes;     }     private static final ThreadLocal<RequestAttributes> requestAttributesHolder =             new NamedThreadLocal<RequestAttributes>("Request attributes");     private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =             new NamedInheritableThreadLocal<RequestAttributes>("Request context");  }

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

優(yōu)缺點(diǎn)

該方法的主要優(yōu)點(diǎn):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 注入不局限于Controller中:在方法1中,只能在Controller中加入request參數(shù)。而對于方法2,不僅可以在Controller中注入,還可以在任何Bean中注入,包括Service、Repository及普通的Bean。

  3. 注入的對象不限于request:除了注入request對象,該方法還可以注入其他scope為request或session的對象,如response對象、session對象等;并保證線程安全。

  4. 減少代碼冗余:只需要在需要request對象的Bean中注入request對象,便可以在該Bean的各個(gè)方法中使用,與方法1相比大大減少了代碼冗余。

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

方法3:基類中自動(dòng)注入

代碼示例

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

基類代碼:

public class BaseController {     @Autowired     protected HttpServletRequest request;   }

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

@Controller  public class TestController extends BaseController {      // 存儲已有參數(shù),用于判斷參數(shù)value是否重復(fù),從而判斷線程是否安全     public static Set<String> set = new ConcurrentSkipListSet<>();      @RequestMapping("/test")     public void test() throws InterruptedException {         String value = request.getParameter("key");         // 判斷線程安全         if (set.contains(value)) {             System.out.println(value + "\t重復(fù)出現(xiàn),request并發(fā)不安全!");         } else {             System.out.println(value);             set.add(value);         }         // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }    @Controller  public class Test2Controller extends BaseController {     @RequestMapping("/test2")     public void test2() throws InterruptedException {         String value = request.getParameter("key");         // 判斷線程安全(與TestController使用一個(gè)set進(jìn)行判斷)         if (TestController.set.contains(value)) {             System.out.println(value + "\t重復(fù)出現(xiàn),request并發(fā)不安全!");         } else {             System.out.println(value);             TestController.set.add(value);         }         // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     } }

線程安全性

測試結(jié)果:線程安全

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

優(yōu)缺點(diǎn)

與方法2相比,避免了在不同的Controller中重復(fù)注入request;但是考慮到j(luò)ava只允許繼承一個(gè)基類,所以如果Controller需要繼承其他類時(shí),該方法便不再好用。

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

方法4:手動(dòng)調(diào)用

代碼示例

@Controller  public class TestController {     @RequestMapping("/test")     public void test() throws InterruptedException {         HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();         // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }

線程安全性

測試結(jié)果:線程安全

分析:該方法與方法2(自動(dòng)注入)類似,只不過方法2中通過自動(dòng)注入實(shí)現(xiàn),本方法通過手動(dòng)方法調(diào)用實(shí)現(xiàn)。因此本方法也是線程安全的。

優(yōu)缺點(diǎn)

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

方法5:@ModelAttribute方法

代碼示例

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

@Controller  public class TestController {     private HttpServletRequest request;     @ModelAttribute     public void bindRequest(HttpServletRequest request) {         this.request = request;     }     @RequestMapping("/test")     public void test() throws InterruptedException {         // 模擬程序執(zhí)行了一段時(shí)間         Thread.sleep(1000);     }  }

線程安全性

測試結(jié)果:線程不安全

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

關(guān)于Spring中怎么獲取request就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

網(wǎng)站標(biāo)題:Spring中怎么獲取request
標(biāo)題URL:http://chinadenli.net/article10/ipcpgo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站響應(yīng)式網(wǎng)站域名注冊網(wǎng)站收錄網(wǎng)站建設(shè)微信公眾號

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站