RequestContextHolder 深度剖析

1,833 阅读3分钟

一、概述

  • RequestContextHolder:持有上下文的Request容器

  • 获取Request对象常见的几种方式, 通过IOC进行获取, 通过传参获取, 通过RequestContextHolder获取

  • 通过RequestContextHolder的静态方法可以随时随地取到当前请求的request对象

    public class RequestContext {
    ​
        public static void setAttr(String key, Object value) {
            HttpServletRequest request = RequestContext.getRequest();
            request.setAttribute(key, value);
        }
    ​
        public static Object getAttr(String key) {
            HttpServletRequest request = RequestContext.getRequest();
            return request.getAttribute(key);
        }
    ​
        protected static ServletRequestAttributes getRequestAttributes() {
            try {
                RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
                return (ServletRequestAttributes) attributes;
            } catch (Exception e) {
                throw new BusinessException("获取RequestAttributes失败!");
            }
        }
    ​
        public static HttpServletRequest getRequest() {
            try {
                return getRequestAttributes().getRequest();
            } catch (Exception e) {
                throw new BusinessException("获取request对象失败!");
            }
        }
    ​
    }
    

二、RequestContextHolder 源码

RequestAttributes 存储在TL中, 这样就可以保证用户线程每次请求的时候都能够拿到一个新的对象, 从而保证在每一次请求中, RequestAttributes都是唯一的

public abstract class RequestContextHolder {
    private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
    /** 得到存储进去的request  */
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
    /** 可被子线程继承的request */
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
​
    public RequestContextHolder() {
    }
​
    public static void resetRequestAttributes() {
        requestAttributesHolder.remove();
        inheritableRequestAttributesHolder.remove();
    }
​
    public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
        setRequestAttributes(attributes, false);
    }
​
    public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        } else if (inheritable) {
            inheritableRequestAttributesHolder.set(attributes);
            requestAttributesHolder.remove();
        } else {
            requestAttributesHolder.set(attributes);
            inheritableRequestAttributesHolder.remove();
        }
​
    }
​
    //直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.
    @Nullable
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
        if (attributes == null) {
            attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
        }
​
        return attributes;
    }
​
    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = RequestContextHolder.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: In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }
​
        return attributes;
    }
​
    private static class FacesRequestAttributesFactory {
        private FacesRequestAttributesFactory() {
        }
​
        @Nullable
        public static RequestAttributes getFacesRequestAttributes() {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            return facesContext != null ? new FacesRequestAttributes(facesContext) : null;
        }
    }
}

三、问题

1. request和response怎么和当前请求挂钩

getRequestAttributes():直接获取ThreadLocal里面的值,保证了每一次获取到的Request是该请求的request.

2. request和response等是什么时候设置进去的

FrameworkServlet类重写了service()、doGet()、doPost() 等方法,方法里面都有一个预处理方法 processRequest(request, response); 查看 processRequest(request, response) 的实现:

  1. 获取上一个请求的参数
  2. 重新建立新的参数
  3. 设置到XXContextHolder
  4. 处理父类的service()处理请求
  5. 恢复request 发布事件

processRequest 源码

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // 获取上一个请求保存的LocaleContext
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        // 建立新的LocaleContext
        LocaleContext localeContext = buildLocaleContext(request);
        // 获取上一个请求保存的RequestAttributes
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        // 建立新的ServletRequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
 
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        // 将ServletRequestAttributes设置到RequestContextHolder中, 这也是为什么RequestAttributes可以强转的原因
        initContextHolders(request, localeContext, requestAttributes);
 
        try {
            // 执行对应的业务方法
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }
 
        finally {
            // 移除TL和ITL中的数据
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
 
            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }
            //发布事件
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

initContextHolders(request, localeContext, requestAttributes)

把新的RequestAttributes设置进LocalThread,实际上保存的类型为ServletRequestAttributes,这也是为什么在使用的时候可以把RequestAttributes强转为ServletRequestAttributes。

    private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
 
        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
    }
因此RequestContextHolder里面最终保存的为ServletRequestAttributes, 这个类相比RequestAttributes方法增强了很多实现