问题:
在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; } }我们先在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对象。
接下来,我们再进入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 也是每条请求线程中私有的,所以它是线程安全的。
那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>变量中。
使用方法参数传递的HttpServletRequest ,框架没有做封装,直接使用的是原生的HttpServletRequest 对象。
使用方法成员变量和@Autowired注解注入的HttpServletRequest ,实际上SpringMVC做了一层代理,最终获取的是RequestContextHolder 中的类型为ThreadLocal<RequestAttributes>中存放的
RequestAttributes中的HttpServletRequest 对象,因为使用了ThreadLocal,所以保证了每条线程
HttpServletRequest 对象的隔离,从而实现线程安全。