HttpServletRequest Thread Safety

spring对HttpServletRequest和HttpServletResponse线程安全问题

Overview

最近在工作中看到了这样一段代码

public abstract class GeneralController {

    @Autowired
    protected HttpServletRequest httpServletRequest;

    @Autowired
    protected HttpServletResponse httpServletResponse;
}

@RestController
public class Controller extends GeneralController {
     this.httpServletResponse.setContentType("application/xml");
     this.httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
     this.httpServletResponse.setHeader("Content-Disposition", "attachment;fileName=" + inboundTypeCode + ".xsd");
     this.httpServletResponse.setHeader("Pragma", "no-cache");
     this.httpServletResponse.setHeader("Expires", "0");
     try
     {
          this.httpServletResponse.getOutputStream().write(xml.getBytes());
     }
     catch (IOException e)
     {
          EmsLogger.error(e.getLocalizedMessage(), e);
     }
}

这是一个简单的xml下载的接口。但是有个细节,HttpServletRequest和HttpServletResponse都是通过@Autowired进来.我们知道,默认Autowired都是singleton的,这个地方在多请求访问时,肯定会有线程安全的问。但是在实际debug过程中,发现这里是线程安全的。


How do spring keep thread safety

这是使用requst时的事情
注入

I get the solution in spring IOC process. The question how do spring generate the bean is the key to solving the problem. So I find that:
AbstractRefreshableWebApplicationContext.class


Alttext

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
  beanFactory.ignoreDependencyInterface(ServletContextAware.class);
  beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

  WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
  WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

这个方法中
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);是关键。

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
  beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
  beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
  beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
  if (sc != null) {
    ServletContextScope appScope = new ServletContextScope(sc);
    beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
    // Register as ServletContext attribute, for ContextCleanupListener to detect it.
    sc.setAttribute(ServletContextScope.class.getName(), appScope);
  }

  beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
  beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
  beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
  beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
  if (jsfPresent) {
    FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
  }
}

可以看到,ServletRequest和ServletResponse和HttpSession都是做了特殊处理,让我们来看看这个工厂类,以RequestObjectFactory为例子,其他两个都相似,这是一个内部静态类

@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

  @Override
  public ServletRequest getObject() {
    return currentRequestAttributes().getRequest();
  }

  @Override
  public String toString() {
    return "Current HttpServletRequest";
  }
}

这里重写了getObject()方法,通过工厂模式生成bean。
终于到了这个问题的关键了:
currentRequestAttributes()方法

/**
 * Return the current RequestAttributes instance as ServletRequestAttributes.
 * @see RequestContextHolder#currentRequestAttributes()
 */
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;
}

RequestContextHolder

public abstract class RequestContextHolder  {

    ...

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");

    ...

    /**
     * Return the RequestAttributes currently bound to the thread.
     * @return the RequestAttributes currently bound to the thread,
     * or {@code null} if none bound
     */
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }

    /**
     * Return the RequestAttributes currently bound to the thread.
     * <p>Exposes the previously bound RequestAttributes instance, if any.
     * Falls back to the current JSF FacesContext, if any.
     * @return the RequestAttributes currently bound to the thread
     * @throws IllegalStateException if no RequestAttributes object
     * is bound to the current thread
     * @see #setRequestAttributes
     * @see ServletRequestAttributes
     * @see FacesRequestAttributes
     * @see javax.faces.context.FacesContext#getCurrentInstance()
     */
    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;
    }
}

到这里,我们找到了solution of spring. 简单的使用了ThreadLocal方式,确保了线程安全。但是我们还有一个疑问,什么时候调用ThreadLocal的set方法呢?
RequestContextListener.class

@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
  if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
    throw new IllegalArgumentException(
        "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
  }
  HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
  ServletRequestAttributes attributes = new ServletRequestAttributes(request);
  request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
  LocaleContextHolder.setLocale(request.getLocale());
  RequestContextHolder.setRequestAttributes(attributes);
}