二、SpringMVC请求处理过程

1. 说说请求是怎么到DispatcherServlet的doDispatch()方法的

前面一篇文章我们已经分析过了SpringMVC的初始化流程(https://www.jianshu.com/p/b254a45612e8),现在我们继续探究一下SpringMVC的请求处理过程;
首先复习一下,Spring基于Servlet实现的类的继承结构,有了继承结构图,我们就能更好的分析其原理

结构图

如你所知,HttpServlet类中有处理请求的doGet(), doPost(), service()等方法,如下图所示

HttpServlet-Method

HttpServlet

有了HttpServlet的成员,我们再来看看它具体的实现,下面是HttpServlet接口的部分源码
关键部分都已经在源码中给出说明

public abstract class HttpServlet extends GenericServlet {

    /**
     * 该方法重写的GenericServlet类中的service()方法,
     * GenericServlet 类中的 service()方法是重写的 Servlet 接口的
     *
     * 该类中的这个方法作用是将 ServletRequest 请求转换为 HttpServletRequest 请求
     */
    @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的重载方法
        service(request, response);
    }


    /**
     *  根据传入的 HttpServletRequest 根据请求方式不同,调用不同的方法;
     *  比如 method是 GET 请求的话,就会调用doGet(HttpServletRequest req, HttpServletResponse resp) 方法
     *  其他的请求方式也是如此
     */
    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);
        }
    }

    /**
     * 如果HttpServletRequest的请求方式是 GET 的话,调用以下方法,
     *
     * 而Spring在 FrameworkServlet中对doGet()方法进行了实现
     */
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

}

从HttpServlet到FrameworkServlet

从源码可知,FrameworkServlet对部分不同类型的请求进行了实现,那么就来看看FrameworkServlet的源码


@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

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

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

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

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

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

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        //获取与当前线程关联的localeContText对象
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
        //为当前请求构建一个新的LocaleContext对象
        LocaleContext localeContext = buildLocaleContext(request);

        //获取与当前线程关联的RequestAttributes对象
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //为当前请求构建一个新的ServletRequestAttributes实例
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(org.springframework.web.servlet.FrameworkServlet.class.getName(), new org.springframework.web.servlet.FrameworkServlet.RequestBindingInterceptor());

        //让新构造的localeContext对象,requestAttributes对象与当前请求绑定,通过ThreadLocal完成
        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 {
            //doService()完成之后,解除localeContext对象,requestAttributes对象与当前请求的绑定
            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");
                    }
                }
            }

            //通过 new ServletRequestHandledEvent()发布事件
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
}

从FrameworkServlet到DispatcherServlet

从FrameworkServlet源码可知, doGet(), doPost(),doDelete(), doPut()等方法都调用了该类的processRequest()方法;而这个processRequest()方法最重要的就是doService()方法, 而doService()方法是由其子类DispatcherServlet实现的,所以我们不得不去看看DispatcherServlet的源码了;


@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {

    @Override
    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) + "]");
        }
        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        //判断请求是不是include请求,如果是include请求就对request的属性做一个备份,放到attributesSnapshot中, 在finally代码块中还原
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        // 设置请求的各种属性
        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,如果存在 flashMap 则进行复原
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
        try {
            //调用 doDispatch() 方法, 进行请求的分发
            doDispatch(request, response);
        } finally {
            //对备份的属性进行还原。
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }
}

至此,我们才到了DispatcherServlet处理请求的核心方法了, 至于doDispatch()方法,究竟是怎么处理的呢?
我们继续探究,先贴出doDispatch()的源码

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //这个用来保存实际上所用的 request 对象,在后面的流程中会对当前 request 对象进行检查,
        //如果是文件上传请求,则会对请求重新进行封装,如果不是文件上传请求,则继续使用原来的请求。
        HttpServletRequest processedRequest = request;

        //这是具体处理请求的处理器链,包括请求处理器和对应的Interceptor;
        HandlerExecutionChain mappedHandler = null;

        //文件上传请求的标记
        boolean multipartRequestParsed = false;

        //异步请求管理器
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            //渲染返回的 ModelAndView 对象
            ModelAndView mv = null;

            //请求处理过程中所抛出的异常,这个异常不包括渲染过程抛出的异常
            Exception dispatchException = null;

            try {
                //检查是不是文件上传请求,如果是则对当前 request 重新进行包装,如果不是,则直接将request返回
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                //获取当前请求的处理器
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    //抛出NoHandlerFoundException异常 或者 在返回404
                    noHandlerFound(processedRequest, response);
                    return;
                }

                //获取当前请求的处理器适配器
                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;
                    }
                }

                //拦截器的预处理方法
                //如果执行链应继续处理下一个拦截器或处理程序本身,则为true.否则,DispatcherServlet假设这个拦截器已经处理了响应本身。
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                //调用真正的请求,获取到返回结果 mv
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                //是否需要异步处理, 如果需要, 直接return
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                //如果不需要异步处理,则执行 applyDefaultViewName 方法,检查当前 mv 是否没有视图,如果没有(例如方法返回值为 void),则给一个默认的视图名
                applyDefaultViewName(processedRequest, mv);
                //执行拦截器的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);
            }
            //异常处理、渲染页面以及执行拦截器的 afterCompletion()方法
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

这里借用松哥大佬的一张流程图,来详细总结一下doDispatch的流程, 原文链接:https://blog.csdn.net/u012702547/article/details/115176519?spm=1001.2014.3001.5501

doDispatch流程

2. 探究doDispatch()方法中的getHandler(processedRequest)

未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容