问题:
在SpringMVC的应用中,我们使用HttpServletRequest
时都是将其直接放到Controller
方法中的入参,但是这样十分冗余, 如果我们可以使用@Autowired
注解配合Spring的注入机制在Controller
中让HttpServletRequest
作为成员变量注入,那需要使用session
的方法入参中就少了一个参数,但是这样做真的安全吗?
Spring默认的注入的bean的作用域是单例的,使用这种方式会不会导致HttpServletRequest
也是单例的从而出现线程不安全的情况?
接下来看看例子,例子源代码:
@Controller
@RequestMapping("/request")
public class RequestController {
@Autowired
HttpServletRequest request;
@RequestMapping("/methodParam")
@ResponseBody
public String byMethodParam(HttpServletRequest httpServletRequest) {
String id = httpServletRequest.getParameter("id");
return id;
}
@ResponseBody
@RequestMapping("/memberVariable")
public String byMemberVariable() {
String id = request.getParameter("id");
return id;
}
}
1. 方法参数使用流程分析:
我们先在byMethodParam(HttpServletRequest httpServletRequest)
方法中打上断点,看看方法入参的HttpServletRequest
是什么对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Uj2iEag-1599138406075)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/854c6f9c-7d7b-4bf3-bd09-8727469bb991/Untitled.png)]
我们可以看到,这里的HttpServletRequest
是个SecurityContextHolderAwareRequestWrapper
对象,我们再进入SecurityContextHolderAwareRequestWrapper
对象看看它的继承关系:
SecurityContextHolderAwareRequestWrapper
继承了HttpServletRequest
,所以实际上它是个Servlet
中最原生的HttpServletRequest
对象。
2. 成员变量注入流程分析:
接下来,我们再进入byMemberVariable
方法中打断点。
可以看到,作为成员变量注入的HttpServletRequest对象实际的类型与上面的不一样了。它实际上是一个AutowireUtils
类的静态内部类ObjectFactoryDelegatingInvocationHandler
。
这个静态内部类ObjectFactoryDelegatingInvocationHandler
实际上继承了InvocationHandler
, 实际上它是一个代理,在代理实现的invoke()
方法中,我们重点看method.invoke(this.objectFactory.getObject(), args);
这里代理方法使用的对象是this.objectFactory.getObject()
获取的, 断点走下来看看objectFactory
的对象类型:
它实际上是WebApplicationContextUtils
类中定义的静态内部类RequestObjectFactory
,我们再看看这个RequestObjectFactory
类中定义的方法:
我们再进入currentRequestAttributes()
方法:
再进入RequestContextHolder.currentRequestAttributes()
方法:
再进入getRequestAttributes()
方法:
再看看requestAttributesHolder
是个什么东西:
看到这里就真相大白了,这里实际上使用了一个ThreadLocal
存着RequestAttributes
, 而HttpServletRequest
是在RequestAttributes
对象中获取的,这里用了ThreadLocal,所以每个请求都对应了一条线程自己的RequestAttributes
,所以HttpServletRequest
也是每条请求线程中私有的,所以它是线程安全的。
3. 引申问题:
那SpringMVC是什么时候将HttpServletRequest
对象设置到ThreadLocal
中的呢?
我们先看DispatcherServlet
类,这里没有发现相关方法,再看它的父类FrameworkServlet
,找到FrameworkServlet
中的doService()、doGet()、doPost()等几个基本的Http处理方法,里面都执行了processRequest(HttpServletRequest request, HttpServletResponse response)
方法:
再进入processRequest(HttpServletRequest request, HttpServletResponse response)
一探究竟:
这里从RequestContextHolder
中获取了RequestAttributes
对象,再看看initContextHolders()方法:
原来就是在这个地方设置了RequestAttributes
。
简单来说,就是对每个请求进行处理时,在FrameworkServlet
的processRequest(HttpServletRequest request, HttpServletResponse response)
设置了RequestAttributes
到RequestContextHolder
类的ThreadLocal<RequestAttributes>
变量中。
4. 总结:
使用方法参数传递的HttpServletRequest
,框架没有做封装,直接使用的是原生的HttpServletRequest
对象。
使用方法成员变量和@Autowired注解注入的HttpServletRequest
,实际上SpringMVC做了一层代理,最终获取的是RequestContextHolder
中的类型为ThreadLocal<RequestAttributes>
中存放的
RequestAttributes
中的HttpServletRequest
对象,因为使用了ThreadLocal
,所以保证了每条线程
HttpServletRequest
对象的隔离,从而实现线程安全。