Spring中獲取request的幾種方法

方法1:Controller中加參數

代碼示例:

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


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

該方法實現的原理是,在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:自動注入

代碼示例:

先上代碼:


@Controller
public class TestController{

@Autowired
private HttpServletRequest request; //自動注入request

@RequestMapping("/test")
public void test() throws InterruptedException{
//模擬程序執行了一段時間
Thread.sleep(1000);
}
}

線程安全:

測試結果:線程安全

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

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

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

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

Spring中獲取request的幾種方法

在圖中可以看出,request實際上是一個代理:代理的實現參見AutowireUtils的內部類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 {
// ……省略無關代碼
try {
return method.invoke(this.objectFactory.getObject(), args); // 代理實現核心代碼
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}

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

繼續觀察上圖,發現objectFactory的類型為WebApplicationContextUtils的內部類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";
}
}
/<servletrequest>

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


/**
* 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中,其中相關代碼如下(省略了該類中的無關代碼):


public abstract class RequestContextHolder {
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
// 此處省略不相關邏輯…………
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>/<requestattributes>/<requestattributes>/<requestattributes>

通過這段代碼可以看出,生成的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相比大大減少了代碼冗餘。

方法3:手動調用

代碼示例:


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

Thread.sleep(1000);
}
}

線程安全:

測試結果:線程安全

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

優缺點:

 優點:可以在非Bean中直接獲取。


分享到:


相關文章: