簡介
本文介紹SpringBoot如何在Controller中擷取HttpServletRequest。
法1:Controller中加參數
簡介
該方法實作的原理是,在Controller方法開始處理請求時,Spring會将request對象指派到方法參數中。除了request對象,可以通過這種方法擷取的參數還有很多。
Controller中擷取request對象後,如果要在其他方法中(如service方法、工具類方法等)使用request對象,需要在調用這些方法時将request對象作為參數傳入。
代碼示例
這種方法實作最簡單,直接上Controller代碼:
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping
public class HelloController {
@GetMapping("/test1")
public String test1(HttpServletRequest request) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(request);
return request.toString();
}
}
線程安全性
是線程安全的。
測試:接口中睡眠,模拟多個請求同時處理
直接用“代碼示例”中的代碼
Postman開兩個視窗,都通路:http://localhost:8080/test1
結果:(兩個不同的request,可見線程安全的)
org.apache.catalina.connector.RequestFacade@4613eaaa
org.apache.catalina.connector.RequestFacade@492b9e2e
注意:如果不加睡眠,結果可能是:兩個相同的request,因為如果它能處理過來,就會用同一個request去接收了。
法2:自動注入
代碼示例
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping
public class HelloController {
@Autowired
private HttpServletRequest request;
@GetMapping("/test1")
public String test1() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(request);
return request.toString();
}
}
線程安全性
線程安全(不是測出來的,是看代碼得出的)。
分析:在Spring中,Controller的scope是singleton(單例),也就是說在整個web系統中,隻有一個TestController;但是其注入的request卻是線程安全的。
測試:接口中睡眠,模拟多個請求同時處理
使用上邊“代碼示例”中的代碼
Postman開兩個視窗,都通路:http://localhost:8080/test1
結果:(這個結果很奇怪,隻能去追尋源碼。見:SpringMVC原理--Controller線程安全_IT利刃出鞘的部落格)
Current HttpServletRequest
Current HttpServletRequest
法3:基類中自動注入
說明
與方法2相比,将注入部分代碼放入到了基類中。
代碼示例
基類代碼:
public class BaseController {
@Autowired
protected HttpServletRequest request;
}
Controller代碼
這裡列舉了BaseController的兩個派生類,由于此時測試代碼會有所不同,是以服務端測試代碼沒有省略;用戶端也需要進行相應的修改(同時向2個url發送大量并發請求)。
@Controller
public class TestController extends BaseController {
// 存儲已有參數,用于判斷參數value是否重複,進而判斷線程是否安全
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重複出現,request并發不安全!");
} else {
System.out.println(value);
set.add(value);
}
// 模拟程式執行了一段時間
Thread.sleep(1000);
}
}
@Controller
public class Test2Controller extends BaseController {
@RequestMapping("/test2")
public void test2() throws InterruptedException {
String value = request.getParameter("key");
// 判斷線程安全(與TestController使用一個set進行判斷)
if (TestController.set.contains(value)) {
System.out.println(value + "\t重複出現,request并發不安全!");
} else {
System.out.println(value);
TestController.set.add(value);
}
// 模拟程式執行了一段時間
Thread.sleep(1000);
}
}
線程安全性
測試結果:線程安全
分析:在了解了方法2的線程安全性的基礎上,很容易了解方法3是線程安全的:當建立不同的派生類對象時,基類中的域(這裡是注入的request)在不同的派生類對象中會占據不同的記憶體空間,也就是說将注入request的代碼放在基類中對線程安全性沒有任何影響;測試結果也證明了這一點。
優缺點
與方法2相比,避免了在不同的Controller中重複注入request;但是考慮到java隻允許繼承一個基類,是以如果Controller需要繼承其他類時,該方法便不再好用。
無論是方法2和方法3,都隻能在Bean中注入request;如果其他方法(如工具類中static方法)需要使用request對象,則需要在調用這些方法時将request參數傳遞進去。下面介紹的方法4,則可以直接在諸如工具類中的static方法中使用request對象(當然在各種Bean中也可以使用)。
法4:@ModelAttribute
代碼示例
下面這種方法及其變種(變種:将request和bindRequest放在子類中)在網上經常見到:
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping
public class HelloController {
protected HttpServletRequest request;
@ModelAttribute
public void bindreq(HttpServletRequest request) {
this.request = request;
}
@GetMapping("/test1")
public String test1() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(request);
return request.toString();
}
}
線程安全性
是線程安全的。
分析:@ModelAttribute注解用在Controller中修飾方法時,其作用是Controller中的每個@RequestMapping方法執行前,該方法都會執行。是以在本例中,bindRequest()的作用是在test()執行前為request對象指派。
測試:接口中睡眠,模拟多個請求同時處理
org.apache.catalina.connector.RequestFacade@3bb8d854
org.apache.catalina.connector.RequestFacade@3bb8d854