标签:开发 tco contex hold web服务器 方法 线程池 测试 获取客户端ip
概述:
在使用Springmvc开发web系统时,经常要用到request对象来处理请求,比如获取客户端IP地址、请求的url、header中的属性(cookie、授权信息等)、body中的数据等。由于Springmvc中的Controller、Service等都是单例的,因此就需要关注request对象是否是线程安全的:当有大量并发请求时,能否保证不同请求/线程中使用不同的request对象。
(本文对request的讨论,同样适用于response对象、InputStream/Reader、OutputStream/Writer等)
测试线程安全性的方法:
基本思路:模拟客户端大量并发请求,然后在服务器判断这些请求是否使用了相同的request对象。
判断request对象是否相同,最直观的方式是打印出request对象的地址。然而,几乎所有的web服务器实现中都使用了线程池,这就导致了先后到达的两个请求,可能由同一个线程处理:在前一个请求处理完成后,线程池回收该线程,并将该线程重新分配给后面的请求;而在同一线程中,使用的request对象很可能是同一个(地址相同,属性不同),因此,即便是对于线程安全的方法,不同的请求使用的request对象地址也可能相同。在这里,使用request中的请求参数作为request是否相同的依据。
获取request的方法:
方法1,Controller方法中加参数:
分析:在Controller方法开始处理请求时,Spring会将request对象赋值到方法参数中。此时request对象是方法参数,相当于是局部变量,一定是线程安全的。
缺点:
(1)如果多个controller方法中都需要request对象,那么在每个方法中都需要添加一遍request参数;
(2)request对象的获取只能从controller开始,如果使用request对象的地方在函数调用层级比较深的地方,那么整个调用链上的所有方法都需要添加request参数。
(实际上,除了定时器等特殊情况,request相当于线程内部的一个全局变量,而该方法相当于将一个全局变量传来传去)
方法2,自动注入:
@Autowired
private HttpServletRequest request;
分析:在Springmvc中,Controller是单例的,但是其中注入的request却是线程安全的,因为在这种方式中,Controller初始化时并没有注入一个request对象,而是注入了一个代理proxy,当Controller中需要使用request对象时,通过代理获取request对象(url中有源码解释,最终request是线程局部变量(ThreadLocal),是线程安全的)。
优点:
(1)注入不局限于Controller中,在Service、Repository、普通Bean中都可以注入;
(2)注入对象不局限于request,并保证线程安全;
(3)减少代码冗余。
(不过,如果web系统中有很多controller,每个controller中都会使用request对象(实际上这很常见),这时就需要写很多次注入request的代码,如果再加上response,代码就更繁琐了)
方法3,基类中自动注入:
分析:当创建不同的派生类对象时,基类中的域在不同的派生类对象中会占据不同的内存空间,也就是说将注入request的代码放在基类中对线程安全没有任何影响。
优缺点:
(1)与方法2相比,避免了在不同的Controller中重复注入request,但考虑到Java只允许继承一个基类,所以如果Controller需要继承其它类时,该方法不再好用;
(2)无论方法2还是方法3,都只能在Bean中注入request,如果其它方法(如工具类中的static方法)需要使用request对象,依然需要在调用这些方法时将request参数传进去。
方法4:手动调用:
HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getRequest();
分析:该方法与方法2类似,只不过方法2通过自动注入实现,本方法使用手动调用,线程安全。
优点:可以在非Bean中直接获取;
缺点:代码繁琐,可以与其它方法配合使用
方法5:@ModelAttribute方法:
@ModelAttribute
public void bindRequest(HttpServletRequest request) {
this.request = request;
}
分析:虽然request本身是线程安全的,但是Controller是单例的,request作为单例的一个域,无法保证线程安全。
总结:
除了方法5,方法1-4都是线程安全的。如果系统中request对象使用较少,哪种方式均可;如果使用较多,建议使用方法2/3来减少代码冗余,如果需要在非Bean对象中使用request,既可以通过参数传入,也可以直接在方法中通过手动调用获得。
标签:开发 tco contex hold web服务器 方法 线程池 测试 获取客户端ip
原文地址:https://www.cnblogs.com/yuanfei1110111/p/10137003.html