Spring MVC 成員變量 request 線程安全問題的討論

 

做者:wangxinxijava

 

   最近有人問我,Spring MVC Controller的成員變量@Resource private HttpServletRequest request,這樣用會不會產生線程安全問題。咋一想Spring MVC 的 Controller默認是單例,成員變量request應該會致使線程安全問題,那麼真的是這樣的?web

 

已知一個Spring MVC的Controller, 用戶的控制器spring

代碼以下:安全

@Controller多線程

@RequestMapping("/user")併發

public class UserController {app

 

private HttpServletRequest request1;ide

 

@Resource高併發

private HttpServletRequest request2;this

 

@ModelAttribute

public void setRequest1(HttpServletRequest request) {

    this.request1 = request;

}

 

@RequestMapping("/test")

public void testRequest(){

    System.out.println(request1.getParameter("test"));

    System.out.println(request2.getParameter("test"));

}

 

那麼request1和request2, 在高併發下是線程安全性的?

先說答案:request1非線程安全,而request2是線程安全

 

衆所周知,在 Controller 是單例的狀況下,成員變量通常不是線程安全的(即多個線程共享一個成員變量), 可是request2倒是線程安全的, 爲何?

 

request1非線程安全咱們絲絕不懷疑:

假設有用戶甲和乙同事併發的訪問user/test接口,甲和乙都會把本身的request賦值給request1。當甲使用request1的時候, 乙能夠把乙的request賦值給成員變量request1,可是甲這時候使用的就是乙的request了,就有可能致使安全問題。

 

可是request2僅僅加上了一個註解"@Resource"就變成了線程安全的。

因此我決定分析一下,下圖是用idea調試模式,很容易發現, 接口request1的 多態是基於WebAPP容器的RequestFacade,對與當前的環境具體是指Tomcat對HttpServletRequest 的門面模式的實習;接口request2是$Proxy15,這個是啥東西?明眼的人一眼就看出了, 這個是基於JDK動態代理生成的代理類,生成代理類型的規則是$ProxyN, 若是不知道JDK的動態代理請自行Google。

 

 

 

繼續分析request2,代理類的InvocationHandler是AutowireUtils的內部類ObjectFactoryDelegatingInvocationHandler,這一切都是怎麼發生的?

尋蹤溯源

 

一、Spring 實例化Bean的時候,發現有一個@Resource private HttpServletRequest request2;須要注入,須要進行注入處理

 

若是類型是

這8種接口, 須要特殊處理,包含javax.servlet.http.HttpServletRequest,被映射成WebApplicationContextUtils.RequestObjectFactory這個是生產Request的工廠類,這個工廠就是生產ServletRequest的,其源代碼以下:

 

而後經過JDK動態代理生成HttpServletRequest的代理類

 

ObjectFactoryDelegatingInvocationHandler 動態代理的具體實現

 

最後完成對request2注入。

 

二、粗略看注入流程,除了特殊處理HttpServletRequest以外沒有什麼特殊的。

然而咱們在看看WebApplicationContextUtils.RequestObjectFactory的getObject()方法

經過currentRequestAttributes方法拿到了ServletRequestAttributes,最後經過ServletRequestAttributes拿到了HttpServletRequest。

 

下面咱們分析一下currentRequestAttributes方法

接着調用了RequestContextHolder.currentRequestAttributes();

而後調用了

最後是經過requestAttributesHolder拿到了HttpServletRequest。

咱們再來看看requestAttributesHolder是什麼鬼?

 

 

咱們驚奇的發現requestAttributesHolder是ThreadLocal<RequestAttributes>,有了ThreadLocal這個神器,RequestAttributes綁定着了HttpServletRequest,難怪能夠保證@Resource private HttpServletRequest request2;這個線程安全,自此真相大白。

RequestAttributes是如何綁定HttpServletRequest的?

在web.xml配置的監聽器

<listener>

<listener-class>

org.springframework.web.context.request.RequestContextListener

</listener-class>

</listener>

 

經過HTTP請求時的監聽器進行的配置,在這個請求的上下文中均可以獲得這個request

 

這裏講一下,ThreadLocal的做用是提供線程內的局部變量,這種變量在多線程環境下訪問時可以保證各個線程裏變量的獨立性。更多關於ThreadLocal知識請自行google。

 

簡而言之,Spring MVC在 Controller 是單例的狀況下,會對HttpServletRequest等須要注入的接口作特殊處理,經過JDK的動態代理的方式和ThreadLocal對應的線程變量綁定,從而保證線程安全。因此在Controller等其餘的請求上下文中放心的使用@Resource private HttpServletRequest request吧。

 

但願對您有所幫助

相關文章
相關標籤/搜索