SpringMVC 源码分析之 DispatcherServlet

前面松哥和你们聊了 DispatcherServlet 的父类 FrameworkServlet,你们从中了解到在 DispatcherServlet 中,方法执行的入口应该是 doService。若是小伙伴们还没看前面的分析,能够先看下,这有助于理解本文,传送门SpringMVC 源码分析之 FrameworkServletjava

即便你没看过 DispatcherServlet 的源码,估计也据说过:DispatcherServlet 是 SpringMVC 的大脑,它负责整个 SpringMVC 的调度工做,是 SpringMVC 中最最核心的类,SpringMVC 整个顶层架构设计都体如今这里,因此搞明白 DispatcherServlet 的源码,基本上 SpringMVC 的工做原理也就了然于胸了。ios

通过上篇文章的分析,你们已经知道 DispatcherServlet 的入口方法是 doService,因此今天咱们就从 doService 方法开始看起,松哥将带领你们,一步一步揭开 DispatcherServlet 的面纱。浏览器

doService

先来看 doService,把源码先贴上来,而后咱们逐步分析:架构

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);
    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    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));
            }
        }
    }
    // Make framework objects available to handlers and view objects.
    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 {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
    }
}

这里的代码并不长,咱们来稍微分析一下:app

  1. 首先判断当前请求是否是 include 请求,若是是 include,则对 request 的 attribute 作一个快照备份,在最后的 finally 中再对备份的属性进行还原。
  2. 接下来对 request 设置一些常见属性,例如应用上下文、国际化的解析器、主题解析器等等,这些东西在初始化的时候已经准备好了,这里只是应用(初始化过程参见SpringMVC 初始化流程分析一文)。
  3. 接下来处理 flashMap,若是存在 flashMap 则进行复原,这一块松哥在以前的文章中和小伙伴们已经分享过了,传送门SpringMVC 中的参数还能这么传递?涨姿式了!
  4. 接下来处理 RequestPath,将请求路径对象化以备后续使用(在后面的请求映射匹配时会用到)。
  5. 调用 doDispatch 方法进行下一步处理。
  6. 还原快照属性、还原 RequestPath。

因此说这段代码并不难理解,它的核心在于 doDispatch 方法,因此接下来咱们就来看看 doDispatch 方法。异步

doDispatch

doDispatch 方法所作的事情就比较多了,咱们来看下:async

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);
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // Process last-modified header, if supported by the handler.
            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;
            }
            // Actually invoke the handler.
            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);
        }
        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()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

这个方法比较长,涉及到不少组件的处理,这里松哥先和你们把思路梳理畅通,各个组件的详细用法松哥将在之后的文章中和你们仔细分享。源码分析

doDispatch 方法其实主要作了两方面的事情:请求处理以及页面渲染,咱们先来看看初始变量的含义:post

  1. processedRequest:这个用来保存实际上所用的 request 对象,在后面的流程中会对当前 request 对象进行检查,若是是文件上传请求,则会对请求从新进行封装,若是不是文件上传请求,则继续使用原来的请求。
  2. mappedHandler:这是具体处理请求的处理器链,处理器链包含两方面的东西:请求处理器和对应的 Interceptor。
  3. multipartRequestParsed:表示是不是文件上传请求的标记。
  4. asyncManager:这是一个异步请求管理器。
  5. mv:这是最终渲染返回的 ModelAndView 对象。
  6. dispatchException:表示请求处理过程当中所抛出的异常,这个异常不包括渲染过程抛出的异常。

接下来再来看看具体的处理逻辑:this

  1. 首先经过 checkMultipart 检查是否是文件上传请求,若是是,则对当前 request 从新进行包装,若是不是,则直接将参数返回。
  2. 若是 processedRequest 不等于 request,则说明当前请求是文件上传请求(request 在 checkMultipart 方法中被从新封装了),不然说明当前请求不是文件上传请求。
  3. 根据当前请求,调用 getHandler 方法获取请求处理器,若是没找到对应的请求处理器,则调用 noHandlerFound 方法抛出异常或者给出 404。
  4. 接下来再调用 getHandlerAdapter 方法,根据当前的处理器找处处理器适配器。
  5. 而后处理 GET 和 HEAD 请求头的 Last_Modified 字段。当浏览器第一次发起 GET 或者 HEAD 请求时,请求的响应头中包含一个 Last-Modified 字段,这个字段表示该资源最后一次修改时间,之后浏览器再次发送 GET、HEAD 请求时,都会携带上该字段,服务端收到该字段以后,和资源的最后一次修改时间进行对比,若是资源尚未过时,则直接返回 304 告诉浏览器以前的资源仍是能够继续用的,若是资源已通过期,则服务端会返回新的资源以及新的 Last-Modified。
  6. 接下来调用拦截器的 preHandle 方法,若是该方法返回 false,则直接 return 掉当前请求(拦截器的用法你们能够参考松哥以前录的免费的 SpringMVC 视频教程,里边有讲,传送门硬核!松哥又整了一套免费视频,搞起!)。
  7. 接下来执行 ha.handle 去调用真正的请求,获取到返回结果 mv。
  8. 接下来判断当前请求是否须要异步处理,若是须要,则直接 return 掉。
  9. 若是不须要异步处理,则执行 applyDefaultViewName 方法,检查当前 mv 是否没有视图,若是没有(例如方法返回值为 void),则给一个默认的视图名。
  10. 接下来调用 applyPostHandle 方法执行拦截器里边的 postHandle 方法。
  11. 接下来调用 processDispatchResult 方法对执行结果进行处理,包括异常处理、渲染页面以及执行拦截器的 afterCompletion 方法都在这里完成。
  12. 最后在 finally 代码块中判断是否开启了异步处理,若是开启了,则调用相应的拦截器;若是请求是文件上传请求,则再调用 cleanupMultipart 方法清除文件上传过程产生的一些临时文件。

这是 doDispatch 方法的一个大体执行逻辑,doDispatch 里边的 try-catch 有两层,最里边那一层,抛出来的异常会被赋值给 dispatchException 变量,这些异常最终在 processDispatchResult 方法中被处理掉,外面的异常则是 processDispatchResult 方法在执行的过程当中抛出的异常,通常来讲主要是页面渲染时候的异常。

processDispatchResult

最后咱们再来看下 processDispatchResult 方法的执行逻辑:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
    }
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }
    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

能够看到,在 processDispatchResult 方法中首先对异常进行了处理,配置好异常对应的 ModelAndView,而后调用 render 方法对页面进行渲染,最后经过 triggerAfterCompletion 方法去触发拦截器的 afterCompletion 方法。

小结

至此,咱们就把一个请求的大体流程和你们梳理完了,松哥画了一张流程图咱们一块儿来看下:

这下相信你们对 doDispatch 方法比较熟悉了,固然这里还涉及到不少组件,这些组件松哥将在后面的文章中和你们逐一进行分析。

相关文章
相关标签/搜索