[转载]Spring MVC 请求执行流程的源码深度解析

Scroll Down

源码入口

  Spring MVC是对原始Servlet的封装,虽然我们在开发过程中不会再接触到Servlet级别的API,但是我们知道Spring MVC中有一个核心的Servlet实现,那就是DispatcherServlet,它作为核心控制器,用于接收任何的请求并将请求转发给对应的处理组件。因此,Spring MVC的请求处理入口仍然可以从DispatcherServlet中找到。
  DispatcherServlet的uml类图如下:
image.png

可以看到,从它间接的继承了HttpServlet,因此Spring MVC的请求源码入口同样是HttpServlet#service()方法!

/**
 * Dispatches client requests to the protected
 * <code>service</code> method. There's no need to
 * override this method.
 *
 * @param req   the {@link HttpServletRequest} object that
 *                  contains the request the client made of
 *                  the servlet
 *
 * @param res   the {@link HttpServletResponse} object that
 *                  contains the response the servlet returns
 *                  to the client
 *
 * @exception IOException   if an input or output error occurs
 *                              while the servlet is handling the
 *                              HTTP request
 *
 * @exception ServletException  if the HTTP request cannot
 *                                  be handled
 *
 * @see javax.servlet.Servlet#service
 */
@Override
public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
}

这个service方法中并没有做太多的事情,主要是将ServletRequest和ServletResponse强转转换为HttpServletRequest和HttpServletResponse,最后会调用另一个service方法,该方法才是真正的核心处理请求的方法。
  HttpServlet自己的service方法源码如下。其内部时一系列的以do开头的模版方法,回顾一下,在早期原始的Servlet项目中,我们所要开发的就是这些doGet、doPost方法:

/**
 * Receives standard HTTP requests from the public
 * <code>service</code> method and dispatches
 * them to the <code>do</code><i>Method</i> methods defined in
 * this class. This method is an HTTP-specific version of the
 * {@link javax.servlet.Servlet#service} method. There's no
 * need to override this method.
 *
 * @param req   the {@link HttpServletRequest} object that
 *                  contains the request the client made of
 *                  the servlet
 *
 * @param resp  the {@link HttpServletResponse} object that
 *                  contains the response the servlet returns
 *                  to the client
 *
 * @exception IOException   if an input or output error occurs
 *                              while the servlet is handling the
 *                              HTTP request
 *
 * @exception ServletException  if the HTTP request
 *                                  cannot be handled
 *
 * @see javax.servlet.Servlet#service
 */
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;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // 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);
    }
}

并且,如果我们足够心细,我们能发现FrameworkServlet直接重写了HttpServlet的整个service方法,此前的原始Servlet开发的我们都是开发的do开头的方法,比如doGet、doPost,但是Spring MVC将整个service方法都重写了,我们一起来看看。

FrameworkServlet#service入口

  FrameworkServlet#service方法可以看作Spring MVC一次请求的处理入口方法。
  FrameworkServlet重写父类HttpServlet的service方法的原因在注释上说的很明白了,就是为了支持PATCH请求。
  重写的方法的逻辑比较简单,首先解析出当前请求的方法,如果是PATCH方法或者没有请求方法,则直接调用processRequest方法处理该请求,否则,对于其他请求方法,则还是会调用父类HttpServlet的service来处理,最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理。

/**
 * FrameworkServlet的方法
 * <p>
 * 重写父类HttpServlet的service实现用以拦截PATCH请求。
 */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //获取当前请求的请求方法,可能是:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    //如果是PATCH请求,或者请求方法为null,则另外处理
    //PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前用的比较少
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
        //直接调用processRequest方法处理
        processRequest(request, response);
    } else {
        //对于其他请求方法,则还是会调用父类HttpServlet的service来处理
        //最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理
        super.service(request, response);
    }
}

很明显,do开头的一系列方法也都由DispatcherServlet及其父类帮我们都实现好了,因为在使用Spring MVC框架的时候我们并没有编写这些底层方法的实现。实际上,do开头的一系列模版方法,也是由FrameworkServlet帮我们实现的,并且我们能够发现它实现的这些方法最终都会指向processRequest方法。
从这里就能看出processRequest方法的重要性了,继续向下看!

processRequest处理请求

  processRequest方法位于FrameworkServlet中,提供了处理请求方法的骨架实现,并且留出了一系列模版方法让子类去实现自己的逻辑,这是模版方法模式的应用。
  该方法会将本次请求的LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中,这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();,注意需要在当前请求线程中才能获取到。
  请求处理完毕之后,无论成功与否,都会调用publishRequestHandledEvent方法发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,我们可以监听该事件,并做出不同的处理。

  该方法中的核心处理请求的方法就是doService模版方法,该方法留给子类比如DispatcherServlet实现。

/**
 * FrameworkServlet的方法
 * <p>
 * 处理此请求,发布事件,无论结果如何。
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //当前时间毫秒
    long startTime = System.currentTimeMillis();
    //失败原因(异常)
    Throwable failureCause = null;
    //获取之前可能存在的LocaleContext,可能在Filter中设置的
    //LocaleContext中可以获取当前的语言环境Locale
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    //根据当前request构建LocaleContext,就是new 一个SimpleLocaleContext
    LocaleContext localeContext = buildLocaleContext(request);

    //获取之前可能存在的RequestAttributes,可能在Filter中设置的
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    //根据当前request、response、previousAttributes构建新的ServletRequestAttributes
    //如果此前的previousAttributes不为null,那么就还是使用这个对象
    //否则就通过request和response直接new 一个ServletRequestAttributes,因此RequestAttributes中可以获取request和response
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    //获取异步请求管理器,新建一个WebAsyncManager对象,并且通过setAttribute存入request的属性中
    //属性名为org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    //注册RequestBindingInterceptor这个拦截器,该拦截器可以初始化或者重置LocaleContext和RequestAttributes
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    //初始化LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中
    //这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如:
    //HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    initContextHolders(request, localeContext, requestAttributes);

    try {
        /*
         * 处理请求的核心模版方法,留给子类比如DispatcherServlet实现
         */
        doService(request, response);
    } catch (ServletException | IOException ex) {
        //获取处理请求过程中抛出的异常
        failureCause = ex;
        throw ex;
    } catch (Throwable ex) {
        //获取处理请求过程中抛出的异常
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    } finally {
        //最终,将LocaleContext和RequestAttributes从LocaleContextHolder和RequestContextHolder的线程本地变量属性中解除绑定
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            //发出请求已完成的信号。
            //将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        /*
         * 发布一个事件,表示该请求处理完毕,无论成功与否
         * 我们可以监听该事件,并做出不同的处理
         */
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

doService处理请求

  doService是处理请求的核心模版方法,该方法留给子类比如DispatcherServlet实现。
  DispatcherServlet的doService方法主要是将DispatcherServlet的一些属性放置在当前请求的属性中,方便后续直接从request中获取,并委托doDispatch方法进行实际请求处理,因此实际的真正请求处理还得看doDispatch方法(是不是觉得调用层次很深呢?)。

//DispatcherServlet的属性

/**
 * 包含请求执行完毕后执行请求属性的清理吗,默认清理
 */
private boolean cleanupAfterInclude = true;

/**
 * DispatcherServlet的默认策略属性以其开头的公共前缀。
 */
private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet";

/**
 * 保存当前的Web应用程序上下文的请求属性
 */
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";

/**
 * 保存当前的LocaleResolver的请求属性,可通过视图检索。
 */
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";

/**
 * 保存当前的ThemeResolver的请求属性,可由视图检索。
 */
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";

/**
 * 保存当前的ThemeSource的请求属性,可通过视图检索。
 */
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";

/**
 * DispatcherServlet的方法
 * <p>
 * 公开特定于DispatcherServlet的请求属性,并委托doDispatch进行实际请求处理。
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    //如果是包含请求(include,即请求包含),那么保存request 属性快照,以便能够在包含请求处理完毕之后恢复原始属性
    //也就是说,在包含请求处理过程中设置的request 属性将不会被保留
    Map<String, Object> attributesSnapshot = null;
    //判断是否是请求包含,尝试从request的属性中获取名为javax.servlet.include.request_uri的属性
    //如果存在该属性,那么就是包含请求,否则就不是
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        //获取属性名
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            //默认将所有属性存入快照map中
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    //将一些对象存入request的属性中,方便handler和view中使用
    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());

    //flashMap管理器,默认为SessionFlashMapManager
    //FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。
    if (this.flashMapManager != null) {
        //查找由与当前请求匹配的先前请求保存的FlashMap,将其从基础存储中删除,还删除其他过期的FlashMap实例。
        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 {
        /*
         * 分发、处理请求的真正核心方法
         */
        doDispatch(request, response);
    } finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            //如果是包含请求,那么还原原始属性快照。
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

publishRequestHandledEvent发布请求处理完毕事件

  在processRequest方法的finally块的最后,将会通过当前DispatcherServlet关联的webApplicationContext发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,无论成功与否。
  ServletRequestHandledEvent中包含了本次请求的各种信息,我们可以监听该事件,并做出不同的处理。

//FrameworkServlet的属性

/**
 * 我们是否应该在每个请求的末尾发布ServletRequestHandledEvent?
 * 默认是应该的,但可以通过该参数关闭
 */
private boolean publishEvents = true;
/**
 * 此Servlet关联的WebApplicationContext,在此前的initServletBean方法中被初始化
 */
@Nullable
private WebApplicationContext webApplicationContext;


/**
 1. FrameworkServlet的方法
 2. <p>
 3. 发布一个事件,表示该请求处理完毕,无论成功与否
 4.  5. @param request      当前request
 6. @param response     当前response
 7. @param startTime    请求处理开始时间戳毫秒
 8. @param failureCause 失败原因(抛出的异常,可能为null)
 */
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
                                        long startTime, @Nullable Throwable failureCause) {
    //如果允许发布事件并且关联的IoC容器不为null,那么无论我们是否成功,都发布一个事件。
    if (this.publishEvents && this.webApplicationContext != null) {
        //计算请求处理花费的时间
        long processingTime = System.currentTimeMillis() - startTime;
        //通过IoC容器发布ServletRequestHandledEvent事件,包含各种请求信息
        //这样我们就可以使用Spring的事件监听机制来监听这个事件进而来监听这个请求了
        this.webApplicationContext.publishEvent(
                //this:事件源,当前DispatcherServlet对象
                new ServletRequestHandledEvent(this,
                        //请求路径、ip地址
                        request.getRequestURI(), request.getRemoteAddr(),
                        //请求方法、ServletName
                        request.getMethod(), getServletConfig().getServletName(),
                        //SessionId、username
                        WebUtils.getSessionId(request), getUsernameForRequest(request),
                        //请求处理时间、失败原因、响应状态码
                        processingTime, failureCause, response.getStatus()));
    }
}

doDispatch分发请求

  将请求实际分派给对应的handler并且调用不同的组件进行请求处理。该方法的逻辑实际上就是Spring MVC请求处理的主体流程。

  大概逻辑为:

  • 调用getHandler方法确定当前请求的处理器——handler。
  • 调用getHandlerAdapter方法根据handler确定当前请求的处理器适配器——HandlerAdapter。
  • 调用applyPreHandle方法,顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法。全部通过则执行后续步骤,不通过则倒序执行已通过的拦截器的afterCompletion方法,随后直接返回。
  • 调用handle方法,通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。
  • handler正常处理完毕时,调用applyPostHandle方法,倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法。
  • 调用processDispatchResult方法,处理执行handler的结果。主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图。
  • 最后无论请求处理成功还是失败抛出异常,都会调用triggerAfterCompletion方法,倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕。
/**
 * DispatcherServlet的方法
 * <p>
 * 将请求实际分派给对应的handler进行处理。
 * <p>
 * 该handler将通过依次应用servlet的HandlerMappings来获得,并且通过查询Servlet的
 * handlerAdapters来查找支持该handler的第一个HandlerAdapter,从而获得HandlerAdapter。
 * 所有HTTP方法都由该方法处理,由HandlerAdapters或handler本身来决定可接受的方法。
 *
 * @param request  当前 HTTP request
 * @param response 当前 HTTP response
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //processedRequest初始化为当前request
    HttpServletRequest processedRequest = request;
    //已匹配的handler,也就是Handler执行链
    HandlerExecutionChain mappedHandler = null;
    //是否是已解析的多部件请求,即文件上传请求
    boolean multipartRequestParsed = false;
    //异步请求管理器
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        //模型和视图对象,handler执行的返回结果,内部包含了model和view对象
        ModelAndView mv = null;
        //记录异常
        Exception dispatchException = null;

        try {
            /*
             * 将请求使用多部分解析器转换为多部分请求,如果未设置多部分解析器或者当前请求不是多部分请求,则返回原始请求。
             * 对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖
             * 它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求
             * 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象
             *
             * 对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖
             * 它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求
             * 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象
             */
            processedRequest = checkMultipart(request);
            //判断是否是多部件请求,即文件上传请求
            multipartRequestParsed = (processedRequest != request);

            /*
             * 1 确定当前请求的处理器——handler
             *
             * 通过遍历handlerMappings,依次调用每个HandlerMapping的getHandler方法获取HandlerExecutionChain对象
             * 只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找
             * 如果在所有的handlerMapping中都没找到,则返回null。
             *
             * 对于RequestMappingHandlerMapping,他返回的handler就是HandlerMethod
             */
            mappedHandler = getHandler(processedRequest);
            //如果没有找到任何handler,那么执行noHandlerFound的逻辑
            if (mappedHandler == null) {
                //通常返回404响应码或者抛出NoHandlerFoundException异常
                noHandlerFound(processedRequest, response);
                //直接返回,doDispatch方法结束
                return;
            }

            /*
             * 2 根据handler确定当前请求的处理器适配器——HandlerAdapter
             *
             * 通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法
             * 只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找
             * 如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。
             *
             * 对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter
             */
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            /*
             * 如果handler支持,则处理last-modified头
             */
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            //如果是GET或者HEAD请求
            if (isGet || "HEAD".equals(method)) {
                //获取last-modified头信息,这是对应的文档的最近一次更新时间
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                //如果没有任何改变并且是GET请求,则直接返回
                //这是对于不经常改变的文档的缓存机制的支持,如果此前获取过某个文档,并且此次访问时该文档仍然没有改变,则服务器不会再次渲染该文档
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            /*
             * 3 顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法
             * 只要有一个拦截器不通过,那么该请求就不会继续处理,而是直接返回
             */
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            /*
             * 4 通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象
             *
             * 不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验等等步骤
             * 对于HandlerMethod来说,就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑
             */
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            //如果存在mv并且没有view,则设置view为默认的viewName
            applyDefaultViewName(processedRequest, mv);
            /*
             * handler正常处理完毕
             * 5 倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法
             */
            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);
        }
        /*
         * 6 处理执行handler返回的结果
         * 主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序应用拦截器的afterCompletion方法
         */
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
        /*
         * 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕
         *
         * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
         */
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    } catch (Throwable err) {
        /*
         * 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕
         *
         * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
         */
        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 {
            //清理多部分请求使用的所有资源。
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

checkMultipart检查文件上传请求

  使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。多部分解析器来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver。
  对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖,它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象。
  对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖,它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象。

//DispatcherServlet的属性

/**
 * 此servlet使用的MultipartResolver
 * 来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver
 */
@Nullable
private MultipartResolver multipartResolver;


/**
 * DispatcherServlet的方法
 * <p>
 * 使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。
 *
 * @param request 当前 HTTP request
 * @return 已处理的请求(如果需要,则使用多部分请求包装)
 */
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    //如果设置了multipartResolver并且此请求是多部分请求,即文件上传请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        //如果该请求已经是MultipartHttpServletRequest 那仅仅打印日志
        //如果我们设置了MultipartFilter这个过滤器,那么在请求到达Servlet之前就会被判断是否是多部分请求
        //如果是,则会被被解析为MultipartHttpServletRequest,MultipartFilter默认使用StandardServletMultipartResolver来解析
        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 {
                //调用多部分解析器的resolveMultipart方法将原始HttpServletRequest请求解析为MultipartHttpServletRequest的实现请求
                //对于CommonsMultipartResolver,会创建一个DefaultMultipartHttpServletRequest并返回,其内部包装了原始的的请求
                //对于StandardServletMultipartResolver则会创建一个StandardMultipartHttpServletRequest并返回,其内部包装了原始的的请求
                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;
                }
            }
        }
    }
    //返回原始请求
    return request;
}

getHandler获取HandlerExecutionChain

  按顺序尝试调用所有的HandlerMapping,返回此请求的HandlerExecutionChain,如果找不到handler,则返回null。
  该方法就是常见的Spring MVC执行流程的第一步了,即“通过HandlerMapping处理器映射器找到对应的Handler处理器,返回一个HandlerExecutionChain执行链对象,该对象包含一个拦截器链,链的尾部就是当前的handler处理器,对于基于@RequestMapping注解方法注册的控制器来说,handler就是HandlerMethod”。
  SpringMVC默认加载三个请求处理映射类:RequestMappingHandlerMapping、SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping。如果配置了</mvc:annotation-driven>标签,或者@EnableWebMvc注解,则不会加载默认配置,但至少会加载两个:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,还会加上我们自定义配置的HandlerMapping的bean。

//DispatcherServlet的属性

/**
 * 此servlet使用的HandlerMappings列表
 */
@Nullable
private List<HandlerMapping> handlerMappings;

/**
 1. DispatcherServlet的方法
 2. <p>
 3. 按顺序尝试调用所有的HandlerMapping,返回此请求的HandlerExecutionChain。
 4.  5. @param request 当前 HTTP request
 6. @return HandlerExecutionChain;如果找不到handler,则返回null
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        //遍历所有的HandlerMapping,依次调用getHandler方法尝试获取HandlerExecutionChain
        //如果某个返回结果不为null,则返回该返回值,不会继续向后查找
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

mapping#getHandler获取HandlerExecutionChain

  Spring MVC通过调用HandlerMapping#getHandler方法来获取该请求对应的HandlerExecutionChain。
  通常RequestMappingHandlerMapping排在handlerMappings集合的首位,将首先被调用。我们主要讲解RequestMappingHandlerMapping的getHandler方法实现。
  不同的HandlerMapping实现,它的getHandler方法也不一样。我们能够很容易的猜到Spring对此使用了模板方法模式,有一个HandlerMapping的抽象实现类,该类实现了getHandler接口并提供了骨架实现,然后有各个子类提供细节的实现,这个抽象类就是AbstractHandlerMapping。
  该方法的主要逻辑为:

  • 调用getHandlerInternal方法尝试获取匹配的handler处理器,对于RequestMappingHandlerMapping就是返回HandlerMethod。
  • 如果没有获取到处理器,那么获取默认处理器,这个默认handler是我们自己配置的,默认也是为null。
  • 通过上面的查找,如果handler还是为null,那么直接返回null。
  • 如果找到了handler,判断handler是Bean名称或已解析的handler实例,如果是beanName,那么就从IoC容器中查找对应的handler实例。这里的逻辑主要是BeanNameUrlHandlerMapping用到。
  • 调用getHandlerExecutionChain方法,为给定的handler构建一个HandlerExecutionChain对象,其中还包括所有适用的拦截器形成的拦截器链属性interceptorList。
  • 如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过mvc:cors标签或者WebConfig#addCorsMappings),或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),或者说当前请求是CORS预检请求。满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理。
  • 返回最终的HandlerExecutionChain。
/**
 * AbstractHandlerMapping的属性
 * 全局的CORS配置
 */
@Nullable
private CorsConfigurationSource corsConfigurationSource;

/**
 * AbstractHandlerMapping的方法
 * <p>
 * 查找给定请求的handler,如果未找到特定的handler,则退回到使用默认handler。
 *
 * @param request 当前 HTTP request
 * @return 相应的handler处理程序实例或默认handler处理程序
 */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    /*
     * 1 查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler.
     *
     * 对于CORS的预检请求,此方法从"Access-Control-Request-Method"头部字段中获取实际请求所使用的HTTP方法
     * 从"Access-Control-Request-Headers"头部字段中获取实际请求所携带的自定义首部字段与此方法的CORS配置进行匹配
     *
     * 该方法是一个抽象方法,留给子类实现。对于RequestMappingHandlerMapping,则会尝试返回HandlerMethod
     * 过程中会匹配@ReuqestMapping设置的各种条件,比如path、Method、params、headers、consumes、produces、patterns……
     */
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        //如未未找到特定请求,则使用默认handler。这个默认handler是我们自己配置的,默认也是为null
        handler = getDefaultHandler();
    }
    //通过上面的查找,如果handler还是为null,那么返回null
    if (handler == null) {
        return null;
    }
    //判断handler是Bean名称或已解析的handler实例
    if (handler instanceof String) {
        //如果是beanName,那么就从IoC容器中查找对应的handler实例
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    /*
     * 2 为给定的handler构建一个HandlerExecutionChain对象,其中还包括所有适用的拦截器形成的拦截器链interceptorList
     *
     * 默认实现使用给定的handler,公共拦截器以及与当前请求URL匹配的任何MappedInterceptors来构建标准的HandlerExecutionChain。
     */
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }
    /*
     * 3 如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过<mvc:cors>标签或者WebConfig#addCorsMappings),
     * 或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解)
     * 或者说当前请求是CORS预检请求
     * 满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理
     */
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        //获取全局CORS配置,比如基于<mvc:cors>标签配置
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        //获取针对当前handler的CORS配置,比如类和方法上的@CrossOrigin注解配置
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        //CORS配置合并,对于单个字段的配置,方法上的@CrossOrigin注解优先级更高,方法和类上的@CrossOrigin注解的值将组合(java中是list集合)
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        /*
         * 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,并且使用此前的
         * chain中的拦截器链构建一个新的HandlerExecutionChain对象返回
         *
         * 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,
         * 用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象
         */
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}
getHandlerInternal获取内部处理器

  查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler。
  对于CORS的预检请求,此方法从"Access-Control-Request-Method“头部字段中获取实际请求所使用的HTTP方法,从”Access-Control-Request-Headers"头部字段中获取实际请求所携带的自定义首部字段,将会获取此方法的CORS配置进行匹配。
  该方法是一个抽象方法,留给子类实现,并且比较复杂,需要详细讲解,我们主要看RequestMappingHandlerMapping的实现。RequestMappingHandlerMapping的getHandlerInternal在它的父类RequestMappingInfoHandlerMapping和父类的父类AbstractHandlerMethodMapping中均有实现。
  RequestMappingHandlerMapping的uml类图如下:

image.png

/**
 * HandlerMapping中的常量
 * <p>
 * HttpServletRequest属性的名称,该属性包含适用于映射处理程序的可产生MediaType的集合。
 */
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

/**
 * RequestMappingInfoHandlerMapping的方法
 *
 * @param request 当前 request
 * @return 内部的handler处理器对象,实际上就是HandlerMethod
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //移除该属性
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        //调用父类AbstractHandlerMethodMapping的实现方法
        return super.getHandlerInternal(request);
    } finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}


/**
 * HttpServletRequest属性的名称,该属性包含用于查找匹配handler的路径
 */
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 查找给定请求的HandlerMethod
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //获取请求路径,将会通过该路径匹配handler
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    //设置到当前request的属性中
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        //调用lookupHandlerMethod()方法根据给定的path和request尝试查找HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        this.mappingRegistry.releaseReadLock();
    }
}
lookupHandlerMethod查找匹配的HandlerMethod

  该方法将查找查找当前请求的最佳匹配handler的HandlerMethod,如果找到多个匹配项,则选择最佳匹配项。将会匹配path、Method、params、headers、consumes、produces、patterns、customCondition等等条件。
  如果是匹配的CORS预检请求,将返回一个特殊的空HandlerMethod。

/**
 * AbstractHandlerMethodMapping的属性
 * <p>
 * 用于CORS预检请求的特殊HandlerMethod
 * 其内部的bean是一个EmptyHandler,方法也是该bean的handle方法
 * 执行该方法将抛出异常
 */
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
        new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));


/**
 * HandlerMapping的属性
 * 最佳匹配的handler的HttpServletRequest属性的名称。
 */
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";


/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 查找当前请求的最佳匹配handler的方法,如果找到多个匹配项,则选择最佳匹配项。
 *
 * @param lookupPath 当前servlet映射内的映射查找路径
 * @param request    当前 request
 * @return 最佳匹配的HandlerMethod;如果不匹配,则返回null
 */
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    //匹配的结果集合
    List<Match> matches = new ArrayList<>();
    /*
     * 根据lookupPath取注册表中查找所有匹配的RequestMappingInfo,这里并没有模式匹配,而是精确匹配
     * 这里只匹配路径,不会匹配其他的条件,因此可能找到多个RequestMappingInfo,比如它们之间使用请求方法来区分时
     */
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    //对找到的精确路径匹配的RequestMappingInfo继续应用其他属性匹配
    if (directPathMatches != null) {
        //比如Method、params、headers、consumes、produces、patterns、customCondition等等
        //如果这些条件都匹配,那么对应的RequestMappingInfo和handlerMethod会被包装为一个Match对象存入matches集合
        addMatchingMappings(directPathMatches, matches, request);
    }
    /*
     * 如果没有任何精确路径匹配,那么遍历所有的映射,再次尝试匹配
     * 这里面会尝试模式匹配
     */
    if (matches.isEmpty()) {
        //该方法中再次匹配时实际上会使用模式匹配
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    /*
     * 如果找到了至少一个匹配,那么查找最佳匹配
     */
    if (!matches.isEmpty()) {
        //默认第一个是最佳匹配
        Match bestMatch = matches.get(0);
        //如果总匹配数大于1个,那么查找最佳匹配
        if (matches.size() > 1) {
            //匹配比较器
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            //比较并排序,最终,最佳匹配将位于集合首位
            matches.sort(comparator);
            //获取第一个元素,就是最佳匹配的元素
            bestMatch = matches.get(0);
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            //如果是CORS的预检请求,那么固定返回一个空的HandlerMethod实例
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            //第二匹配
            Match secondBestMatch = matches.get(1);
            //如果第二匹配和第一匹配的比较结果是相等的,这说明没找到最佳匹配,或者说有两个最佳匹配
            //那么将抛出异常
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        //将最佳匹配的HandlerMethod的存入到当前request的属性中公开
        //属性名为org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        //在请求中继续公开lookupPath、URI模板变量,矩阵变量和可使用的媒体类型,将它们作为当前请求的属性
        handleMatch(bestMatch.mapping, lookupPath, request);
        //返回最佳匹配的handlerMethod
        return bestMatch.handlerMethod;
    } else {
        //如果没有匹配任何一个,那么会放宽条件再次尝试全部匹配
        //如果存在部分匹配的handlerMethod,那么会尝试使用该handlerMethod,否则将抛出异常或者返回null
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}
CORS配置

  如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过mvc:cors标签或者WebConfig#addCorsMappings),或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),或者说当前请求是CORS预检请求。满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理。
  将会对全局CORS配置和局部CORS配置合并,对于只能接受单个值的属性(如allowCredentials和 maxAge),本地值将覆盖全局值,对于其他接受多个值的属性,比如origins,则全局和本地的CORS配置值会组合在一起(java中是list集合)!
  最后会调用getCorsHandlerExecutionChain配置CORS。

hasCorsConfigurationSource是否具有CORS配置

  该方法判断是否具有全局CORS配置或者对于当前HandlerMethod的局部CORS配置。如果有的话,那么当前请求就可以执行CORS配置处理。

/**
 * AbstractHandlerMethodMapping的方法
 * 是否具有适用于当前handler的CORS配置
 *
 * @param handler 当前handler
 * @return true——有
 */
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
    //调用父类的同名方法校验
    return super.hasCorsConfigurationSource(handler) ||
            //当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),那么返回true
            (handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
}


/**
 1. AbstractHandlerMapping的方法
 2. <p>
 3. 是否具有适用于当前handler的CORS配置
 */
protected boolean hasCorsConfigurationSource(Object handler) {
    if (handler instanceof HandlerExecutionChain) {
        handler = ((HandlerExecutionChain) handler).getHandler();
    }
    //如果handler属于CorsConfigurationSource类型或者具有全局的CORS配置,那么返回true
    return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null);
}
getCorsHandlerExecutionChain配置CORS

  该方法将会判断:

如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain对象返回。后面执行handler时,实际上是进行CORS校验。
否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,用于后面执行拦截器时校验CORS配置,最后还是返回当前chain对象。

/**
 * AbstractHandlerMapping的方法
 * <p>
 * 获取具有CORS配置的HandlerExecutionChain
 *
 * @param request 当前request
 * @param chain   当前的handler chain
 * @param config  适用的CORS配置(可能为null)
 * @return 配置了CORS的HandlerExecutionChain
 */
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
                                                             HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
    /*
     * 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,
     * 并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain对象返回。
     */
    if (CorsUtils.isPreFlightRequest(request)) {
        HandlerInterceptor[] interceptors = chain.getInterceptors();
        chain = new HandlerExecutionChain(new AbstractHandlerMapping.PreFlightHandler(config), interceptors);
    } else {
        /*
         * 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象。
         */
        chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
    }
    return chain;
}

noHandlerFound没找到handler的处理

  找不到handler的时候,则调用该方法,通常是设置404的HTTP响应状态码,但可能抛出NoHandlerFoundException异常,需要手动设置DispatcherServlet的throwExceptionIfNoHandlerFound属性为true。

/**
 * DispatcherServlet的属性
 * <p>
 * 如果未找到处理该请求的handler,则抛出NoHandlerFoundException,默认不抛出
 */
private boolean throwExceptionIfNoHandlerFound = false;

/**
 * DispatcherServlet的方法
 * <p>
 * 找不到handler->设置适当的HTTP响应状态码。
 *
 * @param request  当前 HTTP request
 * @param response 当前 HTTP response
 */
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
    }
    //如果该属性为true,则直接抛出NoHandlerFoundException异常
    if (this.throwExceptionIfNoHandlerFound) {
        throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                new ServletServerHttpRequest(request).getHeaders());
    } else {
        //否则,直接设置404相应状态吗,这是常见逻辑
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

getHandlerAdapter获取HandlerAdapter

  该方法根据上面找到的handler获取对应的处理器适配器——HandlerAdapter。
  通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法,只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找,如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。

/**
 * DispatcherServlet的属性
 * <p>
 * 此servlet使用的HandlerAdapter列表
 */
@Nullable
private List<HandlerAdapter> handlerAdapters;

/**
 * DispatcherServlet的方法
 * <p>
 * 返回此handler对象的HandlerAdapter。
 *
 * @param handler 用来查找适配器的handler对象,比如HandlerMethod
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        //遍历所有的HandlerAdapter,依次调用supports方法尝试判断是否支持
        //如果某个返回结果为true,则说明该adapter支持该handler,则返回该adapter,不继续向后查找
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    //如果找不到处理程序的HandlerAdapter,则直接抛出ServletException异常
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

adapter#supports是否支持handler

  不同的handler需要不同的HandlerAdapter来适配,对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter。
  RequestMappingHandlerAdapter将会被默认配置,它的uml类图如下:

image.png

它的supports方法是由它的父类AbstractHandlerMethodAdapter实现的,非常的简单,只要此handler是HandlerMethod类型那么就返回true,表示可以处理。

/**
 * AbstractHandlerMethodAdapter的方法
 * <p>
 * 此实现期望handler的类型为HandlerMethod。
 *
 * @param handler 要检查的handler实例
 * @return 此适配器是否可以适应给定的handler
 */
@Override
public final boolean supports(Object handler) {
    //如果此handler是HandlerMethod类型并且,supportsInternal方法返回true(默认就返回true)
    //则此此适配器可以适应给定的handler,将返回true
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

/**
 * RequestMappingHandlerAdapter的方法
 * <p>
 * 始终返回true
 */
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

applyPreHandle执行拦截器的预处理

  在实际执行请求之前,应用拦截器链中的此前找到的所有HandlerInterceptor拦截器的PreHandle预处理方法,这里的拦截器是Spring MVC的拦截器。如果执行链应该继续下一个拦截器或处理程序本身,则返回true。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回,不会继续执行doDispatch方法。
  我们此前知道,如果存在CORS配置并且不是CORS预检请求,将会拦截器链的头部添加一个CorsInterceptor拦截器,此时就会执行该拦截器以校验CORS,如果不通过,那么preHandle返回false,后续handler方法也就不再执行,避免了由于CORS不通过但是业务代码执行的问题!而对于预检请求,它虽然没有添加这个拦截器,但是它的PreFlightHandler的handler仅仅调用DefaultCorsProcessor的processRequest方法而没有处理器方法的调用,这样就同样避免了业务代码的执行。注意,如果没有配置CorsConfiguration(无论是全局的还是局部的),或者请求不是预检请求,那么即使是CORS请求到来时,业务代码会按照正常请求流程来执行,也就不会有CORS校验,虽然浏览器不会呈现最终结果!

  每一个拦截器的preHandle方法返回true,则会使用interceptorIndex变量记录该拦截器在拦截器链中的下标索引。
  需要注意到,如果某个拦截器的preHandle方法返回false,则执行triggerAfterCompletion方法,triggerAfterCompletion方法将会对拦截器链中之前通过的(即preHandle方法返回true)的拦截器倒序的执行afterCompletion方法,最后返回false。

//HandlerExecutionChain的属性

/**
 * 当前应用的拦截器在拦截器链中的索引位置
 */
private int interceptorIndex = -1;

/**
 * 通过构造器指定的拦截器数组,要应用的拦截器链数组
 */
@Nullable
private HandlerInterceptor[] interceptors;

/**
 * initInterceptorList方法初始化的拦截器集合
 */
@Nullable
private List<HandlerInterceptor> interceptorList;

/**
 * HandlerExecutionChain的方法
 * <p>
 * 应用此Chain中注册的拦截器的preHandle方法。
 *
 * @return 如果执行链应该继续下一个拦截器或处理程序本身,则返回true。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回。
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //获取此前找到的拦截器链
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            //执行preHandle方法,如果某个拦截器的preHandle方法返回false则执行triggerAfterCompletion方法,随后返回false
            if (!interceptor.preHandle(request, response, this.handler)) {
                //执行triggerAfterCompletion方法
                //即对拦截器链中之前通过的拦截器执行afterCompletion方法
                triggerAfterCompletion(request, response, null);
                return false;
            }
            //改变当前执行的拦截器索引
            this.interceptorIndex = i;
        }
    }
    //所有的拦截器都通过,那么返回true
    return true;
}

handle实际处理请求

  如果此前的handler和HandlerAdapter都找到了,并且通过了所有拦截器的preHandle校验,那么通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验、返回值处理等等步骤。
  对于HandlerMethod来说,它是通过RequestMappingHandlerAdapter来调用的,最终目的就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑。
  我们直接看RequestMappingHandlerAdapter的handle方法的逻辑就行了,它的handle方法是由父类AbstractHandlerMethodAdapter实现的!

/**
 1. AbstractHandlerMethodAdapter的方法
 2. <p>
 3. 使用给定的handler来处理此请求,不同的handler所需的工作流程可能相差很大。
 4. 此实现期望handler为HandlerMethod类型。
 5.  6. @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了该请求(比如application/json),则返回null
 */
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    //委托另一个handleInternal方法处理请求,该方法由子类RequestMappingHandlerAdapter实现
    return handleInternal(request, response, (HandlerMethod) handler);
}

内部委托另一个handleInternal方法处理请求,该方法由子类RequestMappingHandlerAdapter实现。

  • 首先会调用checkRequest方法,检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常。
  • 随后调用invokeHandlerMethod方法真正的处理请求,执行handlerMethod方法,返回ModelAndView。
  • 如果自己没有设置Cache-Control响应头,那么帮助处理。
/**
 * RequestMappingHandlerAdapter的方法
 * <p>
 * 使用给定的HandlerMethod处理请求,真正的处理请求的入口
 *
 * @param request       当前request
 * @param response      当前response
 * @param handlerMethod 当前handlerMethod
 * @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了请求(比如application/json),则为null
 */
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    //检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常
    checkRequest(request);

    //如果需要,在同步块中执行invokeHandlerMethod,默认为false
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        //完全不需要session同步,则调用直接调用invokeHandlerMethod,这是常见逻辑
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    //如果自己没有设置Cache-Control响应头,那么帮助处理,一般都没有
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            prepareResponse(response);
        }
    }
    //返回ModelAndView
    return mav;
}

invokeHandlerMethod执行HandlerMethod

  在handleInternal方法中,最终是通过执行invokeHandlerMethod方法来执行HandlerMethod并处理请求的。该方法也是这个适配器中最重要的方法,它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、设置返回值等工作。
  最终返回一个ModelAndView对象,可用于视图解析,因此对于不需要视图解析的请求,比如application/json,则返回的是null。

/**
 * RequestMappingHandlerAdapter的方法
 * <p>
 * 执行HandlerMethod,尝试返回ModelAndView以用于视图解析
 */
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    //Request和Response包装为一个ServletWebRequest
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        /*
         * 1 获取WebDataBinderFactory,用于为当前请求创建一个 WebDataBinder实例,用于参数绑定
         */
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        /*
         * 2 获取ModelFactory,用于在控制器方法调用之前协助初始化Model,并在调用之后协助对其进行更新。
         */
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        /*
         * 3 根据给定的HandlerMethod定义创建一个ServletInvocableHandlerMethod,具有执行HandlerMethod的能力。
         * 还扩展了InvocableHandlerMethod的能力,使其能够通过注册的HandlerMethodReturnValueHandler处理返回值,
         * 并且还支持基于方法级别的@ResponseStatus注解设置响应状态。
         */
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        /*
         * 4 设置各种要用到的工具类属性
         */
        //设置参数解析器
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        //设置返回值解析器
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        //设置DataBinderFactory
        invocableMethod.setDataBinderFactory(binderFactory);
        //设置ParameterNameDiscoverer
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        /*
         * 4 创建ModelAndViewContainer并初始化Model对象的属性
         */
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //将上个请求传递的FlashMap的值设置到mavContainer
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        /*
         * 初始化Model对象,并填充模型属性
         * 1、从request中检索类上的@SessionAttributes的属性名对应的值并设置到mavContainer(只有没有的key才会存储)
         * 2、调用@ModelAttribute 注解标注的方法,并尝试将返回结果设置到mavContainer(只有没有的key才会存储)
         * 3、查找方法中标注了@ModelAttribute注解的方法参数并在@SessionAttributes中根据name 和type匹配查找属性名
         * 如果找到了属性名,则从request中检索该属性名的属性值并设置到mavContainer(只有没有的key才会存储)
         */
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        /*
         * 设置默认model中的数据在渲染和重定向中是否都可使用,默认可以,通过ignoreDefaultModelOnRedirect参数关闭
         * 或者如果控制器方法中存在RedirectAttributes参数,那么将使用该参数来确定在重定向中使用的参数
         */
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        /*
         * 5 异步请求处理相关,后面单独讲
         */
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
        /*
         * 6 核心方法,真正的执行handlerMethod方法并且进行处理,比如参数封装、校验、返回值处理成View对象或者JSON数据等等
         * 执行的结果会被放在mavContainer中,可能只有Model或者只有VIew或者都没有(比如application/json请求)
         */
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
        /*
         * 7 根据mavContainer、modelFactory、webRequest内容获取ModelAndView对象
         * ModelAndView对象中包含了Model、View、HttpStatus
         */
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        //在finally块中,发出请求已完成的信号。
        //将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false
        webRequest.requestCompleted();
    }
}
invokeAndHandle执行请求并处理

  调用控制器方法,并通过已配置的HandlerMethodReturnValueHandler其中之一处理返回值。它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、返回值处理成View对象或者JSON数据等等工作。
  执行的结果会被放在mavContainer中,可能只有Model或者只有VIew或者都没有(比如application/json请求)。

/**
 1. ServletInvocableHandlerMethod的方法
 2. <p>
 3. 调用控制器方法并通过已配置的HandlerMethodReturnValueHandlers其中之一处理返回值。
 4.  5. @param webRequest   当前 request
 6. @param mavContainer 此请求的ModelAndViewContainer
 7. @param providedArgs 按类型匹配的“给定”参数(未解析)
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {
    /*
     * 1 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回方法执行返回的原始值。
     * 这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果
     */
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    /*
     * 尝试设置响应状态码,这是为了支持方法上的@ResponseStatus注解
     */
    setResponseStatus(webRequest);
    //如果没有返回值
    if (returnValue == null) {
        //如果请求已被处理
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            //设置requestHandled属性为true,表示请求已被处理,方法结束
            //实际上,如果具有void返回类型(或null返回值)的方法也具有ServletResponse、
            //OutputStream参数或@ResponseStatus注解,那么requestHandled属性会被设置为true
            mavContainer.setRequestHandled(true);
            return;
        }
        //否则,如果存在responseStatusReason属性,即@ResponseStatus存在reason
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        //设置requestHandled属性为true,表示请求已被处理,方法结束
        mavContainer.setRequestHandled(true);
        return;
    }
    //到这里,表示请求未被处理完成,后面还要继续处理。一般没有@ResponseStatus注解并且方法正常完成的请求都会走这里
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        /*
         * 2 使用HandlerMethodReturnValueHandlerComposite中的HandlerMethodReturnValueHandler来处理返回值
         *
         * 通过向Model模型添加属性并设置View视图或调用ModelAndViewContainer.setRequestHandled
         * 方法设置为true来处理给定的返回值,以指示已直接处理响应,后续不需要处理。
         */
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}
invokeForRequest执行请求

  首先在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回方法执行返回的原始值。这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果。

首先调用getMethodArgumentValues方法获取方法参数,并为每个参数选择适当的参数解析器,解析、获取方法参数,最后按照参数顺序返回解析后的参数数组,Spring MVC的入参都是通过HandlerMethodArgumentResolver体系来解析的,其提供了几十个该类型的入参解析器。该过程中会进行依赖项的校验,比如某个参数有@RequestParam注解并且required=true,但是如果该参数不存在,那么就会抛出异常。该过程中还会执行@InitBinder方法。
对于常见的@RequestBody注解标注的方法参数,将会使用RequestResponseBodyMethodProcessor这个解析器来解析,实际上是依赖其内部的messageConverters来执行解析的。对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON反序列化。
随后调用doInvoke方法,根据解析出来的参数数组反射调用目标方法,返回原始结果,如果该方法还应用了@Validated校验,那么此时又会进行Validated校验规则,实际上此时的类对象是一个代理对象,它的控制器方法已被AOP增强,在方法实际调用之前将会应用Validated校验方法。

/**
 * InvocableHandlerMethod的方法
 * <p>
 * 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回原始结果
 * <p>
 * 参数值通常通过HandlerMethodArgumentResolvers进行解析,但是,providedArgs参数可以提供要直接使用的参数值,即无需参数解析。
 * 提供的参数值的示例包括WebDataBinder,SessionStatus或引发的异常实例,在参数解析之前检查提供的参数值。
 *
 * @param request      当前 request
 * @param mavContainer 此请求的ModelAndViewContainer
 * @param providedArgs 按类型匹配的“给定”参数,未解析,可以不传
 * @return 调用的方法返回的原始值
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                               Object... providedArgs) throws Exception {
    /*
     * 1 获取方法参数,并为每个参数选择适当的参数解析器,解析、获取方法参数,最后按照参数顺序返回解析后的参数数组
     *
     * Spring MVC的入参都是通过HandlerMethodArgumentResolver体系来解析的,其提供了几十个该类型的入参解析器
     * 该过程中会进行依赖项的校验,比如某个参数有@RequestParam注解并且required=true,但是如果该参数不存在,那么就会抛出异常
     * 该过程中还会执行@InitBinder方法
     *
     * 对于常见的@RequestBody标注的方法参数,将会使用RequestResponseBodyMethodProcessor这个解析器来解析
     * 实际上是依赖其内部的messageConverters来执行解析的。
     * 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON反序列化
     */
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    /*
     * 2 根据解析出来的参数数组反射调用目标方法,返回原始结果
     *
     * 如果该方法还应用了@Validated校验,那么此时又会进行Validated校验规则,实际上此时的类对象是一个代理对象
     * 它的控制器方法已被AOP增强,在方法实际调用之前将会应用Validated校验方法
     */
    return doInvoke(args);
}
handleReturnValue处理返回值

  遍历已注册的HandlerMethodReturnValueHandler并调用支持该返回值的那个处理器来处理返回值。
  对于常见的@ResponseBody标注的方法,将会使用RequestResponseBodyMethodProcessor这个解析器来解析,实际上是依赖其内部的messageConverters来执行解析的,对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON序列化,并将JSON字符串结果设置到response中,随后会调用mavContainer.setRequestHandled方法设置为true来指示已直接处理响应,后续无需解析、处理视图。

/**
 * HandlerMethodReturnValueHandlerComposite的方法
 * <p>
 * 遍历已注册的HandlerMethodReturnValueHandlers并调用支持该返回值和返回值的那个处理器来处理返回值
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    /*
     * 1 根据返回的原始返回值和类型选择可以处理的HandlerMethodReturnValueHandler
     *
     * 对于常见的@ResponseBody标注的方法,将会使用RequestResponseBodyMethodProcessor这个解析器来解析
     * 实际上是依赖其内部的messageConverters来执行解析的
     * 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON序列化,
     * 并将JSON字符串结果设置到response中
     */
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        //没找到就抛出异常
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    /*
     * 2 通过HandlerMethodReturnValueHandler处理返回值
     * 通过向Model模型添加属性并向mavContainer设置View视图
     * 或调用mavContainer.setRequestHandled方法设置为true来指示已直接处理响应,通常application/json请求会执行该操作
     */
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
getModelAndView获取ModelAndView

  获取ModelAndView,用以渲染视图,如果不需要渲染视图,则返回null,此情况通常是已将返回的数据写入response中了,比如application/json请求。
  这里的ModelAndView中通常包含的是model和viewName,model实际上是一个ModelMap,保存着一系列的数据,而在前后端不分离的项目中,controller方法所要跳转时返回的String字符串就是视图名,比如"/xx/xx"、"redirect:/xx"、"forward:/xx"。随后将会根据视图名解析为View视图对象。

/**
 1. RequestMappingHandlerAdapter的方法
 2. <p>
 3. 获取ModelAndView,用以渲染视图,如果不需要渲染视图
 4. 则返回null,此情况通常是已将返回的数据写入response中了,比如application/json请求
 */
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                     ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    /*
     * 1 更新Model数据
     * 也就是将列为@SessionAttributes的Model属性提升到session会话级别,还可能添加BindingResult属性。
     */
    modelFactory.updateModel(webRequest, mavContainer);
    //如果此请求已被处理完毕,比如application/json请求返回的JSON数据已被写入到了response中
    //那么不需要返回ModelAndView,也不需要渲染视图,返回null即可
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    /*
     * 2 设置ModelAndView,用于视图渲染
     */
    //获取model
    ModelMap model = mavContainer.getModel();
    /*
     * 创建一个ModelAndView对象,这里的view就是viewName,如果view是String类型,则算作视图名,这通常就是视图资源路径
     * 在前后端不分离的项目中,controller方法返回的String字符串就是视图名,比如"/xx/xx"、"redirect:/xx"、"forward:/xx"
     */
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    //判断是否不是视图名,如果不是视图名称,则直接设置为视图对象
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    /*
     * 3 重定向属性传递的处理
     */
    //如果model属于RedirectAttributes类型,即重定向,使用"redirect:"前缀进行重定向的都不是
    if (model instanceof RedirectAttributes) {
        //获取flashAttributes,用于重定向时参数的传递
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        //
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            //存入OutputFlashMap中,这是为后续请求保存的属性
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

processDispatchResult处理异常或渲染视图

  处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法。

  • 如果此前的执行中抛出了异常,那么在这里处理执行过程中的异常,将会通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView。
  • 随后根据的ModelAndView结果渲染视图。首先可能会调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现),随后调用View本身的render方法,真正的执行渲染的工作。
  • 最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法。
/**
 1. DispatcherServlet的方法
 2. <p>
 3. 处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图
 4. 最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法
 5.  6. @param request       当前request
 7. @param response      当前response
 8. @param mappedHandler 当前mappedHandler
 9. @param mv            执行返回的ModelAndView,可以为null
 10. @param exception     此前处理过程中抛出的异常,可以为null
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {
    //是否设置了异常视图
    boolean errorView = false;


    /*
     * 1 如果此前抛出了异常,则处理异常
     */
    if (exception != null) {
        //如果属于ModelAndViewDefiningException异常,该异常表示此请求应转发给具有特定model的特定view
        //这个异常通常是在业务代码中手动抛出的,用于将请求转发到错误视图
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        } else {
            //其他的异常
            //获取handler
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            /*
             * 通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView
             *
             * 尝试执行配置的全部HandlerExceptionResolver的resolveException方法,返回一个ModelAndView的结果
             * 如果某个异常解析器的返回值不为null,则基于该ModelAndView返回结果,否则将抛出参数中的异常
             */
            mv = processHandlerException(request, response, handler, exception);
            //如果确定了处理错误的ModelAndView,则errorView为true
            errorView = (mv != null);
        }
    }
    /*
     * 2 渲染视图
     */
    //判断是否需要渲染视图,即mv不为null并且内部的View不为null并且内部的Model也不为空
    if (mv != null && !mv.wasCleared()) {
        /*
         * 渲染给定的ModelAndView,即渲染视图
         * 这是处理请求的最后阶段,它可能涉及按视图名称来解析视图。
         */
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        /*
         * 3 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法
         *
         * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
         */
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

render渲染视图

  如果返回的ModelAndView不为null,并且内部的View不为null,并且内部的Model也不为空,那么就渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按视图名称解析为视图对象的逻辑。

如果视图是视图名,则调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。
随后调用View的render方法,真正的执行渲染的工作,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图。

/**
 * DispatcherServlet的方法
 * <p>
 * 渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按名称解析视图。
 *
 * @param mv       要渲染的ModelAndView
 * @param request  当前的HTTP Servlet请求
 * @param response 当前的HTTP Servlet响应
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    //确定请求的语言环境并将其应用于响应,用于国际化
    Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    //获取取视图名,如果view是String类型,那么view就是viewName,否则返回null。
    String viewName = mv.getViewName();
    //大多数情况都是viewName都不为null
    if (viewName != null) {
        /*
         * 1 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。
         */
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    } else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    /*
     * 委托给View对象进行渲染,也就是说具体的渲染工作是由View对象本身来完成的
     */
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        //设置相应状态码
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        /*
         * 2 调用View的render方法,真正的执行渲染的工作
         *
         * 渲染工作由View自己来完成,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图
         */
        view.render(mv.getModelInternal(), request, response);
    } catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}
resolveViewName解析视图名

  遍历viewResolvers,依次调用每一个ViewResolver的resolveViewName方法,尝试将给定的视图名称解析为一个View视图对象(待呈现)。如果某个返回结果不为null,则使用该返回的View。
  默认的viewResolver只有一个InternalResourceViewResolver,它将解析为viewName为InternalResourceView(请求转发或者包含)或者RedirectView(请求重定向)。这两个View类中的url属性就是viewName处理后的结果,也就是说viewName实际上就包含资源路径。

/**
 * DispatcherServlet的属性
 * <p>
 * 此servlet使用的ViewResolvers列表。
 */
@Nullable
private List<ViewResolver> viewResolvers;

/**
 * DispatcherServlet的方法
 * <p>
 * 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。
 */
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                               Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        /*
         * 遍历viewResolvers,依次调用每一个ViewResolver的resolveViewName方法,尝试获取View
         * 如果某个返回结果不为null,则使用该返回的View
         *
         * 默认的viewResolver只有一个InternalResourceViewResolver,它将解析为viewName为
         * InternalResourceView或者RedirectView(具有重定向前缀"redirect:")
         */
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    //没有解析成功,返回null
    return null;
}
view#render渲染视图

  真正的渲染视图是通过调用View自己的render方法来实现的。
  不同的View执行不同的渲染逻辑,常见的JSP等视图文件都是通过InternalResourceView/RedirectView来渲染的,而如果引入JSTL,则将使用JstlView,如果是FreeMarker视图则是通过FreeMarkerView渲染的。

/**
 * AbstractView的方法
 * <p>
 * 渲染视图
 */
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
                   HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
        logger.debug("View " + formatViewName() +
                ", model " + (model != null ? model : Collections.emptyMap()) +
                (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }
    //创建包含动态值和静态属性的组合输出Map(绝不为null)。
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    //准备响应,主要是对于视图生成下载内容的处理,比如PDF视图
    prepareResponse(request, response);
    /*
     * 委托renderMergedOutputModel方法进行实际渲染,该方法由子类提供的具体实现
     */
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
renderMergedOutputModel执行渲染

  来看看子类InternalResourceView的renderMergedOutputModel方法,它用于执行请求转发和请求包含,最终会委托给RequestDispatcher的forward 或者include这两个原始方法。

/**
 * InternalResourceView的方法
 * <p>
 * 根据指定的模型渲染内部资源,这包括将模型设置为请求属性。
 */
@Override
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    //公开模型对象作为请求属性,即将model的属性设置到request中(通过request.setAttribute方法)
    exposeModelAsRequestAttributes(model, request);

    //公开每个渲染操作特有的帮助器,通常JSTL的渲染需要
    exposeHelpers(request);

    // 准备渲染,并确定要转发到(或包括请求包含)的请求分派器路径,实际上此实现仅返回配置的url属性,也就是viewName处理后的结果。
    String dispatcherPath = prepareForRendering(request, response);

    //获取目标资源(通常是JSP)的RequestDispatcher
    //实际上就是request.getRequestDispatcher(path)方法
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    //如果是包含请求或响应已经提交,则执行请求包含,否则执行请求转发。
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including [" + getUrl() + "]");
        }
        //最终会执行RequestDispatcher#include这个原始方法
        rd.include(request, response);
    } else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to [" + getUrl() + "]");
        }
        //最终会执行RequestDispatcher#forward这个原始方法
        rd.forward(request, response);
    }
}

而对于RedirectView来说,最终会调用response.sendRedirect方法完成请求重定向。

总结

  上面我们详细介绍了Spring MVC处理请求的入口以及核心流程。
  源码入口就是FrameworkServlet#service方法,而核心的请求处理类就是DispatcherServlet。

  在学习了源码之后,我们可以总结出Spring MVC处理请求大概流程:

  • 请求到达DispatcherServlet。
  • 确定当前请求的处理器——handler。通过遍历handlerMappings,依次调用每个HandlerMapping的getHandler方法获取HandlerExecutionChain对象,只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找,如果在所有的handlerMapping中都没找到,则返回null。对于- RequestMappingHandlerMapping,他返回的handler就是HandlerMethod。该过程中还会进行CORS的配置,用于后续CORS请求的验证、处理。
  • 找到的handler实际上是一个HandlerExecutionChain对象,它包含了真正的handler以及适用于当前handler的HandlerInterceptor拦截器链。
  • 如果没有找到任何handler(handler返回null),那么通常返回404响应码或者抛出NoHandlerFoundException异常。
  • 根据handler确定当前请求的处理器适配器——HandlerAdapter。通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法,只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找,如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter。
  • 顺序应用拦截器链中所有拦截器的PreHandle预处理方法,只要有一个拦截器不通过,那么该请求就不会继续处理,而是对拦截器链中之前通过的(即此前的preHandle方法返回true)的拦截器倒序的执行afterCompletion方法,最后返回。如果都通过,那么继续后续处理。
  • 通过HandlerAdapter来执行给定的handler,将返回一个ModelAndView结果对象(可能返回null)。
  • 不同的handler的实际工作流程差别非常大,对于HandlerMethod来说,它是通过RequestMappingHandlerAdapter来调用的,最终目的就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑,其中还包括参数反序列化、数据绑定、检验、返回值处理、序列化等等步骤。
  • ModelAndView对象的Model部分是业务返回的模型数据,它是一个map,View部分为视图的逻辑视图名,即ViewName。
  • 返回的ModelAndView用以渲染视图,如果不需要渲染视图,则返回null,此情况通常是已将返回的数据序列化为JSON字符串写入response中了,比如application/json请求(通常是前后端分离的项目)。
  • handler正常处理完毕时,倒序应用拦截器链中的所有拦截器的postHandle后处理方法。
  • 处理handler执行过程中抛出的异常(如果有),将会通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView,借此我们可以对异常转发到统一配置的错误页面,或者直接返回错误的JSON数据!
  • 根据ModelAndView(无论是正产返回的还是错误处理的)渲染视图,如果此前返回的ModelAndView为null,则这一步不会执行。
  • 首先可能会调用ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现,内部保存了经过逻辑视图名viewName解析出来的物理视图资源URL路径)。
  • 随后调用View本身的render方法,真正的执行渲染的工作。但是,如果更加严格的话,View 更多的是用于在将模型数据移交给特定视图技术之前进行数据准备,而不是真正的进行视图渲染,视图渲染仍然依靠其该类型视图本身的视图渲染技术!
  • 对于一个简单的转发请求或者包含请求,如果目标是JSP视图,则View就是InternalResourceView,在渲染时首先就是将model数据存储到request域属性中,然后通过RequestDispatcher直接将请求include包含或者forward转发到物理视图路径URL对应的JSP资源,而include/forward方法则是我们在之前就见过的原始的Servlet API,最- 终还是通过response响应给用户的(对JSP来说就是将JSP渲染之后通过response输出为HTML文本)。
  • 从上面JSP视图的渲染和输出可以看出来,View中仅仅是进行了数据的封装和准备(比如将model数据存储到request域属性中),并没有进行实际渲染视图的工作(仅仅是调用include/forward方法),对于JSP视图来说,真正的视图渲染和数据填充以及返回响应仍然是依赖最原始JSP技术,与Spring MVC无关,与View无关。
  • 最后,无论请求处理成功还是失败抛出异常,都会倒序应用拦截器链中的所有拦截器的afterCompletion处理方法,表示请求处理完毕。

  为此,我们给出一幅比较完整的Spring MVC请求执行流程图:

image.png