1. 萬字長文,深度解析SpringMVC 源碼,讓你醍醐灌頂!!

        共 43044字,需瀏覽 87分鐘

         ·

        2021-09-15 12:00


        文末可以領取所有系列高清 pdf。

        大家好,我是路人,這是 SpringMVC 系列第 16 篇。

        本文將通過閱讀源碼的方式帶大家了解 springmvc 處理請求的完整流程,干貨滿滿。

        目錄

        • 1、先了解下 SpringMVC 常用的 10 組件

          • 1.1、DispatcherServlet:前端控制器

          • 1.2、HandlerMapping:處理器映射器

          • 1.3、HandlerExecutionChain:處理器執(zhí)行鏈

          • 1.4、handler:處理器

          • 1.5、HandlerAdapter:處理器適配器

          • 1.6、ModelAndView:模型和視圖

          • 1.7、ViewResolver:視圖解析器

          • 1.8、View:視圖

          • 1.9、HandlerExceptionResolver:處理器異常解析器

          • 1.10、HttpMessageConverter:http 報文轉換器

        • 2、處理流程:源碼解析

          • 2.1、請求到達入口:doDispatch

          • 2.2、①:解析 multipart 類型的請求

          • 2.3、②:根據請求獲取 HandlerExecutionChain 對象

          • 2.4、③:根據處理器獲取 HandlerAdapter

          • 2.5、④:調用攔截器的 preHandle 方法

          • 2.6、⑤:調用 handler 實際處理請求,獲取 ModelAndView 對象

          • 2.7、⑥:調用攔截器的 postHandle 方法

          • 2.8、⑦:渲染視圖

        • 3、處理流程:純文字描述

        • 4、小結

        • 5、案例代碼

        • 6、SpringMVC 系列

        • 7、更多好文章

        • 8、【路人甲 Java】所有系列高清 PDF


        1、先了解下 SpringMVC 常用的 10 組件

        1.1、DispatcherServlet:前端控制器

        這個大家是最熟悉的,是一個 servlet,是 springmvc 處理請求的入口,不需要咱們開發(fā),由框架提供。

        作用:統一處理請求和響應,整個流程控制的中心,由它來調用其他組件處理用戶的請求。

        1.2、HandlerMapping:處理器映射器

        作用:根據請求的信息(如 url、method、header 等)查找請求處理器,即找到自定義的 controller 中處理請求的方法。

        HandlerMapping 接口源碼如下,getHandler:根據請求查找請求處理器,會返回一個 HandlerExecutionChain 對象。

        public interface HandlerMapping {
         HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
        }

        常見的實現類:

        • RequestMappingHandlerMapping:請求映射處理器映射,用來處理@RequestMapping 定義的處理器的

        1.3、HandlerExecutionChain:處理器執(zhí)行鏈

        HandlerMapping#getHandler 方法會根據請求得到一個 HandlerExecutionChain 對象。

        HandlerExecutionChain 源碼如下,主要包含了 3 個信息

        • handler:請求處理器,通常就是我們自定義的 controller 對象及方法
        • interceptorList:攔截器,當前請求匹配到的攔截器列表
        • interceptorIndex:攔截器索引,用來記錄執(zhí)行到第幾個攔截器了
        public class HandlerExecutionChain {

         private final Object handler;

         private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

         private int interceptorIndex = -1;

        }

        1.4、handler:處理器

        通常需要我們自己開發(fā),一般指我們自定義的 controller,在 DispatcherServlet 的控制下 handler 對具體的請求進行處理。

        1.5、HandlerAdapter:處理器適配器

        他負責對 handler 的方法進行調用,由于 handler 的類型可能有很多種,每種 handler 的調用過程可能不一樣,此時就需要用到適配器 HandlerAdapte,適配器對外暴露了統一的調用方式(見其 handle 方法),內部將 handler 的調用過程屏蔽了,HandlerAdapter 接口源碼如下,主要有 2 個方法需要注意:

        • supports:當前 HandlerAdapter 是否支持 handler,其內部主要就是判 HandlerAdapter 是否能夠處理 handler 的調用
        • handle:其內部負責調用 handler 的來處理用戶的請求,返回返回一個 ModelAndView 對象
        public interface HandlerAdapter {

         boolean supports(Object handler);

         @Nullable
         ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

        }

        常見的實現類:

        • RequestMappingHandlerAdapter:其內部用來調用@RequestMapping 標注的方法

        1.6、ModelAndView:模型和視圖

        這個對象中主要用來存放視圖的名稱和共享給客戶端的數據。

        public class ModelAndView {

         /*視圖*/
         @Nullable
         private Object view;

         /*模型,用來存放共享給客戶端的數據*/
         @Nullable
         private ModelMap model;

        }

        1.7、ViewResolver:視圖解析器

        這個是框架提供的,不需要咱們自己開發(fā),它負責視圖解析,根據視圖的名稱得到對應的視圖對象(View)。

        ViewResolver 接口源碼

        public interface ViewResolver {

         @Nullable
         View resolveViewName(String viewName, Locale locale) throws Exception;

        }

        這個接口有很多實現類,比如 jsp 的、freemarker、thymeleaf 的等,他們都有各自對應的 ViewResolver。

        而比較常的實現類是InternalResourceViewResolver,這個大家應該比較熟悉吧,目前為止我們前面的文章用到的都是這個視圖解析器,用來處理 jsp 格式的視圖頁面,帶大家再回顧一下這個類的配置,如下

        <!-- 添加視圖解析器 -->
        <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/view/"/>
            <property name="suffix" value=".jsp"/>
        </bean>

        InternalResourceViewResolver 比較重要,這里說下這個類的 resolveViewName 方法獲取視圖的過程,大家也可以去閱讀InternalResourceViewResolver#resolveViewName方法獲得,大致的過程如下:

        step1:判斷視圖 viewName 是否以redirect:開頭,如果是,則返回RedirectView類型的視圖對象,RedirectView 是用來重定向的,RedirectView 內部用到的是response.sendRedirect(url)進行頁面重定向;否則繼續(xù)向下 step2

        step2:判斷 viewName 是否以forward:開頭,如果是,則返回InternalResourceView類型的視圖對象,InternalResourceView 是用來做跳轉的,InternalResourceView 內部用到的是request.getRequestDispatcher(path).forward(request, response)進行頁面跳轉;否則繼續(xù)向下 step3

        step3:判斷當前項目是否存在 jstl 所需的類,如果是,則返回 JstlView 類型的視圖,否則返回 InternalResourceView 類型的視圖,這兩個視圖的 render 方法最終會通過request.getRequestDispatcher(path).forward(request, response)進行頁面的跳轉,跳轉的路徑是:InternalResourceViewResolver 的前綴 prefix + viewName+InternalResourceViewResolver 的后綴 prefix

        1.8、View:視圖

        負責將結果展示給用戶,View 接口源碼如下,render 方法根據指定的模型數據(model)渲染視圖,即 render 方法負責將結果輸出給客戶端。

        public interface View {
         void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
           throws Exception
        ;
        }

        View 接口常見的 2 個實現類

        • RedirectView:負責重定向的,內部通過response.sendRedirect(url)進行頁面重定向
        • InternalResourceViewResolver:負責頁面跳轉的,內部通過request.getRequestDispatcher(path).forward(request, response)進行頁面的跳轉

        1.9、HandlerExceptionResolver:處理器異常解析器

        負責處理異常的,HandlerExceptionResolver 接口有個resolveException方法,用來解析異常,返回異常情況下對應的 ModelAndView 對象

        public interface HandlerExceptionResolver {

         @Nullable
         ModelAndView resolveException(
           HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
        ;

        }

        1.10、HttpMessageConverter:http 報文轉換器

        將請求報文轉換為 Java 對象,或將 Java 對象轉換為響應報文,在處理@RequestBody、RequestEntity、@ResponseBody、ResponseEntity 的時候會用到

        public interface HttpMessageConverter<T{

         /**
          * 是否可以將請求保溫讀取給方法參數指定的類型
          */

         boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

         /**
          * 是否可以將響應的保溫轉換為方法參數指定的類型輸出
          */

         boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

         /**
          * 當前轉換器支持的類型
          */

         List<MediaType> getSupportedMediaTypes();

         /**
          * 當前轉換器支持的類型
          */

         default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
          return (canRead(clazz, null) || canWrite(clazz, null) ?
            getSupportedMediaTypes() : Collections.emptyList());
         }

         /**
          * 將http保溫轉換為給定的類型,然后返回
          */

         read(Class<? extends T> clazz, HttpInputMessage inputMessage)
           throws IOException, HttpMessageNotReadableException
        ;

         /**
          * 將給定的對象t,轉換為http報文輸出到客戶端
          */

         void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
           throws IOException, HttpMessageNotWritableException
        ;

        }

        2、處理流程:源碼解析

        2.1、請求到達入口:doDispatch

        springmvc 的所有請求,最終都會到達org.springframework.web.servlet.DispatcherServlet#doDispatch這個方法,整個請求的大致處理過程都在這個方法中,咱們從這個方法開始分析,源碼如下,大家注意代碼中的注釋,帶有標號,比如 ①、②、③ 這樣需要的注釋,大家需要注意了,這些是關鍵的步驟,稍后會對這些步驟做詳細的說明

        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //請求對象
            HttpServletRequest processedRequest = request;
            //處理器執(zhí)行鏈對象
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;

            //獲取異步處理管理器,servlet3.0后支持異步處理,可以在子線程中響應用戶請求
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

            try {
                //模型和視圖
                ModelAndView mv = null;
                //異常對象
                Exception dispatchException = null;

                try {
                    //①:解析multipart類型的請求,上傳文件用的就是multipart類型的請求方式
                    processedRequest = checkMultipart(request);
                    //用來標記是否是multipart類型的請求
                    multipartRequestParsed = (processedRequest != request);

                    //②:根據請求獲取HandlerExecutionChain對象
                    mappedHandler = getHandler(processedRequest);
                    //如果沒有找到處理器,就404了
                    if (mappedHandler == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }

                    //③:根據處理器獲取HandlerAdapter
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                    //④:調用攔截器的preHandle方法,若返回false,處理結束
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    //⑤:調用handler實際處理請求,獲取ModelAndView對象,這里會調用HandlerAdapter#handle方法處理請求,其內部會調用handler來處理具體的請求
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                    //判斷異步請求不是已經開始了,開始了就返回了
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    //如果mv對象中沒有視圖 & DispatcherServlet配置了默認的視圖,則給mv安排一個默認的視圖
                    applyDefaultViewName(processedRequest, mv);

                    //⑥:調用攔截器的postHandle方法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                catch (Throwable err) {
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                //⑦:處理分發(fā)結果,渲染視圖(包含了正常處理和異常情況的處理),將結果輸出到客戶端
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                //⑧:調用攔截器的afterCompletion方法
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                //⑧:調用攔截器的afterCompletion方法
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                //對于異步處理的情況,調用異步處理的攔截器AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法
                if (asyncManager.isConcurrentHandlingStarted()) {
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    //對于multipart的請求,清理資源,比如文件上傳的請求,在上傳的過程中文件會被保存到臨時文件中,這里就會對這些文件繼續(xù)清理
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }

        下面我們來對上面帶有編號的步驟進行分析。

        2.2、①:解析 multipart 類型的請求

        //①:解析multipart類型的請求,上傳文件用的就是multipart類型的請求方式
        processedRequest = checkMultipart(request);

        checkMultipart(request)源碼

        protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
            //判斷multipartResolver解析器是否存在 && 請求是否是multipart類型
            if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
                //將請求轉換為multipart類型的請求對象,通常為MultipartHttpServletRequest類型
                return this.multipartResolver.resolveMultipart(request);
            }
            return request;
        }

        2.3、②:根據請求獲取 HandlerExecutionChain 對象

        //②:根據請求獲取HandlerExecutionChain對象
        mappedHandler = getHandler(processedRequest);

        getHandler(processedRequest)源碼如下,遍歷所有的處理器映射器HandlerMapping,調用他們的getHandler方法得到能夠處理當前請求的HandlerExecutionChain對象,這個對象中包含了 3 個信息

        • handler:請求處理器,通常就是我們自定義的 controller 對象及方法
        • interceptorList:攔截器,當前請求匹配到的攔截器列表
        • interceptorIndex:攔截器索引,用來記錄執(zhí)行到第幾個攔截器了
        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            if (this.handlerMappings != null) {
                for (HandlerMapping mapping : this.handlerMappings) {
                    HandlerExecutionChain handler = mapping.getHandler(request);
                    if (handler != null) {
                        return handler;
                    }
                }
            }
            return null;
        }

        有興趣的可以去看一下RequestMappingHandlerMapping這個類的源碼,也是最常用的一個 HandlerMapping,它會根據@RequestMapping來找到能夠處當前請求的處理器,RequestMappingHandlerMapping#getHandler 方法查找得到的 HandlerExecutionChain 對象中的 handler 類型為HandlerMethod,代碼在下面這個位置

        org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

        HandlerMethod 對象中包含了能夠處理請求的 bean 及方法信息

        2.4、③:根據處理器獲取 HandlerAdapter

        //③:根據處理器獲取HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        getHandlerAdapter方法源碼,遍歷HandlerAdapter列表,找到能夠處理當前 handler 的HandlerAdapter,如果沒找到會報錯

        protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
            if (this.handlerAdapters != null) {
                for (HandlerAdapter adapter : this.handlerAdapters) {
                    if (adapter.supports(handler)) {
                        return adapter;
                    }
                }
            }

            throw new ServletException("No adapter for handler [" + handler +
                                       "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
        }

        此方法通常返回的是RequestMappingHandlerAdapter類型的對象,RequestMappingHandlerAdapter這個類會根據HandlerMethod提供的信息,通過反射調用@RequestMapping 標注的方法。

        2.5、④:調用攔截器的 preHandle 方法

        //④:調用攔截器的preHandle方法,若返回false,處理結束
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        mappedHandler.applyPreHandle源碼如下,主要干了 3 個事情

        • 循環(huán)調用攔截器的preHandle方法
        • 如果某個攔截器的preHandle方法返回 false,則反向依次調用那些 preHandle 方法返回 ture 的攔截器的 afterCompletion 方法;這句話有點繞,比如有 3 個攔截器,1、2 的 preHandler 返回了 true,而 3 返回的是 false,那么這里將按照 2、1 的順序調用他們的 afterCompletion 方法
        • 記錄攔截器的執(zhí)行位置
        boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
            for (int i = 0; i < this.interceptorList.size(); i++) {
                HandlerInterceptor interceptor = this.interceptorList.get(i);
                //調用攔截器的preHandle方法
                if (!interceptor.preHandle(request, response, this.handler)) {
                    //如果攔截器返回false,則反向依次調用那些preHandle方法返回ture的攔截器的afterCompletion方法
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                //記錄當前攔截器執(zhí)行的位置
                this.interceptorIndex = i;
            }
            return true;
        }

        triggerAfterCompletion方法源碼如下,通過攔截器當前執(zhí)行的位置interceptorIndex逆向調用攔截器的afterCompletion方法

        void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = this.interceptorList.get(i);
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }

        2.6、⑤:調用 handler 實際處理請求,獲取 ModelAndView 對象

        2.6.1、過程

        //⑤:調用handler實際處理請求,獲取ModelAndView對象,這里會調用HandlerAdapter#handle方法處理請求,其內部會調用handler來處理具體的請求
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        ha.handler 方法內部通通過程會走到RequestMappingHandlerAdapter#invokeHandlerMethod方法,這個方法內部會通過反射調用@RequestMapping 標注的方法,這個方法內部代碼比較復雜,咱們就不進去了,這里說一下這個方法主要做了 3 個非常重要的事情:

        • step1:組裝目標方法需要的參數
        • step2:通過反射調用處理請求的目標方法,獲取方法的返回值
        • step3:對方法的返回值進行處理

        下面來細說一下這 3 個步驟,這些地方有好東西,大家集中注意力了。

        2.6.2、step1:組裝目標方法需要的參數:HandlerMethodArgumentResolver

        處理器的方法需要的參數有各種類型的,所以組裝這些參數是比較關鍵的地方,組裝參數的源碼位于下面這個位置

        org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

        獲取方法需要的參數值,會用到HandlerMethodArgumentResolver這個對象,叫做:處理器方法參數解析器,用來解析請求,得到方法需要的參數,大家看一下這個接口,源碼如下,主要有 2 個方法

        • supportsParameter:是否能夠解析 parameter 指定的參數
        • resolveArgument:通過請求和 parameter 參數解析得到參數的值
        public interface HandlerMethodArgumentResolver {

         //判斷當前解析器是否能處理這個parameter這個參數,也就是說是否能夠將請求中的數據轉換為parameter指定的參數的值
         boolean supportsParameter(MethodParameter parameter);

         //解析參數:從http請求中解析出控制器需要的參數的值
         Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
           NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
         throws Exception
        ;

        }

        這個接口有很多實現類,列幾個比較熟悉的,當大家想知道 springmvc 可以接收哪些類型的參數,以及這些參數有什么特點的時候,可以去看看這些類的源碼,你會秒懂的

        實現類對應的控制器參數說明
        PathVariableMapMethodArgumentResolver@PathVariable 標注參數從 url 中提取參數的值
        RequestHeaderMethodArgumentResolver@RequestHeader 標注參數從 http 頭中提取參數值
        RequestParamMethodArgumentResolver@RequestParam 標注參數http 請求參數中獲取值
        RequestResponseBodyMethodProcessor@RequestBody 標注參數提取 body 數據,轉換為參數類型
        ServletResponseMethodArgumentResolverServletResponse、OutputStream、Writer 這 3 種類型的參數這幾種類型用來控制 http 請求的響應輸出流
        HttpEntityMethodProcessorHttpEntityHttpEntity 類型的參數HttpEntity 中包含了 http 請求頭和 body 的所有信息
        ExpressionValueMethodArgumentResolver@Value 標注的參數spel 表達式,從 spring 容器中獲取值
        MapMethodProcessor參數為 Map 或者子類型-
        ModelMethodProcessor參數為 org.springframework.ui.Model 或子類型-
        ModelAttributeMethodProcessor@ModelAttribute 標注的參數-

        2.6.3、step2:通過反射調用目標方法

        也就是調用 controller 中的@RequestMapping 標注的方法,代碼位置

        org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

        對應的源碼如下,這個方法 springmvc 框架中主要有 2 個地方會調用

        • 第 1 個地方是:調用處理請求的實際方法的時候
        • 第 2 個地方是:方法有異常的時候,異常解析器里面也會用到這個方法,稍后后面會講
        public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                                    Object... providedArgs)
         throws Exception 
        {
            //1.通過反射調用目標方法,內部會組裝目標方法需要的參數
            Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

            //如果返回值為空,表示目標方法中已經完成了請求的所有處理,表示請求處理結束了,將執(zhí)行mavContainer.setRequestHandled(true)標記請求處理完畢
            if (returnValue == null) {
                if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                    mavContainer.setRequestHandled(true);
                    return;
                }
            }
            //若getResponseStatusReason()不為空,表示請求已經處理過了
            else if (StringUtils.hasText(getResponseStatusReason())) {
                mavContainer.setRequestHandled(true);
                return;
            }
            //走到這里,說明有返回值,標記請求未處理完畢
            mavContainer.setRequestHandled(false);
            //對返回值進行處理
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }

        2.6.4、step3:處理方法返回值:HandlerMethodReturnValueHandler

        大家注意,上面代碼中這部分代碼,如下,會對反射調用的結果 returnValue 進行處理

        //對返回值進行處理
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

        進入handleReturnValue方法內部去看一下,最終代碼在下面這個位置

        org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue

        這個方法的源碼如下

        public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                                      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
         throws Exception 
        {
            //根據返回值找到HandlerMethodReturnValueHandler
            HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
            if (handler == null) {
                throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
            }
            //調用HandlerMethodReturnValueHandler#handleReturnValue處理返回值
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }

        @Nullable
        private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
            //根據返回值判斷是否是異步請求
            boolean isAsyncValue = isAsyncReturnValue(value, returnType);
            for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
                if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                    continue;
                }
                if (handler.supportsReturnType(returnType)) {
                    return handler;
                }
            }
            return null;
        }

        這里關鍵的信息要看HandlerMethodReturnValueHandler接口,這個接口用來處理返回值,看一下其源碼,包含 2 個方法

        • supportsReturnType:是否能夠處理 returnType 參數指定的返回值
        • handleReturnValue:處理返回值
        public interface HandlerMethodReturnValueHandler {

         boolean supportsReturnType(MethodParameter returnType);

         void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
           ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
         throws Exception
        ;

        }

        此接口有很多實現類,如下圖,圖下的表格中會列出常見的一些及說明,建議大家抽空,都點開看看其源碼

        實現類說明
        ViewNameMethodReturnValueHandler返回值為視圖名稱時的解析器
        MapMethodProcessor返回值為 Map 的解析器
        StreamingResponseBodyReturnValueHandler返回值為 ResponseEntity 類型時的解析器
        DeferredResultMethodReturnValueHandler返回值為 DeferredResult 類型時的解析器,表示異步請求
        CallableMethodReturnValueHandler返回值為 Callable 類型時的解析器,表示異步請求
        ModelMethodProcessor返回值為 Model 類型時的解析器
        ModelAndViewMethodReturnValueHandler返回值為 ModelAndView 類型時的解析器
        RequestResponseBodyMethodProcessor方法上標注有@ResponseBody 注解時返回值的解析器
        HttpEntityMethodProcessor返回值為 HttpEntity 類型但是非 RequestEntity 類型時的解析器

        這里找一個比較有代表性的,帶大家看一下,就以RequestResponseBodyMethodProcessor來說一下,這個會處理@RequestBody標注的方法,抽取其 2 個關鍵方法的代碼,如下

        //判斷類上或者目標方法上是否有@ResponseBody注解
        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                    returnType.hasMethodAnnotation(ResponseBody.class))
        ;
        }

        //處理返回值
        @Override
        public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                                      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)

                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException 
        {
            //1:標注為請求已處理,因為當前handleReturnValue方法會直接將結果輸出到客戶端,所以后續(xù)就不需要再進行視圖渲染了,表示請求已經被處理了
            mavContainer.setRequestHandled(true);
            ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
            ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

            //2:將結果輸出到客戶端
            writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
        }

        上面代碼中,這里大家需要注意handleReturnValue方法,這個方法內部會直接將結果輸出,后續(xù)就沒有視圖渲染的事情了,所以這里會調用mavContainer.setRequestHandled(true),表示請求已經處理了。

        2.7、⑥:調用攔截器的 postHandle 方法

        //⑥:調用攔截器的postHandle方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        mappedHandler.applyPostHandle源碼如下,逆序調用攔截器的postHandle方法

        org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle

        void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
            throws Exception 
        {

            for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = this.interceptorList.get(i);
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }

        2.8、⑦:渲染視圖

        2.8.1、過程

         //⑦:處理分發(fā)結果,渲染視圖(包含了正常處理和異常情況的處理),將結果輸出到客戶端
         processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

        processDispatchResult源碼如下

        org.springframework.web.servlet.DispatcherServlet#processDispatchResult

        private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                           @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                           @Nullable Exception exception)
         throws Exception 
        {
            boolean errorView = false;

            if (exception != null) {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                //⑦-1:如果有異常,進行全局異常處理
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }

            if (mv != null && !mv.wasCleared()) {
                //⑦-2:渲染視圖
                render(mv, request, response);
                if (errorView) {
                    //調用request.removeAttribute方法清理request中錯誤信息
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }

            if (mappedHandler != null) {
                //⑦-3:調用攔截器的afterCompletion方法
                mappedHandler.triggerAfterCompletion(request, response, null);
            }
        }

        這個方法主要干了 3 個事情

        • step1:⑦-1:如果有異常,進行全局異常處理
        • step2:⑦-2:渲染視圖
        • step3:⑦-3:調用攔截器的 afterCompletion 方法

        下面來解析這 3 個步驟

        2.8.2、step1:⑦-1:如果有異常,進行全局異常處理

        if (exception != null) {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            //⑦-1:如果有異常,進行全局異常處理
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }

        processHandlerException方法源碼,主要是遍歷異常處理器HandlerExceptionResolverresolveException來處理異常,稍后會說一下這個接口

        org.springframework.web.servlet.DispatcherServlet#processHandlerException

        protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
           @Nullable Object handler, Exception ex)
         throws Exception 
        {

            // 調用處理器異常解析器解析異常,得到ModelAndView
            ModelAndView exMv = null;
            if (this.handlerExceptionResolvers != null) {
                for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                    exMv = resolver.resolveException(request, response, handler, ex);
                    if (exMv != null) {
                        break;
                    }
                }
            }
            if (exMv != null) {
                //暴露異常信息到request對象中(request.setAttribute)
                WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
                return exMv;
            }

            throw ex;
        }

        HandlerExceptionResolver 接口:處理器異常解析器,內部就只有一個方法,用來解析異常的,得到一個 ModelAndView 對象。

        public interface HandlerExceptionResolver {

         @Nullable
         ModelAndView resolveException(
           HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
        ;

        }

        這個接口有好幾個實現類,我們主要關注下ExceptionHandlerExceptionResolver這個類,大家是否還記得注解方式處理全局異常(即使用@ControllerAdvice 和@ExceptionHandler 實現全局異常處理處理),最終這倆注解定義的異常處理會被ExceptionHandlerExceptionResolver這個類進行處理,這個類的源碼就不細講了,比較簡單,大家可以去看看,就是一個異常類型匹配處理方法的過程。

        2.8.3、step2:⑦-2:渲染視圖

        //⑦-2:渲染視圖
        render(mv, request, response);

        render方法源碼如下

        org.springframework.web.servlet.DispatcherServlet#render

        protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception 
        {
            View view;
            String viewName = mv.getViewName();
            if (viewName != null) {
                //⑦-2-1:調用視圖解析器解析視圖名稱得到視圖View對象
                view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            } else {
                view = mv.getView();
            }

            //⑦-2-2:調用視圖的render方法渲染視圖,將結果輸出到客戶端
            view.render(mv.getModelInternal(), request, response);
        }

        此方法干了 2 件事

        • ⑦-2-1:調用視圖解析器解析視圖名稱得到視圖 View 對象
        • ⑦-2-2:調用視圖的 render 方法渲染視圖,將結果輸出到客戶端

        下面進去細看一下

        ⑦-2-1:調用視圖解析器解析視圖名稱得到視圖 View 對象
        //⑦-2-1:調用視圖解析器解析視圖名稱得到視圖View對象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

        resolveViewName方法源碼如下,遍歷視圖解析器,解析視圖名稱,得到視圖對象 View

        protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
           Locale locale, HttpServletRequest request)
         throws Exception 
        {

            if (this.viewResolvers != null) {
                for (ViewResolver viewResolver : this.viewResolvers) {
                    View view = viewResolver.resolveViewName(viewName, locale);
                    if (view != null) {
                        return view;
                    }
                }
            }
            return null;
        }
        ⑦-2-2:調用視圖的 render 方法渲染視圖,將結果輸出到客戶端
        //⑦-2-2:調用視圖的render方法渲染視圖,將結果輸出到客戶端
        view.render(mv.getModelInternal(), request, response);

        這里我們以 InternalResourceView 為例,進到其 render 方法中,看看里面干了什么,最終會進到其renderMergedOutputModel方法中,源碼如下,這里代碼就非常親切了,不多解釋,看注釋

        protected void renderMergedOutputModel(
                Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
         throws Exception 
        {

            // 將model中的數據遍歷后放在request中(request.setAttribute(name,value))
            exposeModelAsRequestAttributes(model, request);

            // 獲取跳轉的頁面的路徑
            String dispatcherPath = prepareForRendering(request, response);

            // 調用request.getRequestDispatcher(path)得到RequestDispatcher對象
            RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

            //實現頁面跳轉
            if (useInclude(request, response)) {
                rd.include(request, response);
            }else {
                rd.forward(request, response);
            }
        }

        2.8.3、step3:⑦-3:調用攔截器的 afterCompletion 方法

        ⑦-3:調用攔截器的afterCompletion方法
        mappedHandler.triggerAfterCompletion(request, response, null);

        mappedHandler.triggerAfterCompletion方法的源碼如下,反向調用攔截器的afterCompletion方法

        void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = this.interceptorList.get(i);
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }

        過程到這里就結束了,需要大家結合源碼多看幾遍,還是比較容易的。

        3、處理流程:純文字描述

        1、用戶向服務器發(fā)送請求,請求被 SpringMVC 前端控制器 DispatcherServlet 捕獲

        2、DispatcherServlet 根據該 URI,調用 HandlerMapping 獲得該 Handler 配置的所有相關的對象(包括 Handler 對象以及 Handler 對象對應的攔截器),最后以 HandlerExecutionChain 執(zhí)行鏈對象的形式返回

        4、DispatcherServlet 根據獲得的 Handler,選擇一個合適的 HandlerAdapter

        5、如果成功獲得 HandlerAdapter,此時將開始執(zhí)行攔截器的 preHandler(…)方法【正向】

        6、提取 Request 中的模型數據,填充 Handler 入參,開始執(zhí)行 Handler(Controller)方法,處理請求,在填充 Handler 的入參過程中,根據你的配置,Spring 將幫你做一些額外的工作:

        1. HttpMessageConveter:將請求消息(如 Json、xml 等數據)轉換成一個對象,將對象轉換為指定的類型信息

        2. 數據轉換:對請求消息進行數據轉換。如 String 轉換成 Integer、Double 等

        3. 數據格式化:對請求消息進行數據格式化。如將字符串轉換成格式化數字或格式化日期等

        4. 數據驗證:驗證數據的有效性(長度、格式等),驗證結果存儲到 BindingResult 或 Error 中

        7、Handler 執(zhí)行完成后,向 DispatcherServlet 返回一個 ModelAndView 對象。

        8、此時將開始執(zhí)行攔截器的 postHandle(...)方法【逆向】

        9、根據返回的 ModelAndView(此時會判斷是否存在異常:如果存在異常,則執(zhí)行 HandlerExceptionResolver 進行異常處理)選擇一個適合的 ViewResolver 進行視圖解析,根據 Model 和 View,來渲染視圖

        10、渲染視圖完畢執(zhí)行攔截器的 afterCompletion(…)方法【逆向】

        11、將渲染結果返回給客戶端

        4、小結

        本文東西比較多,建議大家抽空結合源碼多看幾遍,下一篇文章將通過源碼介紹 springmvc 容器的啟動過程,干貨也是滿滿的,敬請期待。

        5、案例代碼

        git地址:https://gitee.com/javacode2018/springmvc-series

        6、SpringMVC 系列

        1. SpringMVC 系列第 1 篇:helloword
        2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
        3. SpringMVC 系列第 3 篇:異常高效的一款接口測試利器
        4. SpringMVC 系列第 4 篇:controller 常見的接收參數的方式
        5. SpringMVC 系列第 5 篇:@RequestBody 大解密,說點你不知道的
        6. SpringMVC 系列第 6 篇:上傳文件的 4 種方式,你都會么?
        7. SpringMVC 系列第 7 篇:SpringMVC 返回視圖常見的 5 種方式,你會幾種?
        8. SpringMVC 系列第 8 篇:返回 json & 通用返回值設計
        9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
        10. SpringMVC 系列第 10 篇:異步處理
        11. SpringMVC 系列第 11 篇:集成靜態(tài)資源
        12. SpringMVC 系列第 12 篇:攔截器
        13. SpringMVC 系列第 13 篇:統一異常處理
        14. SpringMVC 系列第 14 篇:實戰(zhàn)篇:通用返回值 & 異常處理設計
        15. SpringMVC 系列第 15 篇:全注解的方式  &  原理解析

        7、更多好文章

        1. Spring 高手系列(共 56 篇)
        2. Java 高并發(fā)系列(共 34 篇)
        3. MySql 高手系列(共 27 篇)
        4. Maven 高手系列(共 10 篇)
        5. Mybatis 系列(共 12 篇)
        6. 聊聊 db 和緩存一致性常見的實現方式
        7. 接口冪等性這么重要,它是什么?怎么實現?
        8. 泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!

        8、【路人甲 Java】所有系列高清 PDF

        領取方式,掃碼發(fā)送:yyds

        瀏覽 24
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 日韩一级免费大片 | 99久久久久久 | 水多视频 | 一品道国产精品 | 男女互操软件 |