Spring MVC处理请求过程(Spring MVC源码阅读系列之二)

本文是Spring MVC源码阅读系列的第二篇,在上一篇Spring MVC的创建过程中我们介绍了SpringMVC的创建过程,本文讲给主要介绍当一个请求到达时,SpringMVC都做了些什么,即Spring MVC是如何处理请求的。

前言

从上一篇Spring MVC的创建过程 我们知道Spring的HttpServletBean继承于HttpServlet,熟悉Servlet编程的同学都知道我们要编写一个新的Servlet只需继承HttpServlet,并重写相应的方法即可,当一个新的请求到来时,首先从Servlet接口的service方法开始,在HttpServlet的service方法中根据请求的类型不同将请求路由到对应的方法,Spring MVC的HttpServlet继承于Httpservlet类,换句话说Spring MVC也是重写了HttpServlet相应的方法来处理请求,还是内有乾坤,下面我们将遵循由整体到局部的学习方式,先了解Spring MVC处理请求的整体过程。

Spring MVC请求处理整体过程

Spring MVC请求处理的大体过程时请求由DispatcherServlet根据处理器映射确定控制器并分配给它,在控制器完成处理后,请求会被发送给一个根据视图解析器确定的视图来呈现输出结果。如下图:
《Spring MVC处理请求过程(Spring MVC源码阅读系列之二)》
1. web应用服务器接收到一个新请求是,如果匹配DispatcherServlet的请求映射路径,web容器将该请求转发给DispatcherServlet进行处理
2. DispatcherServlet接收到请求后,将根据请求的信息及HandlerMapping的配置找到处理请求的处理器(Handler)
3. 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,以统一的适配器接口调用Handler
4. 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息
5. ModelAndView中包含的是“逻辑视图名”,而非真正的视图对象,DispatcherServlet借助ViewResolver完成逻辑视图名到真实视图名对象的解析工作
6. 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染
7. 最终客户端得到的可能是HTML页面或者其他对象

Spring MVC的请求处理概述

Spring MVC的三个Servlet中的HttpservletBean主要参与了创建工作,没有涉及请求的处理;FrameworkServlet重写了除了doHead的所有处理请求的方法;DispatcherServlet描述了具体处理请求顶层设计结构。
接下来我们先看看FrameworkServlet是如何重写处理请求的方法,又是如何将请求交给DispatcherServlet进行具体的请求处理的。

FrameworkServlet

首先我们先来看看FrameworkServlet的service方法。

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

    String method = request.getMethod();
    //对PATCH类型请求的处理
    if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
        processRequest(request, response);
    }
    else {
        super.service(request, response);
    }
}

从上面的代码我们可以看出,FrameworkServlet在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理,即调用HttpServlet的service方法进行处理。为什么要这样做呢,不再调用super.service(),而是直接将请求交给processRequest处理不是更简单吗?从结构上来看确实如此,但是如果这样做的话会存在一些问题。例如:我们为了某种特殊需求需要在Post请求处理前对request进行一些处理,这时可能会新建一个继承自DispatcherServlet的类,然后覆盖doPost方法,在里面对request进行处理,然后再调用super.doPost方法,但是父类根本没有调用doPost,这时就会出现问题,虽然有其他方法来解决这个问题,但按正常的逻辑,调用doPost应该可以完成才合理,而且一般情况下开发者并不需要对Spring MVC 的内部结构非常了解。所以Spring MVC的这种做法是有必要的。从前言我们已经知道HttpServlet的service方法是根据请求类型的不同将请求路由到不同的处理方法的,那么接下来我们来看看doGet方法(其余方法需要自己处理的方法与doGet类似)

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

我们从上面的代码以及其余的doXXX类型方法中可以发现,这里所做的事情与Httpservle里将不同类型的请求路由到不同方法进行处理的思路正好相反,这里又将所有的请求合并到了processRequest方法中。下面我们将一步步解开这神秘的面纱。首先我们先来看看processRequest方法,其代码如下:

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

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    //获取LocaleContextHolder中原来保存的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    //获取当前请求的LocaleContext
    LocaleContext localeContext = buildLocaleContext(request);
    //获取RequestContextHolder中原来保存的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    //获取当前请求的ServletRequestAttributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中
    initContextHolders(request, localeContext, requestAttributes);

    try {
        //具体处理方法入口
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }
    finally {
        //恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }
        //发布ServletRequestHandlerEvent消息
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

在上面的代码中核心语句时doService(request,response),这是一个模板方法,在DispatcherServlet中具体实现。在doService前后还做了一些事情,也就是大家熟悉的装饰模式:首先获取了LocalContextHolder和RequestContextHolder中原来保存的LocaleContext和RequestAttributes并设置到previousLocaleContext和previousAttributes临时属性,用于最后将原来的LocaleContext和RequestAttributes恢复;然后调用buildLocalContext和buildRequestAttributes方法获取到当前请求的LocaleContext和RequestAttributes,并通过initContextHolders方法将它们设置到LocaleContextHolder和RequestContextHolder中,最后在finally中将原来的LocaleContext和RequestAttributes恢复到LocaleContextHolder和RequestAttributesHolder中,并调用publishRequestHandleEvent方法发布一个ServletRequestHandledEvent类型的消息。

或许大家对于设置LocaleContext和RequestAttributes不是很理解,在这我们再深入探索一番,帮助大家更好地理解其目的。首先我们要知道LocaleContext里存放的是Locale(本地化信息),因而是用于获取Locale,而RequestAttributes是spring的一个接口,用于管理request和session的属性,通过它可以get/set/removeAttribute,根据scope参数判断是操作request还是session,使用的具体实现类是ServletRequestAttributes类,它还封装了request、response和session,而且提供了get方法,可以直接获取。到这里或许大家对于LocaleContext和RequestAttributes的作用有了大概的了解,可是它们又是如何使用的呢。通过debug进initContextHolders方法,我们可以发现LocalContext和RequestAttributes是保存在LocalContextHolders和RequestAttributesHolders中的。接下来我们来看看LocalContextHolders和RequestAttributesHolders。

首先我们先来看看LocalContextHolders,这是一个abstract类,但里面的方法都是static的,可以直接调用,而且没有父类也没子类,因而我们不能对其进行实例化,只能调用其static方法。这种abstract的使用方式值得我们学习。在LocaleContextHolder中定义了两个属性:

private static final ThreadLocal<LocaleContext> localeContextHolder =
            new NamedThreadLocal<LocaleContext>("Locale context");

private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
            new NamedInheritableThreadLocal<LocaleContext>("Locale context");

LocaleContextHolder还提供了get/set方法,可以获取和设置LocaleContext,另外还提供了get/setLocale方法,可以直接操作Locale,保存在ThreadLocal中能够保证多个请求之间相互独立,互不影响,对于ThreadLocal的作用及其实现原理,我们将在后面的文章中进行说明。相信到此,大家已经初步理解这样做的目的:为了方便在项目的任何地方使用Locale,而不需要将其作为参数进行传递到对应的地方,只需要调用一下LocaleContextHolder的getLocale()方法即可。

RequestAttributesHolder也是一样的道理,里面封装了RequestAttributes,可以get/set/removeAttribute,而且因为实际封装的是ServletRequestAttributes,因此还可以通过get/set方法获取或修改request、response、session对象。

概括总结一下FrameworkServlet处理请求的过程:
FrameworkServlet重写除doHead的所有处理请求的方法,并在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理;与HttpServlet里将不同类型的请求路径路由到不同方法进行处理的思路相反,doXXX类型方法又将所有请求合并到了processRequest方法中;在processRequet方法中,除了异步请求和调用doService方法具体处理请求,主要做了两件事:
1. 对LocaleContext和RequestAttributes的设置,为了便于在其他层次调用locale信息和request信息;而对它们的恢复是为了不影响Servlet以外的处理,如Filter
2. 处理完成后发布了ServletRequestHandledEvent消息

DispatcherServlet

接下来我们将来弄明白Spring MVC最核心的类——DispatcherServlet,整个处理过程的顶层设计都在这里。从上面FrameworkServlet的分析中,我们知道DispatcherServlet里面执行处理的入口方法是doService,其代码如下:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
            " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    //当include请求时对request的Attribute做快照备份
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    //对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());
    //flashMap主要用于Redirect转发时参数的传递
    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()) {
            //还原request快照的备份
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

由上面的代码我们可以看出doService并没有直接进行处理,而是交给了doDispatcher进行具体的处理,在交给doDispatcher处理前,doService还做了以下的事:
1. 判断是不是include请求,如果是则对request的Attribute做个快照备份,等doDispatcher处理完成后进行还原。
2. 对request设置了一些属性,至于FlashMap则是主要用于Redirect转发时参数的传递。

分析完doService,接下来我们将对doDispatcher进行分析,其代码如下:

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

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

        try {
            //检查是不是上传请求
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //根据request找到Handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //根据Handler找到HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // 处理GET、HEAD请求的Last-Modified
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            //执行相应的Interceptor的preHandle
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // HandlerAdapter使用Handler处理请求
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            //如果需要异步处理,直接返回
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            //当view为空时,根据request设置默认view,如Handler返回值为void
            applyDefaultViewName(request, mv);
            //执行相应Interceptor的postHandle
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        //处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // 删除上传资源
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

上述的代码中最核心的代码只有4句,它们的任务分别是:
1. 根据request找到Handler
2. 根据Handler找到对应的HandlerAdapter
3. 用HandlerAdapter处理Handler
4. 调用processRequest方法处理上面处理之后的结果(包含找到View并渲染输出给用户)
对应的代码如下:

mappedHandler = getHandler(processedRequset)
HandlerAdaptor ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
processDispatchResult(processRequest,response,mappedHandler,mv,dispatcherException);

了解完doDispatcher的核心语句之后,我们再来详细分析其内部结构以及处理的流程。

doDispatcher首先检查是不是上传请求,如果是则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。

然后通过getHandler获取Handler处理器链,具体的获取过程,后面分析HandlerMapping时再进行详细分析。

接下来处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器在返回的请求头里会包含一个last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的last-modified,服务器接收到带Last-modified的请求后会用其值和自己实际资源的最后修改时间作对比,如果资源过期了则返回新的资源(同时返回新的last-modified),否则直接返回304状态吗表示资源未过期,浏览器直接使用之前缓存的结果。

接下来依次调用相应的Interceptor的preHandle。处理完preHandle后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这执行的,具体内容在分析HandlerAdapter时在详细解释。

Handler处理完请求后,如果需要异步处理则直接返回,如果不需要异步处理,当view为空时,设置默认view,然后执行相应的Interceptor的postHandle。

至此,请求处理的内容就完成了,接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。

最后是doDispatcher的异常处理,doDispatcher有两层异常处理,内层是捕获在对请求进行处理过程中抛出的异常,外层主要是在处理渲染页面时抛出的。
processDispatchResult代码如下:

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

    boolean errorView = false;
    //如果请求处理的过程中有异常抛出则处理异常
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    //渲染页面
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        //如果启动了异步处理则返回
        return;
    }
    //发出请求处理完成的通知,触发Interceptor的afterCompletion
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    View view;
    if (mv.isReference()) {
        //View如果是引用类型,则需要调用resolveViewName使用ViewResolver得到实际的View
        view = resolveViewName(mv.getViewName(), 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() + "'");
        }
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
    //对页面进行具体渲染
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

从上面代码我们可以看出,processDispatchResult处理异常的方式是将相应的错误页面设置到View,并在其中的processHandlerException方法中用到了HandlerExceptionResolver。

渲染页面具体在render方法中执行,render中首先对response设置了Local,过程中使用到了LocalResolver,然后判断View如果是String类型则调用resolveViewName方法使用ViewResolver得到实际的View,最后调用view的render方法对页面进行具体的渲染,该过程使用到了ThemeResolver。

最后通过mappedHandler的triggerAfterCompletion方法触发Interceptor的afterCompletion方法。

至此doDispatcher的结构已经分析完毕,在上面我们还留下一个问题没有解决,那就是为什么Spring MVC对处理请求的思路与HttpServlet的将不同类型的请求路由到不同的方法进行处理的思路相反,将所有的请求合并到了processRequest方法中,这是因为Spring MVC将不同类型的请求用不同的Handler进行处理,但通过HandlerAdapter对Handler进行封装,以统一的适配器接口调用Handler,因而将所有的请求合并到了processRequest方法中。

概括总结一下DispatcherServlet的处理过程:
DispatcherServlet的处理请求入口方法是doService,但它没有直接进行具体处理,而是将具体处理交给了doDispatcher,而doDispatcher方法从顶层设计了整个请求处理的过程:
1. 根据request找到Handler
2. 根据Handler找到对应的HandlerAdapter
3. 用HandlerAdapter处理Handler
4. 调用processRequestResult方法处理上面处理之后的结果(包括异常处理、渲染页面、触发Interceptor的afterCompletion方法)

最后解释一下三个概念:HandlerMapping、Handler和HandlerAdapter。这三个概念的准确理解对于Spring MVC的学习非常重要。

  1. HandlerMapping,是用来查找Handler的,在Spring MVC中会处理很多请求,每个请求都需要一个Handler来处理,具体接收到一个请求后由HandlerMapping决定使用哪个Handler来处理。
  2. Handler,也就是处理器,直接对应MVC中的C也就是Controller层,标注了@RequestMapping的所有方法都可以看成一个Handler
  3. HandlerAdapter,因为Spring MVC中的Handler可以是任意的形式,但Servlet需要处理的方法的结构确实固定的,都是以request和response为参数,因而需要HandlerAdapter来让固定的Servlet处理方法调用灵活的Handler来进行具体处理。

doDispatcher流程

《Spring MVC处理请求过程(Spring MVC源码阅读系列之二)》
上图是doDispatcher的流程图,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDispatcher方法处理过程中所涉及的组件。图中上半部分的处理请求对应着MVC的Controller即C层,下半部分的processRequestResult主要对应MVC中的View即V层,M层也就是Model贯穿于与整个过程中。

参考资料

  • 《看透Spring MVC 源代码分析与实践》
  • 《Spring Reference》
    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/u013180299/article/details/52297787
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞