SpringMvc之請求執行流程

首先,讓我們來了解一下Spring MVC的幾個組件:DispatcherServletHandlerMappingHandlerAdapter以及ViewResolver:

1、前端控制器DispatcherServlet(不需要工程師開發),由框架提供
作用:接收請求,響應結果,相當於轉發器,中央處理器。有了dispatcherServlet減少了其它組件之間的耦合度。
用戶請求到達前端控制器,它就相當於mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,dispatcherServlet的存在降低了組件之間的耦合性。

2、處理器映射器HandlerMapping(不需要工程師開發),由框架提供
作用:根據請求的url查找Handler
HandlerMapping負責根據用戶請求找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等。

3、處理器適配器HandlerAdapter
作用:按照特定規則(HandlerAdapter要求的規則)去執行Handler
通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

4、處理器Handler(需要工程師開發)
注意:編寫Handler時按照HandlerAdapter的要求去做,這樣適配器纔可以去正確執行Handler
Handler 是繼DispatcherServlet前端控制器的後端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。
由於Handler涉及到具體的用戶業務請求,所以一般情況需要工程師根據業務需求開發Handler。

5、視圖解析器View resolver(不需要工程師開發),由框架提供
作用:進行視圖解析,根據邏輯視圖名解析成物理視圖(view)
View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最後對View進行渲染將處理結果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。
一般情況下需要通過頁面標籤或頁面模版技術將模型數據通過頁面展示給用戶,需要由工程師根據業務需求開發具體的頁面。

來看一下Spring MVC對用戶請求的處理過程,有如下時序圖:

具體處理過程如下:

  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()方法對用戶請求進行響應,根據前一篇文章的分析方法可以得到人如下的調用邏輯圖:

 從上圖的源碼調用邏輯可以看出,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對象,則會執行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就能夠捕獲到三大組件在處理用戶請求時的異常,通過這樣的方法能夠很方便的實現統一的異常處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章