首先,我使用了Spring,意味着对象都是通过IOC容器进行管理。容器会帮我们创建和维护对象,但是为了提高性能,这些对象都是单例的,当然,你也可以通过设置scope改变为多例。
所以我一般不能在Spring管理的对象中使用成员变量,那样会出现线程安全问题。
@ModelAttribute是SpringMVC中一个常用的注解,使用它注释的方法会在Controller方法执行前执行
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
......
//执行@ModelAttribute注解的方法
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
......
//执行Controller中的方法
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
}
网上有这样一种错误的用法:
public class BaseController {
private HttpServletRequest request;
private HttpServletResponse response;
@ModelAttribute
public void setReqAndRes(HttpServletRequest request,
HttpServletResponse response) {
this.request = request;
this.response = response;
}
}
这样虽然能让我们不用每次都注入一些常用的对象,但是这样的做法会出现线程安全问题,千万别这样用。除非你设置为多例,但是性能会下降。
不过这里值得一提的是,如果你将request用@resource或者@Autowired注解注入,是不会出现线程安全问题的。
引用网友小杨vita的解释
- 在controller中注入的request是jdk动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例.当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法.这里的objectFactory是RequestObjectFactory.
- RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的.
- 请求刚进入springmvc的dispatcherServlet的时候会把request相关对象设置到RequestContextHolder的threadlocal中去.
我们可以发现,SpringMVC是通过ThreadLocal对象来保证线程安全的。那么我们是不是也可以这样做?于是上面线程不安全代码可以改为如下内容:
public class BaseController {
private static final ThreadLocal<HttpServletRequest> requestContainer = new ThreadLocal<>();
private static final ThreadLocal<HttpServletResponse> responseContainer = new ThreadLocal<>();
@ModelAttribute
public void setReqAndRes(HttpServletRequest request,
HttpServletResponse response) {
requestContainer.set(request);
responseContainer.set(response);
}
public HttpServletRequest getRequest() {
return requestContainer.get();
}
public HttpServletResponse getResponse() {
return responseContainer.get();
}
}
网友评论