关于java:SpringMVC-源码分析之-DispatcherServlet

36次阅读

共计 7858 个字符,预计需要花费 20 分钟才能阅读完成。

后面松哥和大家聊了 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 办法比拟相熟了,当然这里还波及到很多组件,这些组件松哥将在前面的文章中和大家逐个进行剖析。

正文完
 0