Java

SpringMVC 篇(二)DispatcherServlet

2021-01-23  本文已影响0人  Tubetrue01

引言

前一篇文章 【SpringMVC 篇(一)DispatcherServlet 初始化】 中介绍了刷新的触发、基础组建的加载以及以 HandlerMapping 为例讲解了一下 url 与 controller 的映射。那么这篇文章将继续介绍 DispatcherServlet 关于请求处理的部分。

请求

在开始介绍 DispatcherServlet 处理请求之前,我们回忆一下原始的 servlet 是如何处理请求的。

Servlet

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

servlet 是一个接口,该接口中的 service 方法可以说是核心方法,所有的请求都会由该方法进行调度。根据请求的方法来引导你到 doXXX 系列的方法。而 doXXX 系列的方法并没有在该接口中进行声明,而是放到了它的子类 HttpServlet 中。而 DispatcherServlet 也是一个 servlet,所以它也按照该流程处理请求。

时序图

我们先看一下请求是如何通过 HttpServlet 以及 FrameworkServlet 中转到 DispatcherServlet 的。

image.png

先对请求进行分析:

  1. 请求进入 HttpServlet 的 service(ServletRequest, ServletResponse) 方法,该方法将 ServletRequest 、ServletResponse 转化为 HttpServletRequest、HttpServletResponse。
  2. HttpServlet 调用 FrameworkServlet 的 service(ServletRequest, ServletResponse) 方法。该方法对请求的 Http 方法进行判断,然后调用 HttpServlet 的 service(ServletRequest, ServletResponse) 方法。
  3. HttpServlet 的 service(ServletRequest, ServletResponse) 方法根据请求调用 FrameworkServlet 的 doXXX 系列方法。
  4. FrameworkServlet 的 doXXX 方法调用 processRequest 方法,该方法将请求转到 DispatcherServlet 的 doService() 方法。
  5. DispatcherServlet 的 doService() 方法调用 doDispatcher() 方法。

过程比较绕,但是最终调用的其实就是 DispatcherServlet 的 doDispatch() 方法了,不过在介绍它之前,我想有必要从 FrameworkServlet 的 processRequest() 方法开始。看它有没有对请求对象做处理。

FrameworkServlet

DispatcherServlet 的直接父类。

FrameworkServlet

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // 从当前线程或其子线程中获取原始本地化上下文对象
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        // 从请求对象中获取本地化信息,然后构建出本地化上下文对象
        LocaleContext localeContext = buildLocaleContext(request);
        // 从当前线程及其子线程中获取原始 RequestAttributes 对象
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        // 根据当前请求、响应对象以及原始 previousAttributes 创建 requestAttributes  对象
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        // 创建异步管理器,并与当前请求对象关联(设置到 request 属性表中)
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);  
        // 为异步管理器注册拦截器(设置 ContextHolder 对象,以及请求之后将其清空)
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        // 分别初始化 localeContextHolder 以及 requestContextHolder 对象
        initContextHolders(request, localeContext, requestAttributes);

        try {
            // 这里开始调用 DispatcherServlet 中的 doService() 方法
            doService(request, response);
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            // 请求完成之后,会将之前的 context 对象恢复到 ContextHolder 中
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            // 发布事件 
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

这个方法主要的作用就是获取之前设置的 context 对象,然后为当前 request 建立 ContextHolder (ThreadLocal)对象,并在请求完毕之后将其恢复到之前获取的 context 对象(如果为空就直接清除),同时发布处理事件。这里有个疑问,就是既然先获取之前的 context 对象,那么肯定有个入口会设置该值,那么是什么时候设置的呢?而且,请求之后必须要清空 ContextHolder ,那么这里并没有看到相关的清空操作。那么 Spring 是怎么做的呢?大家可以思考一下。
我们继续分析,打开 DispatcherServlet 的 doService() 方法。

doService

doService 是一个覆盖的方法,供父类调用,而该方法只是做一下请求的准备工作,然后将具体请求处理交由 doDispatch 方法。

DispatcherServlet

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
        // 这里就是对 request 中的属性进行快照备份
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // 将相关的解析器、容器保存到 request 中
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }

        RequestPath previousRequestPath = null;
        if (this.parseRequestPath) {
            previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
            ServletRequestPathUtils.parseAndCache(request);
        }

        try {
            // 调用 doDispatcher 处理请求
            doDispatch(request, response);
        }
        finally {
            // 同步方法的处理 
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // 恢复原始属性快照
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
        }
    }

doDispatch

doDispatch 是处理请求的核心方法

DispatcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 待处理的请求 
        HttpServletRequest processedRequest = request;
        // 请求的完成链,包括拦截器等 
        HandlerExecutionChain mappedHandler = null;
        // 文件上传解析 
        boolean multipartRequestParsed = false;
        // 从当前请求中获取异步处理器(这里获取的其实都是空的处理器,因为程序目前还不知道当前请求是不是异步处理的,后续的文章在进行相关介绍)
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                // 判断当前请求是否是文件上传请求 
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 获取当前请求的处理器
                mappedHandler = getHandler(processedRequest);
                // 404 
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 获取当前处理的处理器适配器,用于真实的处理请求
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                // 如果是 Get、Header 方法的话,获取最近修改时间,进行相关的优化操作
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 前置拦截器
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 开始处理请求
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                // 如果是异步处理,直接返回
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                // 后置拦截器(如果是异步的话,到这之前已经返回了)
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 最后的结果、视图、异常的处理,当然拦截器中的 afterCompletion() 方法也会在这一步进行调用(异步处理除外)
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // 前边提到一点,如果是异步请求的话,postHandle 以及 afterCompletion 方法是不会生效的,所以这里是针对于异步请求的拦截器处理
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // 如果是文件上传请求,这里会对资源进行处理
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

相关的说明已经详细的在代码中标注了。大体上我们可以总结如下:

  1. 每个请求都会赋予一个异步管理器。
  2. 拦截器 preHandle() 方法的调用。
  3. 真实请求的调用。
  4. 如果是异步请求,那么拦截器的 postHandle() 和 afterCompletion() 方法是不会生效的,这里需要使用 AsyncHandlerInterceptor 拦截器。

RequestContextFilter

还记得我们之前提到的就是 ContextHolder 是什么时候进行属性配置的吗?其实在请求进来的时候,先经过一轮轮的 Filter 处理,其中之一的 RequestContextFilter 就是用来做这个工作的。我们看一下它的代码:

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
        // 进行 ContextHolder 的初始化工作,这里会初始化两个 ContextHolder,一个是 LocaleContextHolder ,一个是 RequestContextHolder。
        initContextHolders(request, attributes);

        try {
            // 请求向下传递
            filterChain.doFilter(request, response);
        }
        finally {
            // 请求完成之后,为了避免内存泄漏,这里需要将 ContextHolder 重置。
            resetContextHolders();
            if (logger.isTraceEnabled()) {
                logger.trace("Cleared thread-bound request context: " + request);
            }
            attributes.requestCompleted();
        }
    }

    // 初始化 ContextHolder
    private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
        LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
    }
    // 重置 ContextHolder
    private void resetContextHolders() {
        LocaleContextHolder.resetLocaleContext();
        RequestContextHolder.resetRequestAttributes();
    }

}

代码很简单,无非就是请求之前初始化 ContextHolder,请求之后清空 ContextHolder。我们在项目中常用的 RequestContextHolder 是怎么来的,到这里我想大家已经豁然开朗了。


尾声

这篇文章我们介绍了请求的处理流程,把 DispatcherServlet 核心方法介绍了一遍。有人可能会提出疑问,请求的调用讲的并不是很详细,@RequestBody 以及 @ResponseBody 的处理都没提到。不要紧,因为接下来的系列文章 【SpringMVC 篇 消息转换器】 会对请求接下来的处理进行详细的介绍。

上一篇下一篇

猜你喜欢

热点阅读