跳至主要內容

5.一个请求的旅行过程

Java突击队大约 23 分钟

5.一个请求的旅行过程

Spring 版本:5.1.14.RELEASE

该系列其他文档请查看:[《死磕 Spring MVC 源码分析 - 文章导读》][Spring MVC _ -]

在上一篇[《WebApplicationContext 容器的初始化》][WebApplicationContext]文档中分析了 Spring MVC 是如何创建两个容器的,其中创建Root WebApplicationContext 后,调用其refresh()方法会触发刷新事件,完成 Spring IOC 初始化相关工作,会初始化各种 Spring Bean 到当前容器中,该系列文档暂不分析

我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后,才能更好的理解具体的源码,回过头再来看则会更加的豁然开朗

整体流程图

![ ][nbsp]

Spring MVC 处理请求的流程大致如上图所示

1、 用户的浏览器发送一个请求,这个请求经过互联网到达了我们的服务器Servlet容器首先接待了这个请求,并将该请求委托给DispatcherServlet进行处理;
2、 DispatcherServlet将该请求传给了处理器映射组件HandlerMapping,并获取到适合该请求的HandlerExecutionChain拦截器和处理器对象;
3、 在获取到处理器后,DispatcherServlet还不能直接调用处理器的逻辑,需要进行对处理器进行适配处理器适配成功后,DispatcherServlet通过处理器适配器HandlerAdapter调用处理器的逻辑,并获取返回值ModelAndView对象;
4、 之后,DispatcherServlet需要根据ModelAndView解析视图解析视图的工作由ViewResolver完成,若能解析成功,ViewResolver会返回相应的View视图对象;
5、 在获取到具体的View对象后,最后一步要做的事情就是由View渲染视图,并将渲染结果返回给用户;

以上就是 Spring MVC 处理请求的全过程,上面的流程进行了一定的简化,主要涉及到最核心的组件,还有许多其他组件没有表现出来,不过这并不影响大家对主过程的理解。

组件预览

在上一篇[《WebApplicationContext 容器的初始化》][WebApplicationContext]文档讲述 FramworkServlet 的 onRefresh 方法时,该方法由 DispatcherServlet 去实现,会初始化九大组件,如何初始化的这里暂时不展开讨论,默认会从 spring-webmvc 下面的 DispatcherServlet.properties 文件中读取组件的实现类,感兴趣可以先阅读一下源码😈,后续会依次描述

那么接下来就简单介绍一下 DispatcherServlet 和九大组件:

组件说明
DispatcherServletSpring MVC 的核心组件,是请求的入口,负责协调各个组件工作
MultipartResolver内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件
HandlerMapping请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors
HandlerAdapter处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器
HandlerExceptionResolver处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果
RequestToViewNameTranslator视图名称转换器,用于解析出请求的默认视图名
LocaleResolver本地化(国际化)解析器,提供国际化支持
ThemeResolver主题解析器,提供可设置应用整体样式风格的支持
ViewResolver视图解析器,根据视图名和国际化,获得最终的视图 View 对象
FlashMapManagerFlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session)

Spring MVC 对各个组件的职责划分的比较清晰。DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。

ThemeResolver 和 FlashMapManager 组件在该系列文档中不会进行讲解,因为几乎接触不到,感兴趣的可以去 Google 一下,嘻嘻~😅 笔者没接触过

FrameworkServlet

虽然在上面的整体流程图中,我们看到请求是直接被 DispatcherServlet 所处理,但是实际上,FrameworkServlet 才是真正的入口,再来回顾一个 DispatcherServlet 的类图,如下:

![ ][nbsp 1]

FrameworkServlet 覆盖了 HttpServlet 的以下方法:

  • doGet(HttpServletRequest request, HttpServletResponse response)
  • doPost(HttpServletRequest request, HttpServletResponse response)
  • doPut(HttpServletRequest request, HttpServletResponse response)
  • doDelete(HttpServletRequest request, HttpServletResponse response)
  • doOptions(HttpServletRequest request, HttpServletResponse response)
  • doTrace(HttpServletRequest request, HttpServletResponse response)
  • service(HttpServletRequest request, HttpServletResponse response)

这些方法分别处理不同 HTTP 请求类型的请求,最终都会调用另一个 processRequest(HttpServletRequest request, HttpServletResponse response) 方法

其中doGetdoPostdoPutdoDelete 四个方法是直接调用 processRequest 方法的

service

service(HttpServletRequest request, HttpServletResponse response) 方法,用于处理请求,方法如下:

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // <1> 获得请求方法
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    // <2> 处理 PATCH 请求
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
        processRequest(request, response);
    }
    // <3> 处理其他类型的请求
    else {
        super.service(request, response);
    }
}

1、 获得HttpMethod请求方法;
2、 若请求方法是PATCH,调用processRequest(HttpServletRequestrequest,HttpServletResponseresponse)方法,处理请求;

因为 HttpServlet 默认没提供处理 Patch 请求类型的请求 ,所以只能通过覆盖父类的 service 方法来实现
3、 如果是其他类型的请求,则直接调用父类的service方法,该方法如下:;

// HttpServlet.java
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        // Note that this means NO servlet supports whatever method was requested, anywhere on this server.
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

可能你会有疑惑,为什么不在 service(HttpServletRequest request, HttpServletResponse response) 方法,直接调用 processRequest(HttpServletRequest request, HttpServletResponse response) 方法就好了?因为针对不同的请求方法,处理略微有所不同,父类 HttpServlet 已经处理了。

doOptions

doOptions(HttpServletRequest request, HttpServletResponse response)方法,用于处理 OPTIONS 类型的请求,方法如下:

@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // 如果 dispatchOptionsRequest 为 true ,则处理该请求,默认为 true
    if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
        // 处理请求
        processRequest(request, response);
        // 如果响应 Header 包含 "Allow" ,则不需要交给父方法处理
        if (response.containsHeader("Allow")) {
            // Proper OPTIONS response coming from a handler - we're done.
            return;
        }
    }

    // Use response wrapper in order to always add PATCH to the allowed methods
    // 调用父方法,并在响应 Header 的 "Allow" 增加 PATCH 的值
    super.doOptions(request, new HttpServletResponseWrapper(response) {
        @Override
        public void setHeader(String name, String value) {
            if ("Allow".equals(name)) {
                value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
            }
            super.setHeader(name, value);
        }
    });
}

使用场景:AJAX 进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全

doTrace

doTrace(HttpServletRequest request, HttpServletResponse response)方法,用于处理 TRACE 类型的请求,方法如下:

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

    // 如果 dispatchTraceRequest 为 true ,则处理该请求,默认为 false
    if (this.dispatchTraceRequest) {
        // 处理请求
        processRequest(request, response);
        // 如果响应的内容类型为 "message/http" ,则不需要交给父方法处理
        if ("message/http".equals(response.getContentType())) {
            // Proper TRACE response coming from a handler - we're done.
            return;
        }
    }
    // 调用父方法
    super.doTrace(request, response);
}

回显服务器收到的请求,主要用于测试或诊断,笔者目前还没接触过😈

processRequest

processRequest(HttpServletRequest request, HttpServletResponse response) 方法,用于处理请求,方法如下:

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

    // <1> 记录当前时间,用于计算处理请求花费的时间
    long startTime = System.currentTimeMillis();
    // <2> 记录异常,用于保存处理请求过程中发送的异常
    Throwable failureCause = null;

    // <3> TODO
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    // <4> TODO
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    // <5> TODO
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // <6> TODO
    initContextHolders(request, localeContext, requestAttributes);

    try {
        // <7> 执行真正的逻辑
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex; // <8> 记录抛出的异常
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex; // <8> 记录抛出的异常
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        // <9> TODO
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        // <10> TODO
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        // <11> 如果日志级别为 DEBUG,则打印请求日志
        logResult(request, response, failureCause, asyncManager);
        // <12> 发布 ServletRequestHandledEvent 请求处理完成事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

<1> 记录当前时间,用于计算处理请求花费的时间

<2> 记录异常,用于保存处理请求过程中发送的异常

<7> 【核心】调用 doService(HttpServletRequest request, HttpServletResponse response) 抽象方法,执行真正的逻辑,由 DispatcherServlet 实现,所以这就是 DispatcherServlet 处理请求的真正入口

<8> 记录执行过程抛出的异常,最终在 finally 的代码段中使用。

<11> 如果日志级别为 DEBUG,则打印请求日志

<12> 调用 publishRequestHandledEvent 方法,通过 WebApplicationContext 发布 ServletRequestHandledEvent 请求处理完成事件,目前好像 Spring MVC 没有监听这个事件,可以自己写一个监听器用于获取请求信息,示例如下:

@Component
@Log4j2
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent>{
	@Override
	public void onApplicationEvent(ServletRequestHandledEvent event) {
		log.info("请求描述:{}", event.getDescription());
		log.info("请求路径:{}", event.getRequestUrl());
        log.info("开始时间:{}", event.getTimestamp());
		log.info("请求耗时:{}", event.getProcessingTimeMillis());
		log.info("状 态 码:{}", event.getStatusCode());
        log.info("失败原因:{}", event.getFailureCause());
	}
}

到这里,FrameworkServlet 算是讲完了,接下来就要开始讲 DispatcherServlet 这个核心类了

DispatcherServlet

org.springframework.web.servlet.DispatcherServlet核心类,作为 Spring MVC 的核心类,承担调度器的角色,协调各个组件进行工作,处理请求,一起来揭开这神秘的面纱吧😈

静态代码块

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

private static final Properties defaultStrategies;

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }
}

会从DispatcherServlet.properties 文件中加载默认的组件实现类,将相关配置加载到 defaultStrategies 中,文件如下:

### org.springframework.web.servlet.DispatcherServlet.properties

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

可以看到各个组件的默认实现类

构造方法

/** MultipartResolver used by this servlet. multipart 数据(文件)处理器 */
@Nullable
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet. 语言处理器,提供国际化的支持 */
@Nullable
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet. 主题处理器,设置需要应用的整体样式 */
@Nullable
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet. 处理器匹配器,返回请求对应的处理器和拦截器们 */
@Nullable
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet. 处理器适配器,用于执行处理器 */
@Nullable
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet. 异常处理器,用于解析处理器发生的异常 */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet. 视图名称转换器 */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet. FlashMap 管理器,负责重定向时保存参数到临时存储(默认 Session)中 */
@Nullable
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet. 视图解析器,根据视图名称和语言,获取 View 视图 */
@Nullable
private List<ViewResolver> viewResolvers;

public DispatcherServlet() {
    super();
    setDispatchOptionsRequest(true);
}

public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
    setDispatchOptionsRequest(true);
}

定义了九个组件,在组件预览中已经做过简单介绍了

构造方法中都会设置 dispatchOptionsRequesttrue,在父类 FrameworkServlet 中可以看到,如果请求是 OPTIONS 则会处理请求

onRefresh

onRefresh(ApplicationContext context) 方法,初始化 Spring MVC 的各个组件,方法如下:

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    // 初始化 MultipartResolver
    initMultipartResolver(context);
    // 初始化 LocaleResolver
    initLocaleResolver(context);
    // 初始化 ThemeResolver
    initThemeResolver(context);
    // 初始化 HandlerMapping
    initHandlerMappings(context);
    // 初始化 HandlerAdapter
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolver
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolver
    initViewResolvers(context);
    // 初始化 FlashMapManager
    initFlashMapManager(context);
}

创建 Servlet WebApplicationContext 容器后会触发该方法,在[《WebApplicationContext 容器的初始化》][WebApplicationContext]的 FrameworkServlet小节的 onRefresh 方法中提到过

可以看到每个方法会初始化构造方法中的每个组件

1. doService

doService(HttpServletRequest request, HttpServletResponse response)方法,DispatcherServlet 的处理请求的入口方法,代码如下:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // <1> 如果日志级别为 DEBUG,则打印请求日志
    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.
    // <2> 保存当前请求中相关属性的一个快照
    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.
    // <3> 设置 Spring 框架中的常用对象到 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());

    // <4> FlashMap 的相关配置
    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);
    }

    try {
        // <5> 执行请求的分发
        doDispatch(request, response);
    }
    finally {
        // <6> 异步处理相关
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

1、 调用logRequest(HttpServletRequestrequest)方法,如果日志级别为DEBUG,则打印请求日志;
2、 保存当前请求中相关属性的一个快照,作为异步处理的属性值,防止被修改,暂时忽略;
3、 设置Spring框架中的常用对象到request属性中,例如webApplicationContextlocaleResolverthemeResolver
4、 FlashMap的相关配置,暂时忽略;
5、 **【重点】**调用doDispatch(HttpServletRequestrequest,HttpServletResponseresponse)方法,执行请求的分发;
6、 异步处理相关,暂时忽略;

2. doDispatch【核心】

doDispatch(HttpServletRequest request, HttpServletResponse response) 方法,行请求的分发,在开始看具体的代码实现之前,我们在来回味下这张图片:

![ ][nbsp 2]

这张图,更多的反应的是 DispatcherServlet 的 doDispatch(...) 方法的核心流程,方法如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    // <1> 获取异步管理器
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // <2> 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) { // <3.1> 如果获取不到,则根据配置抛出异常或返回 404 错误
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // <4> 获得当前 handler 对应的 HandlerAdapter 对象
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // <4.1> 处理有Last-Modified请求头的场景
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) { // 不清楚为什么要判断方法类型为'HEAD'
                // 获取请求中服务器端最后被修改时间
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // <5> 前置处理 拦截器
            // 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // <6> 真正的调用 handler 方法,也就是执行对应的方法,并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // <7> 如果是异步
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // <8> 无视图的情况下设置默认视图名称
            applyDefaultViewName(processedRequest, mv);
            // <9> 后置处理 拦截器
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex; // <10> 记录异常
        }
        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);
        }
        // <11> 处理正常和异常的请求调用结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // <12> 已完成处理 拦截器
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        // <12> 已完成处理 拦截器
        triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
    }
    finally {
        // <13.1> Asyn
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        // <13.1> 如果是上传请求则清理资源
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

1、 获得WebAsyncManager异步处理器,暂时忽略;
2、 **【文件】**调用checkMultipart(HttpServletRequestrequest)方法,检测请求是否为上传请求,如果是则通过multipartResolver组件将其封装成MultipartHttpServletRequest对象,便于获取参数信息以及文件;
3、 **【处理器匹配器】**调用getHandler(HttpServletRequestrequest)方法,通过HandlerMapping组件获得请求对应的HandlerExecutionChain处理器执行链,包含HandlerMethod处理器和HandlerInterceptor拦截器们;

1、 如果获取不到对应的执行链,则根据配置抛出异常或返回404错误;
4、 **【处理器适配器】**调用getHandlerAdapter(Objecthandler)方法,获得当前处理器对应的HandlerAdapter适配器对象;
5、 处理有Last-Modified请求头的场景,暂时忽略;
6、 【拦截器】调用HandlerExecutionChain执行链的applyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)方法,拦截器的前置处理

如果出现拦截器前置处理失败,则会调用拦截器的已完成处理方法(倒序)
7、 【重点】调用HandlerAdapter适配器的handle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)方法,真正的执行处理器,也就是执行对应的方法(例如我们定义的Controller中的方法),并返回视图;
8、 如果是异步,则直接return注意,还是会执行finally中的代码;
9、 调用applyDefaultViewName(HttpServletRequestrequest,ModelAndViewmv)方法,ModelAndView不为空,但是没有视图,则设置默认视图名称,使用到了viewNameTranslator视图名称转换器组件;
10、 【拦截器】调用HandlerExecutionChain执行链的applyPostHandle(HttpServletRequestrequest,HttpServletResponseresponse,ModelAndViewmv)方法,拦截器的后置处理倒序);
11、 记录异常,注意,此处仅仅记录,不会抛出异常,而是统一交给<11>处理;
12、 【处理执行结果】调用processDispatchResult(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,ModelAndViewmv,Exceptionexception)方法,处理正常
异常
的请求调用结果,包含页面渲染;
13、 【拦截器】如果上一步发生了异常,则调用triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,Exceptionex)方法,即调用HandlerInterceptor执行链的triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionex)方法,拦截器已完成处理倒序);
14、 finally代码块,异步处理,暂时忽略,如果是涉及到文件的请求,则清理相关资源;

上面将DispatcherServlet 处理请求的整个流程步骤都列出来了,涉及到的组件分别在后续的文档中将分开进行分析

2.1 checkMultipart

checkMultipart(HttpServletRequest request) 方法,检测请求是否为上传请求,如果是则通过 multipartResolver 组件将其封装成 MultipartHttpServletRequest 对象

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 如果该请求是一个涉及到 multipart (文件)的请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

2.2 getHandler

getHandler(HttpServletRequest request) 方法,通过 HandlerMapping 组件获得请求对应的 HandlerExecutionChain 处理器执行链,包含 HandlerMethod 处理器和 HandlerInterceptor 拦截器们

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

2.3 getHandlerAdapter

getHandlerAdapter(Object handler) 方法,获得当前处理器对应的 HandlerAdapter 适配器对象

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

2.4 applyDefaultViewName

applyDefaultViewName(HttpServletRequest request, ModelAndView mv) 方法,ModelAndView 不为空,但是没有视图,则设置默认视图名称

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    if (mv != null && !mv.hasView()) {
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}
@Nullable
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    // 使用到了 viewNameTranslator 视图名称转换器组件
    return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}

2.5 processDispatchResult

processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) 方法,处理正常异常的请求调用结果,方法如下:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {

    // <1> 标记是否为处理生成异常的 ModelAndView 对象
    boolean errorView = false;

    // <2> 如果该请求出现异常
    if (exception != null) {
        // 情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        // 情况二,处理异常,生成 ModelAndView 对象
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            // 标记 errorView
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    // <3> 是否进行页面渲染
    if (mv != null && !mv.wasCleared()) {
        // <3.1> 渲染页面
        render(mv, request, response);
        // <3.2> 清理请求中的错误消息属性
        // 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    // <4> 如果是异步
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    // <5> 已完成处理 拦截器
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

1、 标记是否为处理生成异常的ModelAndView对象;
2、 如果该请求出现异常;

1、 情况一,从ModelAndViewDefiningException中获得ModelAndView对象;
2、 情况二,调用processHandlerException(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)方法,处理异常,生成ModelAndView对象;
3、 如果ModelAndView不为空且没有被清理,例如你现在使用最多的@ResponseBody这里就为空,不需要渲染;

1、 调用render(ModelAndViewmv,HttpServletRequestrequest,HttpServletResponseresponse)方法,渲染页面;
2、 如果是上面第2步中情况二生成的ModelAndView对象,则需要清理请求中的错误消息属性,因为上述的情况二会通过WebUtils设置错误消息属性,所以这里得清理一下;
4、 如果是异步请求,则直接return
5、 正常情况下,调用triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,Exceptionex)方法,即调用HandlerInterceptor执行链的triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionex)方法,拦截器已完成处理倒序);

2.5.1 processHandlerException

processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,处理异常,生成 ModelAndView 对象

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler, Exception ex) throws Exception {

    // Success and error responses may use different content types
    // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    // <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍历 HandlerExceptionResolver 数组
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // 解析异常,生成 ModelAndView 对象
            exMv = resolver.resolveException(request, response, handler, ex);
            // 生成成功,结束循环
            if (exMv != null) {
                break;
            }
        }
    }
    // <b> 情况一,生成了 ModelAndView 对象,进行返回
    if (exMv != null) {
        // ModelAndView 对象为空,则返回 null
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        // 没有视图则设置默认视图
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        // 打印日志
        if (logger.isTraceEnabled()) {
            logger.trace("Using resolved error view: " + exMv, ex);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Using resolved error view: " + exMv);
        }
        // 设置请求中的错误消息属性
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    // <c> 情况二,未生成 ModelAndView 对象,则抛出异常
    throw ex;
}

<a> 处,遍历 HandlerExceptionResolver 数组,调用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,解析异常,生成 ModelAndView 对象

<b> 处,情况一,生成了 ModelAndView 对象,逻辑比较简单

<c> 处,情况二,未生成 ModelAndView 对象,则抛出异常

2.5.2 render

render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) 方法,渲染 ModelAndView,方法如下:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    // <1> 解析 request 中获得 Locale 对象,并设置到 response 中
    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    // 获得 View 对象
    View view;
    String viewName = mv.getViewName();
    // 情况一,使用 viewName 获得 View 对象
    if (viewName != null) {
        // We need to resolve the view name.
        // <2.1> 使用 viewName 获得 View 对象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) { // 获取不到,抛出 ServletException 异常
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    // 情况二,直接使用 ModelAndView 对象的 View 对象
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        // <2.2> 直接使用 ModelAndView 对象的 View 对象
        view = mv.getView();
        if (view == null) { // 获取不到,抛出 ServletException 异常
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    // 打印日志
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        // <3> 设置响应的状态码
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // <4> 渲染页面
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

1、 调用LocaleResolverresolveLocale(HttpServletRequestrequest)方法,从request中获得Locale对象,并设置到response中;
2、 获得View对象,有两种情况;

1、 调用resolveViewName方法,使用viewName通过获得View对象,方法如下:;

    ```java 
    @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {
    
        if (this.viewResolvers != null) {
            // 遍历 ViewResolver 数组
            for (ViewResolver viewResolver : this.viewResolvers) {
                // 根据 viewName + locale 参数,解析出 View 对象
                View view = viewResolver.resolveViewName(viewName, locale);
                // 解析成功,直接返回 View 对象
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
**2、** 直接使用ModelAndView对象的View对象;  
**3、** 设置响应的状态码;  
**4、** 调用`View`的`render(Map<String,?>model,HttpServletRequestrequest,HttpServletResponseresponse)`方法,渲染视图;  

### 总结 ###

本文对Spring MVC 处理请求的整个过程进行了分析,核心就是通过 `DispatcherServlet` 协调各个组件工作,处理请求,因为 `DispatcherServlet` 是一个 Servlet,在 Servlet 容器中,会将请求交由它来处理。

通过本文对 `DispatcherServlet` 是如何处理请求已经有了一个整体的认识,不过在整个处理过程中涉及到的各个 Spring MVC 组件还没有进行分析,对于许多细节存在疑惑,不要慌,那么接下来会对每一个 Spring MVC 组件进行分析。这样,便于我们对 Spring MVC 的理解,然后再回过头来思考 `DispatcherServlet` 这个类,能够更好的将这些组件串联在一起。先整体,后局部,逐步逐步抽丝剥茧,看清理透。

--------------------

>流程示意图,来自 [SpringMVC - 运行流程图及原理分析][SpringMVC -]
> 
>![&nbsp;][nbsp 3]
> 
>代码序列图
> 
>![&nbsp;][nbsp 4]
> 
>--------------------
> 
>流程示意图,来自《看透 Spring MVC 源代码分析与实践》 书籍中的第 123 页
> 
>![&nbsp;][nbsp 5]

>参考文章:**芋道源码**[《死磕 Spring MVC 源码分析》][Spring MVC]
> 
>参考文章:[Spring 揭秘 -- 寻找遗失的 web.xml][Spring _ -- _ web.xml]

  
版权声明:本文不是「本站」原创文章,版权归原作者所有 | [原文地址:][Link 2]


[Link 1]: https://wx.zsxq.com/
[Spring MVC _ GitHub]: https://github.com/liu844869663/spring-framework
[Spring MVC _ -]: https://www.cnblogs.com/lifullmoon/p/14123963.html
[WebApplicationContext]: https://www.cnblogs.com/lifullmoon/p/14131802.html
[nbsp]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848971244.png
[nbsp 1]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973219.png
[nbsp 2]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973402.png
[SpringMVC -]: https://blog.csdn.net/J080624/article/details/77990164
[nbsp 3]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973676.png
[nbsp 4]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848975543.png
[nbsp 5]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848977183.png
[Spring MVC]: http://svip.iocoder.cn/categories/Spring-MVC/
[Spring _ -- _ web.xml]: https://www.cnkirito.moe/servlet-explore/
[Link 2]: https://www.cnblogs.com/lifullmoon/p/14131862.html