我们知道springmvc中request是方法级别的,一个方法对应一个request。那么如果我们把request设置为类级别的变量呢?就像这样:
@Controller
@RequestMapping("/admin")
public class AdminController {
private HttpServletRequest tempRequest;
@ModelAttribute
public void getRequest(HttpServletRequest request){
// 每个方法执行前都将当前的request赋给tempRequest
tempRequest = request;
}
......
}
根据Sprimg的默认规则,controller对于factorybean默认是单例,controller只有一个。我们知道tomcat对于每一个请求都会生成一个处理线程去处理当前请求,那么在并发量高的情况下,我们都去请求例如:public ModelAndView checkAdmin() 这样一个方法,因为我们设置request为类变量,那么势必会造成后一个request覆盖了前一个request的一些请求参数。如果下面这个例子:
private HttpServletRequest tempRequest;
@ModelAttribute
public void getRequest(HttpServletRequest request){
// 每个方法执行前都将当前的request赋给tempRequest
tempRequest = request;
}
@RequestMapping("/checkAdmin")
public void checkAdmin() {
try{
// 这里让请求线程等待10秒,模拟高并发
Thread.currentThread().sleep(10000);
}catch(InterruptedException e){}
String num = tempRequest.getParameter("num");
System.out.println("请求" + tempRequest.hashCode() + ", num = " + num);
}
然后我们在10秒内分别请求:
http://localhost:8081/eHow/admin/checkAdmin?num=1
http://localhost:8081/eHow/admin/checkAdmin?num=2
http://localhost:8081/eHow/admin/checkAdmin?num=3
先看结果:
请求217031088, num = 3
请求217031088, num = 3
请求217031088, num = 3
照道理,应该request 的hashcode(内存地址)不一样,num也不一样。但是通过结果可以发现,虽然说每次请求num都不一样,但是打印结果确是request hashcode相同,num也一样。这是因为我们让请求线程睡眠了10s,10s后再去从tempRequest取值,那么其实tempRequest已经被最后一个覆盖,三次请求其实最后取的都是同一个tempRequest。也就是说,这样设计的request是线程不安全的,并不是将当前request放在当前线程threadlocal中。(threadlocal简单理解就是将一些值与当前线程关联起来,放的时候放在当前线程中,取得时候就从当前线程中去取。)
ok,那么我们试一下,将每次请求的request放在threadlocal中。
// private HttpServletRequest tempRequest;
// 这个threadlocal就是放当前线程的HttpServletRequest变量
private static ThreadLocal<HttpServletRequest> local = new ThreadLocal<HttpServletRequest>();
@ModelAttribute
public void getRequest(HttpServletRequest request){
// 将当前request放入threadlocal中
local.set(request);
}
@RequestMapping("/checkAdmin")
public void checkAdmin() {
try{
Thread.currentThread().sleep(10000);
}catch(InterruptedException e){}
// 取的时候从当前的线程中去取request
HttpServletRequest request = local.get();
String num = request.getParameter("num");
System.out.println("请求" + request.hashCode() + ", num = " + num);
}
请求地址还是和上面一样,看一下结果:
请求2114778512, num = 1
请求693198960, num = 2
请求973234962, num = 3
可以看到,hashcode不一样,num也不同,也就是说确实将每次请求的request放在了当前的线程中,取得时候也可以从当前线程中取得,这样就不存在覆盖的问题,所以说这种方式是线程安全的。
spring应该是考虑到了这一点,所以给我们提供了一个HttpServletRequest的代理bean,通过@AutoWired注入。具体的实现方式其实和我们上面threadlocal差不多。因为controller是单例,所以我们注入一个httpservletrequest的代理bean,当有一个新的请求进来时,就判断当前线程中有没有httpservletrequest对象,没有就在threadlocal中放一个httpservletrequest对象,当我们去取request的一些参数时,代理bean就从当前线程中的httpservletrequest去取。这样就保证了线程安全。
我们试一下:
// 注入代理类
@Autowired
private HttpServletRequest tempRequest;
// 这个threadlocal就是放当前线程的HttpServletRequest变量
// private static ThreadLocal<HttpServletRequest> local = new ThreadLocal<HttpServletRequest>();
// @ModelAttribute
// public void getRequest(HttpServletRequest request){
// // 将当前request放入threadlocal中
// local.set(request);
// }
@RequestMapping("/checkAdmin")
public void checkAdmin() {
try{
Thread.currentThread().sleep(10000);
}catch(InterruptedException e){}
String num = tempRequest.getParameter("num");
System.out.println("请求" + tempRequest.hashCode() + ", num = " + num);
}
请求地址还是和上面一样,看一下结果:
请求368958259, num = 1
请求368958259, num = 2
请求368958259, num = 3
咦,为什么hashcode一样,num不一样,跟上面结果不同?我们刚才说到request在去取参数也就是tempRequest.getParameter(“num”) 的时候代理bean会从当前threadlocal去取request。
我们贴一部分这个代理bean相关的源码,看一下原因:
// 这个就是代理的request bean
private final ObjectFactory objectFactory;
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (methodName.equals("equals")) {
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// 可以看到当方法为hashcode的时候,就会取出代理的hashcode
// 所以hashcode都是一样的
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
// 如果方法是其他
// ObjectFactory的getObjcect就是从当前threadlocal中取数据
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
public ServletRequest getObject() {
// currentReuqestAttributes负责从Threadlocal中获取对象
return currentRequestAttributes().getRequest();
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
ok,也就是说除了equals、hashcode以及toString以外的其他方法都是从threadlocal中取。所以才会造成上面hashcode一样,num不一样的情况。所以这样也是线程安全的。
还有一种线程安全,就是方法级别的reqeust。
// 注入代理类
// @Autowired
// private HttpServletRequest tempRequest;
// 这个threadlocal就是放当前线程的HttpServletRequest变量
// private static ThreadLocal<HttpServletRequest> local = new ThreadLocal<HttpServletRequest>();
// @ModelAttribute
// public void getRequest(HttpServletRequest request){
// // 将当前request放入threadlocal中
// local.set(request);
// }
@RequestMapping("/checkAdmin")
public void checkAdmin(HttpServletRequest request){
try{
Thread.currentThread().sleep(10000);
}catch(InterruptedException e){}
String num = request.getParameter("num");
System.out.println("请求" + request.hashCode() + ", num = " + num);
}
看结果:
请求1814088687, num = 1
请求2071623777, num = 2
请求1324577120, num = 3
可以看到每次输出也是不同的。因为每次请求都是一个不同的request,每个request都是方法级别的,独享的,也就不存在前面覆不覆盖的问题了。
以上就是对springmvc request线程安全的一些测试。