微信搜索
江南一点雨

SpringMVC 源码分析之 DispatcherServlet

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

即使你没看过 DispatcherServlet 的源码,估计也听说过:DispatcherServlet 是 SpringMVC 的大脑,它负责整个 SpringMVC 的调度工作,是 SpringMVC 中最最核心的类,SpringMVC 整个顶层架构设计都体现在这里,所以搞明白 DispatcherServlet 的源码,基本上 SpringMVC 的工作原理也就了然于胸了。

经过上篇文章的分析,大家已经知道 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);
    }
}

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

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

所以说这段代码并不难理解,它的核心在于 doDispatch 方法,所以接下来我们就来看看 doDispatch 方法。

doDispatch

doDispatch 方法所做的事情就比较多了,我们来看下:

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 方法其实主要做了两方面的事情:请求处理以及页面渲染,我们先来看看初始变量的含义:

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

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

  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 方法比较熟悉了,当然这里还涉及到很多组件,这些组件松哥将在后面的文章中和大家逐一进行分析。

赞(4)
未经允许不得转载:江南一点雨 » SpringMVC 源码分析之 DispatcherServlet
分享到: 更多 (0)
扫码关注微信公众号【江南一点雨】,回复 1024,查看松哥原创 Java 实战教程(图文+视频)。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

专注 Java 一百年

关注我们国际站