Spring MVC 请求执行流程的源码深度解析两万字

Posted L-Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC 请求执行流程的源码深度解析两万字相关的知识,希望对你有一定的参考价值。

  基于最新Spring 5.x,详细介绍了Spring MVC 请求的执行流程源码,给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图。

  我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442

  此前,我们学习了Spring MVC项目启动初始化过程中的部分重要源码,Spring MVC项目启动之后,便能够接受请求、处理请求。
  此前,我们已经学习了Spring MVC的请求执行流程,但实际上在Spring MVC请求执行流程过程中,要做的事儿有很多,比图CORS配置、参数转换等等,现在让我们从源码的角度再一次深入了解Spring MVC 请求的执行流程。
  在文章的最后我们也给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图,嫌弃源码太长的小伙伴可以直接跳到末尾。
  下面的源码版本基于5.2.8.RELEASE

Spring MVC源码 系列文章

Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化

Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析

Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析

Spring MVC 请求执行流程的源码深度解析【两万字】

1 源码入口

  Spring MVC是对原始Servlet的封装,虽然我们在开发过程中不会再接触到Servlet级别的API,但是我们知道Spring MVC中有一个核心的Servlet实现,那就是DispatcherServlet,它作为核心控制器,用于接收任何的请求并将请求转发给对应的处理组件。因此,Spring MVC的请求处理入口仍然可以从DispatcherServlet中找到。
  DispatcherServlet的uml类图如下:
在这里插入图片描述
  可以看到,从它间接的继承了HttpServlet,因此Spring MVC的请求源码入口同样是HttpServlet#service()方法!
在这里插入图片描述
  这个service方法中并没有做太多的事情,主要是将ServletRequest和ServletResponse强转转换为HttpServletRequest和HttpServletResponse,最后会调用另一个service方法,该方法才是真正的核心处理请求的方法。
  HttpServlet自己的service方法源码如下。其内部时一系列的以do开头的模版方法,回顾一下,在早期原始的Servlet项目中,我们所要开发的就是这些doGet、doPost方法:
在这里插入图片描述
  并且,如果我们足够心细,我们能发现FrameworkServlet直接重写了HttpServlet的整个service方法,此前的原始Servlet开发的我们都是开发的do开头的方法,比如doGet、doPost,但是Spring MVC将整个service方法都重写了,我们一起来看看。

2 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方法的重要性了,继续向下看!

3 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);
    }
}

3.1 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);
            }
        }
    }
}

3.2 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()));
    }
}

4 doDispatch分发请求

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

  大概逻辑为:

  1. 调用getHandler方法确定当前请求的处理器——handler。
  2. 调用getHandlerAdapter方法根据handler确定当前请求的处理器适配器——HandlerAdapter。
  3. 调用applyPreHandle方法,顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法。全部通过则执行后续步骤,不通过则倒序执行已通过的拦截器的afterCompletion方法,随后直接返回。
  4. 调用handle方法,通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。
  5. handler正常处理完毕时,调用applyPostHandle方法,倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法。
  6. 调用processDispatchResult方法,处理执行handler的结果。主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图。
  7. 最后无论请求处理成功还是失败抛出异常,都会调用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) {
        /*
         * 倒序应用拦截器链中的此前找到的所有拦截器的afterC

以上是关于Spring MVC 请求执行流程的源码深度解析两万字的主要内容,如果未能解决你的问题,请参考以下文章

Spring6源码・MVC请求处理流程源码解析

Spring MVC注解Controller源码流程解析---请求匹配中的容错处理

Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上

Spring MVC注解Controller源码流程解析--定位HandlerMethod

Spring MVC工作原理及源码解析DispatcherServlet实现原理及源码解析

Spring MVC工作原理及源码解析 HandlerMapping和HandlerAdapter实现原理及源码解析