本文主要分析 HttpServletResponse 注入的线程安全问题
问 HttpServletResponse 是不是线程安全?
这个答案要针对具体的场景才能说明,接下来用3个场景演示 HttpServletResponse 是不是线程安全这个疑问
方法注入
先来看一段例子:
@RequestMapping("methodInject")
public void methodInject(HttpServletResponse myResponse) throws IOException {
myResponse.getWriter().println("methodInject");
}
看到这里忍不住就想问了: 放在参数里面的还能不是线程安全的吗?
线程安全是针对堆区,也就是类的实例字段。
放在栈的变量是不存在线程安不安全的问题的
本地保存
private HttpServletResponse localResponse;
@ModelAttribute
public void local(HttpServletResponse response) {
this.localResponse = response;
}
@RequestMapping("localA")
public void localA() throws IOException {
this.localResponse .getWriter().println("localA");
}
@RequestMapping("localB")
public void localB() throws IOException, InterruptedException {
Thread.sleep(5000);
this.localResponse .getWriter().println("localB");
}
springmvc中提供了一个注解@ModelAttribute
,在请求执行之前会执行这部分代码。
那么根据这个思路,可以我们是不是可以在BaseController
里面封装HttpServletResponse
,然后保存到成员变量
以后就可以直接使用了呢?
结果:
先访问localB,后访问localA。 请求localA返回: localB localA ,请求localB返回空
也就是说上面的猜想是不可行的,通过方法注入的 HttpServletResponse 不是线程安全的
实例字段注入
@Autowired
private HttpServletResponse response;
@RequestMapping("memberA")
public void memberA() throws IOException {
response.getWriter().println("memberA");
}
@RequestMapping("memberB")
public void memberB() throws IOException, InterruptedException {
Thread.sleep(5000);
response.getWriter().println("memberB");
}
先访问memberB,后访问memberA. memberB等待5秒输出
memberB
,memberA输出memberA
对于这个结果感到特别意外。
为什么实例字段注入的就是线程安全的,而通过方法注入就不是线程安全的呢?
response实现类
分别断点在 memberA , methodInject 断点, 查看他们的实现类是什么
在方法注入时
HttpServletResponse
的实现类是ResponseFacade
,不是一个ThreadLocal类型的变量,所以不是线程安全在实例字段注入时,实现类是
AutowireUtils.ObjectFactoryDelegatingInvocationHandler
他也不是一个线程安全的类,但是他是一个JDK动态代理的执行类,实现了InvocationHandler接口
最终的从requestAttributesHolder.get().getResponse()获得response, requestAttributesHolder是一个ThreadLocal.
接下来按照 response 通过 objectFactory.getObject() -> currentRequestAttributes().getResponse() 获得
currentRequestAttributes 通过 RequestContextHolder.currentRequestAttributes() -> requestAttributesHolder.get() 获得
requestAttributesHolder 定义是一个ThreadLocal类型的变量。 记录了HttpServletRequest,HttpServletResponse,等对象。
获取response 的详细代码见最下方的获取response
结论
- 通过 Autowired 注入的实例字段 HttpServletRequest,HttpServletResponse 都是线程安全的
- 通过方法注入的 HttpServletResponse 的实现类是 ResponseFacade
- 通过Autowired注入的 HttpServletResponse 的实现类是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler
感谢好友 Docker
提出的疑问,才能总结出这篇文章
获取response
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
private final ObjectFactory<?> objectFactory;
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of proxy.
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
//objectFactory 实现类是 ResponseObjectFactory
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
private static class ResponseObjectFactory implements ObjectFactory<ServletResponse>, Serializable {
@Override
public ServletResponse getObject() {
ServletResponse response = currentRequestAttributes().getResponse();
if (response == null) {
throw new IllegalStateException("Current servlet response not available - " +
"consider using RequestContextFilter instead of RequestContextListener");
}
return response;
}
@Override
public String toString() {
return "Current HttpServletResponse";
}
}
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
"Are you referring to request attributes outside of an actual web request, " +
"or processing a request outside of the originally receiving thread? " +
"If you are actually operating within a web request and still receive this message, " +
"your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
private static ServletRequestAttributes currentRequestAttributes() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Current request is not a servlet request");
}
return (ServletRequestAttributes) requestAttr;
}
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}