SpringMVC在Controller层中注入request的坑 - Go语言中文社区

SpringMVC在Controller层中注入request的坑


摘要: 记一次为了节省代码没有在方法体中声明HttpServletRequest,而用autowire直接注入所钻的坑

结论

给心急的人。 直接在Controller的成员变量上使用@Autowire声明HttpServletRequest,这是线程安全的!


 
  1. @Controller

  2. public class TestController{

  3.  
  4. @Autowire

  5. HttpServletRequest request;

  6.  
  7. @RequestMapping("/")

  8. public void test(){

  9. request.getAttribute("uid");

  10. }

  11. }

  12.  

结论如上。

背景

是这样的,由于项目中我在Request的头部加入身份验证信息,而我在拦截器截获信息并且验证通过后,会将当前用户的身份加到request的Attribute中,方便在Controller层拿出来复用。

疑问:为什么不直接在Controller上使用@RequestHeader取出来呢? 因为header里面是加密后的数据,且要经过一些复杂的身份验证判断,所以直接将这一步直接丢在了拦截器执行。

所以当解密后,我将用户信息(如uid)用request.setAttribute()设入request中在Controller提取。

而如果需要使用request,一般需要在方法上声明,如:


 
  1. public Result save(HttpServletRequest request){

  2. // dosomething();

  3. }

  4.  

那么我每个方法都要用到uid的岂不是每个方法都要声明一个request参数,为了节省着个冗余步骤。我写了一个基类。


 
  1. public class CommonController{

  2.  
  3. @Autowire

  4. HttpServletReqeust request;

  5.  
  6. public String getUid(){

  7. return (String)request.getAttribute("uid");

  8. }

  9. }

  10.  

后来我就担心,因为controller是单例的,这么写会不会导致后面的reqeust覆盖前面的request,在并发条件下有线程安全问题。 于是我就到segmentFault上提问,大部分网友说到,确实有线程问题!segmentFault问题地址

验证过程

因为网友大部分的观点是只能在方法上声明,我自然不想就此放弃多写那么多代码,于是开始我的验证过程。 热心的程序员们给我提供了好几种解决方案,我既然花力气证明了,就把结果放在这里,分享给大家。

方法1

第一个方法就是在controller的方法中显示声明HttpServletReqeust,代码如下:


 
  1. @RequestMapping("/test")

  2. @RestController

  3. public class CTest {

  4.  
  5. Logger logger = LoggerFactory.getLogger(getClass());

  6.  
  7. @RequestMapping("/iiii")

  8. public String test(HttpServletRequest request) {

  9. logger.info(request.hashCode() + "");

  10. return null;

  11. }

  12. }

在浏览器狂按F5

输出

reqeust的hashCode

当时我是懵逼的,说好的线程安全呢!这特么不是同一个request吗!特么的在逗我! 为此我还找了很久request是不是重写了hashcode()!

啊,事实是这样的,因为我用浏览器狂按F5,再怎么按他也是模拟不了并发的。那么就相当于,服务器一直在用同一个线程处理我的请求就足够了,至于这个request的hashcode,按照jdk的说法是根据obj在jvm的虚拟地址计算的,后面的事情是我猜的,如果有知道真正真想的还望告知!

猜测

服务器中每个thread所申请的request的内存空间在这个服务器启动的时候就是固定的,那么我每次请求,他都会在他所申请到的内存空间(可能是类似数组这样的结构)中新建一个request,(类似于数组的起点总是同一个内存地址),那么我发起一个请求,他就会在起始位置新建一个Request传递给Servlet并开始处理,处理结束后就会销毁,那么他下一个请求所新建的Request,因为之前的request销毁了,所以又从起始地址开始创建,这样一切就解释得通了!

猜测完毕

验证猜想:

我不让他有销毁的时间不就可以了吗 测试代码


 
  1. @RequestMapping("/test")

  2. @RestController

  3. public class CTest {

  4.  
  5. Logger logger = LoggerFactory.getLogger(getClass());

  6.  
  7. @RequestMapping("/oooo")

  8. public String testA(HttpServletRequest request) throws Exception {

  9. Thread.sleep(3000);

  10. logger.info(request.hashCode() + "");

  11. logger.info(reqeust.getHeader("uid");

  12. return null;

  13. }

  14.  
  15. @RequestMapping("/iiii")

  16. public String test(HttpServletRequest request) {

  17. logger.info(request.hashCode() + "");

  18. logger.info(reqeust.getHeader("uid");

  19. return null;

  20. }

  21. }

如上,我在接口/oooo中休眠3秒,如果他是共用一个reqeust的话,那么后面的请求将覆盖这个休眠中的reqeust,所传入的uid即为接口地址。先发起/oooo后发起/iiii

输出


 
  1. controller.CTest:33 - 364716268

  2. controller.CTest:34 - iiii

  3. controller.CTest:26 - 1892130707

  4. controller.CTest:27 - oooo

  5.  

结论: 1、后发起的/iiii没有覆盖前面/oooo的数据,没有线程安全问题。 2、request的hashcode不一样,因为/oooo的阻塞,导致另一个线程需要去处理,所以他新建了request,而不是向之前一样全部hashcode相同。

二轮验证


 
  1. public class HttpTest {

  2.  
  3. public static void main(String[] args) throws Exception {

  4.  
  5. for (int i = 300; i > 0; i--) {

  6. final int finalI = i;

  7. new Thread() {

  8. @Override

  9. public void run() {

  10. System.out.println("v###" + finalI);

  11. HttpRequest.get("http://localhost:8080/test/iiii?").header("uid", "v###" + finalI).send();

  12. }

  13. }.start();

  14. }

  15. }

  16. }

  17.  

在模拟并发条件下,header中的uid300个完全接受,没有覆盖

所以这种方式,没有线程安全问题。

方法2

在CommonController中,使用@ModelAttribute处理。


 
  1. public class CommonController {

  2.  
  3. // @Autowired

  4. protected HttpServletRequest request;

  5.  
  6. @ModelAttribute

  7. public void bindreq(HttpServletRequest request) {

  8. this.request = request;

  9. }

  10.  
  11. protected String getUid() {

  12. System.out.println(request.toString());

  13. return request.getAttribute("uid") == null ? null : (String) request.getAttribute("uid");

  14. }

  15. }

  16.  

这样子是有线程安全问题的!后面的request有可能覆盖掉之前的!

验证代码


 
  1. @RestController

  2. @RequestMapping("/test")

  3. public class CTest extends CommonController {

  4.  
  5. Logger logger = LoggerFactory.getLogger(getClass());

  6.  
  7. @RequestMapping("/iiii")

  8. public String test() {

  9. logger.info(request.getHeader("uid"));

  10. return null;

  11. }

  12. }


 
  1. public class HttpTest {

  2.  
  3. public static void main(String[] args) throws Exception {

  4.  
  5. for (int i = 100; i > 0; i--) {

  6. final int finalI = i;

  7. new Thread() {

  8. @Override

  9. public void run() {

  10. System.out.println("v###" + finalI);

  11. HttpRequest.get("http://localhost:8080/test/iiii").header("uid", "v###" + finalI).send();

  12. }

  13. }.start();

  14. }

  15. }

  16. }

  17.  

截取了部分输出结果


 
  1. controller.CTest:26 - v###52

  2. controller.CTest:26 - v###13

  3. controller.CTest:26 - v###57

  4. controller.CTest:26 - v###57

  5. controller.CTest:26 - v###21

  6. controller.CTest:26 - v###10

  7. controller.CTest:26 - v###82

  8. controller.CTest:26 - v###82

  9. controller.CTest:26 - v###93

  10. controller.CTest:26 - v###71

  11. controller.CTest:26 - v###71

  12. controller.CTest:26 - v###85

  13. controller.CTest:26 - v###85

  14. controller.CTest:26 - v###14

  15. controller.CTest:26 - v###47

  16. controller.CTest:26 - v###47

  17. controller.CTest:26 - v###69

  18. controller.CTest:26 - v###22

  19. controller.CTest:26 - v###55

  20. controller.CTest:26 - v###61

  21.  

可以看到57、71、85、47被覆盖了,丢失了部分request!

这么做是线程不安全的!

方法3

使用CommonController作为基类,将request Autowire。


 
  1. public class CommonController {

  2.  
  3. @Autowired

  4. protected HttpServletRequest request;

  5.  
  6. protected String getUid() {

  7. System.out.println(request.toString());

  8. return request.getAttribute("uid") == null ? null : (String) request.getAttribute("uid");

  9. }

  10. }

测试接口同上,结果喜人! 100个request没有任何覆盖,我加大范围测了五六次,上千次请求没一个覆盖,可以证明这种写法没有线程安全问题了!

另外还有一点有趣的是,无论使用多少并发,request的hashcode始终是相同的,而且,测试同一个Controller中不同的接口,他也相同,使用sleep强行阻塞,hashcode也是相同。但是访问不同的controller,hashcode却是不同的,具体里面如何实现我也就没有继续深挖了。

但是结论是出来的,就如文章最开始所说一样。

转载于:https://blog.csdn.net/albertfly/article/details/52680274

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yinni11/article/details/82692615
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-06 09:36:40
  • 阅读 ( 1109 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢