专栏首页iOS技术杂谈SpringMVC DispatcherServlet执行流程及源码分析你要知道的SpringMVC DispatcherServlet执行流程及源码分析都在这里

SpringMVC DispatcherServlet执行流程及源码分析你要知道的SpringMVC DispatcherServlet执行流程及源码分析都在这里

你要知道的SpringMVC DispatcherServlet执行流程及源码分析都在这里

转载请注明出处 https://cloud.tencent.com/developer/user/1605429

本系列文章主要根据源码讲解SpringMVC的启动过程,以及相关重要组件的源码分析。阅读此系列文章需要具备Spring以及SpringMVC相关知识。本文将分以下几篇文章进行讲解,读者可按需查阅。

  • SpringMVC 启动流程及相关源码分析
  • SpringMVC DispatcherServlet执行流程及源码分析
  • SpringMVC HandlerMapping源码分析
  • SpringMVC HandlerAdapter源码分析

DispatcherServlet执行流程及相关源码分析

在前一篇文章SpringMVC 启动流程及相关源码分析中,详细探讨了Spring MVCWeb容器中部署后的启动过程,以及相关源码分析,同时也讨论了DispatcherServlet类的初始化创建过程,相关内容在此不再赘述,如有需求可查阅。

本文主要讲解DispatcherServlet类获取用户请求到响应的全过程,并针对相关源码进行分析。对于基本的MVC架构本文不再进行讲解,有需要的读者可自行查阅。

首先,让我们站在Spring MVC的四大组件:DispatcherServletHandlerMappingHandlerAdapter以及ViewResolver的角度来看一下Spring MVC对用户请求的处理过程,有如下时序图:

DispatcherServlet执行序列图

具体处理过程如下:

  • 1、用户请求发送至DispatcherServlet类进行处理。
  • 2、DispatcherServlet类遍历所有配置的HandlerMapping类请求查找Handler
  • 3、HandlerMapping类根据request请求URL等信息查找能够进行处理的Handler,以及相关拦截器interceptor并构造HandlerExecutionChain
  • 4、HandlerMapping类将构造的HandlerExecutionChain类的对象返回给前端控制器DispatcherServlet类
  • 5、前端控制器拿着上一步的Handler遍历所有配置的HandlerAdapter类请求执行Handler
  • 6、HandlerAdapter类执行相关Handler并获取ModelAndView类的对象。
  • 7、HandlerAdapter类将上一步Handler执行结果的ModelAndView 类的对象返回给前端控制器。
  • 8、DispatcherServlet类遍历所有配置的ViewResolver类请求进行视图解析。
  • 9、ViewResolver类进行视图解析并获取View对象。
  • 10、ViewResolver类向前端控制器返回上一步骤的View对象。
  • 11、DispatcherServlet类进行视图View的渲染,填充Model
  • 12、DispatcherServlet类向用户返回响应。

通过时序图和上面的讲解不难发现,整个Spring MVC对于用户请求的响应和处理都是以DispatcherServlet类为核心,其他三大组件均与前端控制器进行交互,三大组件之间没有交互并且互相解耦,因此,三大组件可以替换不同的实现而互相没有任何影响,提高了整个架构的稳定性并且降低了耦合度。接下来会按照上述的响应过程逐一进行讲解。

DispatcherServlet类本质上依旧是一个Servlet并且其父类实现了Servlet接口,我们知道,Servlet执行Service()方法对用户请求进行响应,根据前一篇文章的分析方法可以得到人如下的调用逻辑图:

service方法调用逻辑

从上图的源码调用逻辑可以看出,HttpServlet抽象类实现了Servlet接口service(ServletRequest, ServletResponse)的方法,因此,用户请求的第一执行方法为该方法,该方法紧接着直接调用了service(HttpServletRequest, HttpServletResponse)方法,其子类FrameworkServlet抽象类重写了该方法,因为多态的特性最终是调用了FrameworkServlet抽象类service(HttpServletRequest, HttpServletResponse)方法,FrameworkServlet抽象类同样也重写了doHead()doPost()doPut()doDelete()doOptions()doTrace()方法,service(ServletRequest, ServletResponse)方法根据请求类型的不同分别调用上述方法,上述六个方法都调用了processRequest()方法,而该方法最终调用了DispatcherServlet类doService()方法。通过层层分析,我们找到了最终要调用的处理用户请求的方法,doService()之前的方法调用都比较简单,这里不再逐一来查看源码,有兴趣的读者可以自行查阅。

查看doService()的源码如下:

    /**
     * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
     * for the actual dispatching.
     */
    @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.
        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(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        /*
        将当前Servlet的子IoC容器放入request请求中
        由此,我们可以访问到当前IoC子容器以及根IoC容器中的Bean
        */
        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 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()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

doService()方法主要进行一些参数的设置,并将部分参数放入request请求中,真正执行用户请求并作出响应的方法则为doDispatch()方法,查看doDispatch()方法的源码如下:

    /**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //用户的request请求
        HttpServletRequest processedRequest = request;
        //HandlerExecutionChain局部变量
        HandlerExecutionChain mappedHandler = null;
        //判断是否解析了文件类型的数据,如果有最终需要清理
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            //ModelAndView局部变量
            ModelAndView mv = null;
            //处理异常局部变量
            Exception dispatchException = null;

            try {
                //检查是否包含文件等类型的数据
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                //向HandlerMapping请求查找HandlerExecutionChain
                mappedHandler = getHandler(processedRequest);
                //如果HandlerExecutionChain为null,则没有能够进行处理的Handler,抛出异常
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                //根据查找到的Handler请求查找能够进行处理的HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                //判断自上次请求后是否有修改,没有修改直接返回响应
                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;
                    }
                }
                
                /*
                按顺序依次执行HandlerInterceptor的preHandle方法
                如果任一HandlerInterceptor的preHandle方法没有通过则不继续进行处理
                */
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                //通过HandlerAdapter执行查找到的handler
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                //逆序执行HandlerInterceptor的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);
            }
            //渲染视图填充Model,如果有异常渲染异常页面
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            //如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            //如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    //倒序执行所有HandlerInterceptor的afterCompletion方法
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                //如果请求包含文件类型的数据则进行相关清理工作
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

根据上述源码并结合文章开始讲解的DispatcherServlet类结合三大组件对用户请求的处理过程不难理解相关处理流程。

doDispatch()方法通过调用getHandler()方法并传入reuqest通过HandlerMapping查找HandlerExecutionChain,查看其源码如下:

    /**
     * Return the HandlerExecutionChain for this request.
     * <p>Tries all handler mappings in order.
     * @param request current HTTP request
     * @return the HandlerExecutionChain, or {@code null} if no handler could be found
     */
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

getHandler()方法遍历了开发者配置的所有HandlerMapping类根据request请求来查找HandlerExecutionChain,从这里可以看出,Spring MVC是支持用户配置多个HandlerMapping类的,在处理用户请求时会逐一查找,找到后立即返回,因此,如果多个HandlerMapping类都能够处理同一request请求,只会返回第一个能够处理的HandlerMapping类构造的HandlerExecutionChain,所以在配置HandlerMapping类时需要注意不要对同一请求多次进行处理,由于篇幅问题HandlerMapping类如何具体查找Handler并构造HandlerExecutionChain的细节不在此进行讲解,如有兴趣可以查阅本系列文章的第三篇SpringMVC HandlerMapping源码分析

如果没有找到对应的HandlerExecutionChain对象,则会执行noHandlerFound()方法,继续查看其源码如下:

    /**
     * No handler found -> set appropriate HTTP response status.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception if preparing the response failed
     */
    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (pageNotFoundLogger.isWarnEnabled()) {
            pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
                    "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        if (this.throwExceptionIfNoHandlerFound) {
            throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                    new ServletServerHttpRequest(request).getHeaders());
        }
        else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

如果没有找到对应的HandlerExecutionChain则会抛出异常NoHandlerFoundException,在开发的过程中,如果我们将具体的URL写错了则会遇到这个404错误。

继续查看doDispatch()方法的源码,如果找到了HandlerExecutionChain接下来会调用getHandlerAdapter()方法来查找能够对Handler进行处理的HandlerAdapter,查看其源码如下:

    /**
     * Return the HandlerAdapter for this handler object.
     * @param handler the handler object to find an adapter for
     * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
     */
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler adapter [" + ha + "]");
            }
            if (ha.supports(handler)) {
                return ha;
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

HandlerMapping类似,查找能够处理具体HandlerHandlerAdapter时同样会遍历所有配置了的HandlerAdapterHandlerAdapter是一个接口包含一个support()方法,该方法根据Handler是否实现某个特定的接口来判断该HandlerAdapter是否能够处理这个具体的Handler,这里使用适配器模式,通过这样的方式就可以支持不同类型的HandlerAdapter。如果没有查找到能够处理HandlerHandlerAdapter则会抛出异常,如果在开发的过程中Handler在实现接口时出现了问题就可能会遇到上述异常。

查找到了对应的HandlerAdapter后就会调用HandlerExecutionChainapplyPreHandle()方法来执行配置的所有HandlerInteceptorpreHandle()方法,查看其源码如下:

    /**
     * Apply preHandle methods of registered interceptors.
     * @return {@code true} if the execution chain should proceed with the
     * next interceptor or the handler itself. Else, DispatcherServlet assumes
     * that this interceptor has already dealt with the response itself.
     */
    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];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

HandlerExecutionChainapplyPreHandle()方法会按照顺序依次调用HandlerInterceptorpreHandle()方法,但当任一HandlerInterceptorpreHandle()方法返回了false就不再继续执行其他HandlerInterceptorpreHandle()方法,而是直接跳转执行triggerAfterCompletion()方法,查看该方法源码如下:

    /**
     * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
     * Will just invoke afterCompletion for all interceptors whose preHandle invocation
     * has successfully completed and returned true.
     */
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
            throws Exception {

        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }

这里遍历的下标为interceptorIndex,该变量在前一个方法applyPreHandle()方法中赋值,如果preHandle()方法返回true该变量加一,因此该方法会逆序执行所有preHandle()方法返回了trueHandlerInterceptorafterCompletion()方法。到这里读者已经掌握了HandlerInterceptorpreHandle()方法以及afterCompletion()方法的执行顺序,这些内容并不需要我们死记,需要知道其执行顺序查看源码是最好的方法。

继续阅读doDispatch()方法的源码,如果所有拦截器的preHandle()方法都返回了true没有进行拦截,接下来前端控制器会请求执行上文获取的Handler,这个Handler就是开发的时候编写的Controller,根据实现接口的不同执行相关方法,并获取到ModelAndView类的对象。

接下来会执行HandlerInterceptorpostHandle()方法,具体源码如下:

    /**
     * Apply postHandle methods of registered interceptors.
     */
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

可以发现,postHandle()方法是按照逆序执行。

执行完postHandle()方法后,doDispatch()方法调用了processDispatchResult()方法,其源码如下:

    /**
     * Handle the result of handler selection and handler invocation, which is
     * either a ModelAndView or an Exception to be resolved to a ModelAndView.
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

        boolean errorView = false;
        //判断HandlerMapping、HandlerAdapter处理时的异常是否为空
        if (exception != null) {
            //上述两个组件处理时的异常不为空
            //如果为ModelAndViewDefiningException异常,则获取一个异常视图
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            //如果不为ModelAndViewDefiningException异常,进行异常视图的获取
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }

        // Did the handler return a view to render?
        //判断mv是否为空,不管是正常的ModelAndView还是异常的ModelAndView,只要存在mv就进行视图渲染
        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()) {
            // Concurrent handling started during a forward
            return;
        }
        //执行相关HandlerInterceptor的afterCompletion()方法
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

该方法传入了一个异常类的对象dispatchException,阅读doDispatch()方法的源码可以看出,Spring MVC对整个doDispatch()方法用了嵌套的try-catch语句,内层的try-catch用于捕获HandlerMapping进行映射查找HandlerExecutionChain以及HandlerAdapter执行具体Handler时的处理异常,并将异常传入到上述processDispatchResult()方法中。

processDispatchResult()方法主要用于针对产生的异常来构造异常视图,接着不管视图是正常视图还是异常视图均调用render()方法来渲染,查看render()方法的具体源码如下:

    /**
     * Render the given ModelAndView.
     * <p>This is the last stage in handling a request. It may involve resolving the view by name.
     * @param mv the ModelAndView to render
     * @param request current HTTP servlet request
     * @param response current HTTP servlet response
     * @throws ServletException if view is missing or cannot be resolved
     * @throws Exception if there's a problem rendering the view
     */
    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()) {
            // We need to resolve the view name.
            // 解析视图名称获取对应视图View
            view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
            //如果视图View为空抛出异常
            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() + "'");
            }
        }

        // Delegate to the View object for rendering.
        if (logger.isDebugEnabled()) {
            logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        try {
            //设置Http响应状态字
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }
            //调用视图View的render方法通过Model来渲染视图
            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;
        }
    }

render()方法通过调用resolveViewName()方法根据视图名称解析对应的视图View,该方法源码如下:

    /**
     * Resolve the given view name into a View object (to be rendered).
     * <p>The default implementations asks all ViewResolvers of this dispatcher.
     * Can be overridden for custom resolution strategies, potentially based on
     * specific model attributes or request parameters.
     * @param viewName the name of the view to resolve
     * @param model the model to be passed to the view
     * @param locale the current locale
     * @param request current HTTP servlet request
     * @return the View object, or {@code null} if none found
     * @throws Exception if the view cannot be resolved
     * (typically in case of problems creating an actual View object)
     * @see ViewResolver#resolveViewName
     */
    protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
            HttpServletRequest request) throws Exception {

        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }

resolveViewName()方法通过遍历配置的所有ViewResolver类根据视图名称来解析对应的视图View,如果找到则返回对应视图View,没有找到则返回null

回到前一个render()方法,如果上述方法返回的视图为null则抛出异常,这个异常相信大多数人也见过,当开发时写错了返回的View视图名称时就会抛出该异常。接下来调用具体视图的render()方法来进行Model数据的渲染填充,最终构造成完整的视图。

到这里,doDispatch()的外层try-catch异常的作用我们就知道了,为了捕获渲染视图时的异常,通过两层嵌套的try-catchSpring MVC就能够捕获到三大组件在处理用户请求时的异常,通过这样的方法能够很方便的实现统一的异常处理。

总结

通过前文的源码分析,我们能够清楚的认识到Spring MVC对用户请求的处理过程,进一步加深对Spring MVC的理解。

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • iOS多线程——你要知道的NSOperation都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

    你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

    WWWWDotPNG
  • iOS多线程——RunLoop与GCD、AutoreleasePool你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

    你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

    WWWWDotPNG
  • iOS网络——NSURLSession详解及SDWebImage源码解析你要知道的NSURLSession都在这里

    你要知道的NSURLSession都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本...

    WWWWDotPNG
  • Golang Leetcode 541. Reverse String II.go

    版权声明:原创勿转 https://blog.csdn.net/anakinsun/article/details/89211437

    anakinsun
  • 聊聊ribbon的retry

    spring-cloud-netflix-ribbon-2.0.0.RELEASE-sources.jar!/org/springframework/cloud...

    codecraft
  • HDU 3367 Pseudoforest(Kruskal)

    Pseudoforest Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/655...

    ShenduCC
  • POJ--1690 (Your)((Term)((Project)))(字符串处理)

    (Your)((Term)((Project))) Time Limit: 1000MS Memory Limit: 10000K Total...

    ShenduCC
  • Springsecurity-oauth2之TokenEndPoint(2)

        当我们访问/oauth/token时,首先会经过BasicAuthenticationFilter,之后才会到TokenEndPoint

    克虏伯
  • Haproxy安装部署文档及多配置文件管理方案

    最近我在负责一个统一接入层的建设项目,涉及到 Haproxy 和 ospf 的运维部署,本文分享一下我在部署 Haproxy 之后整理的运维部署规范,并实现了H...

    张戈
  • Web自动化之Headless Chrome开发工具库

    重新打开终端,我们就可以直接通过 chrome打开稳定版的Chrome,chrome-canary打开试验版的Chrome了。

    IMWeb前端团队

扫码关注云+社区

领取腾讯云代金券