Controller成员变量注入HttpServletRequest安全吗?

tech2024-10-01  6

问题:

在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 对象的隔离,从而实现线程安全。

最新回复(0)