Spring MVC 成员变量 request 线程安全问题的讨论

 

作者:wangxinxi

 

   最近有人问我,Spring MVC Controller的成员变量@Resource private HttpServletRequest request,这样用会不会产生线程安全问题。咋一想Spring MVC 的 Controller默认是单例,成员变量request应该会导致线程安全问题,那么真的是这样的?

 

已知一个Spring MVC的Controller, 用户的控制器

代码如下:

@Controller

@RequestMapping("/user")

public class UserController {

 

private HttpServletRequest request1;

 

@Resource

private HttpServletRequest request2;

 

@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。

 

Spring MVC 成员变量 request 线程安全问题的讨论

 

 

继续分析request2,代理类的InvocationHandler是AutowireUtils的内部类ObjectFactoryDelegatingInvocationHandler,这一切都是怎么发生的?

Spring MVC 成员变量 request 线程安全问题的讨论

寻踪溯源

 

1、Spring 实例化Bean的时候,发现有一个@Resource private HttpServletRequest request2;需要注入,需要进行注入处理

 

Spring MVC 成员变量 request 线程安全问题的讨论

如果类型是

Spring MVC 成员变量 request 线程安全问题的讨论

这8种接口, 需要特殊处理,包含javax.servlet.http.HttpServletRequest,被映射成WebApplicationContextUtils.RequestObjectFactory这个是生产Request的工厂类,这个工厂就是生产ServletRequest的,其源代码如下:

 

Spring MVC 成员变量 request 线程安全问题的讨论

然后通过JDK动态代理生成HttpServletRequest的代理类

 

Spring MVC 成员变量 request 线程安全问题的讨论

ObjectFactoryDelegatingInvocationHandler 动态代理的具体实现

Spring MVC 成员变量 request 线程安全问题的讨论

 

最后完成对request2注入。

 

2、粗略看注入流程,除了特殊处理HttpServletRequest之外没有什么特殊的。

然而我们在看看WebApplicationContextUtils.RequestObjectFactory的getObject()方法

Spring MVC 成员变量 request 线程安全问题的讨论

通过currentRequestAttributes方法拿到了ServletRequestAttributes,最后通过ServletRequestAttributes拿到了HttpServletRequest。

 

下面我们分析一下currentRequestAttributes方法

Spring MVC 成员变量 request 线程安全问题的讨论

接着调用了RequestContextHolder.currentRequestAttributes();

Spring MVC 成员变量 request 线程安全问题的讨论

然后调用了

Spring MVC 成员变量 request 线程安全问题的讨论

最后是通过requestAttributesHolder拿到了HttpServletRequest。

Spring MVC 成员变量 request 线程安全问题的讨论

我们再来看看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>

 

Spring MVC 成员变量 request 线程安全问题的讨论

通过HTTP请求时的监听器进行的配置,在这个请求的上下文中都可以得到这个request

 

这里讲一下,ThreadLocal的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。更多关于ThreadLocal知识请自行google。

 

简而言之,Spring MVC在 Controller 是单例的情况下,会对HttpServletRequest等需要注入的接口做特殊处理,通过JDK的动态代理的方式和ThreadLocal对应的线程变量绑定,从而保证线程安全。所以在Controller等其他的请求上下文中放心的使用@Resource private HttpServletRequest request吧。

 

希望对您有所帮助

 

转载于:https://my.oschina.net/u/270991/blog/1785896