<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          萬(wàn)字長(zhǎng)文,深度解析SpringMVC 源碼,讓你醍醐灌頂!!

          共 43044字,需瀏覽 87分鐘

           ·

          2021-09-15 12:00


          文末可以領(lǐng)取所有系列高清 pdf。

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

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

          目錄

          • 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 報(bào)文轉(zhuǎn)換器

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

            • 2.1、請(qǐng)求到達(dá)入口:doDispatch

            • 2.2、①:解析 multipart 類型的請(qǐng)求

            • 2.3、②:根據(jù)請(qǐng)求獲取 HandlerExecutionChain 對(duì)象

            • 2.4、③:根據(jù)處理器獲取 HandlerAdapter

            • 2.5、④:調(diào)用攔截器的 preHandle 方法

            • 2.6、⑤:調(diào)用 handler 實(shí)際處理請(qǐng)求,獲取 ModelAndView 對(duì)象

            • 2.7、⑥:調(diào)用攔截器的 postHandle 方法

            • 2.8、⑦:渲染視圖

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

          • 4、小結(jié)

          • 5、案例代碼

          • 6、SpringMVC 系列

          • 7、更多好文章

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


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

          1.1、DispatcherServlet:前端控制器

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

          作用:統(tǒng)一處理請(qǐng)求和響應(yīng),整個(gè)流程控制的中心,由它來(lái)調(diào)用其他組件處理用戶的請(qǐng)求。

          1.2、HandlerMapping:處理器映射器

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

          HandlerMapping 接口源碼如下,getHandler:根據(jù)請(qǐng)求查找請(qǐng)求處理器,會(huì)返回一個(gè) HandlerExecutionChain 對(duì)象。

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

          常見(jiàn)的實(shí)現(xiàn)類:

          • RequestMappingHandlerMapping:請(qǐng)求映射處理器映射,用來(lái)處理@RequestMapping 定義的處理器的

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

          HandlerMapping#getHandler 方法會(huì)根據(jù)請(qǐng)求得到一個(gè) HandlerExecutionChain 對(duì)象。

          HandlerExecutionChain 源碼如下,主要包含了 3 個(gè)信息

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

           private final Object handler;

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

           private int interceptorIndex = -1;

          }

          1.4、handler:處理器

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

          1.5、HandlerAdapter:處理器適配器

          他負(fù)責(zé)對(duì) handler 的方法進(jìn)行調(diào)用,由于 handler 的類型可能有很多種,每種 handler 的調(diào)用過(guò)程可能不一樣,此時(shí)就需要用到適配器 HandlerAdapte,適配器對(duì)外暴露了統(tǒng)一的調(diào)用方式(見(jiàn)其 handle 方法),內(nèi)部將 handler 的調(diào)用過(guò)程屏蔽了,HandlerAdapter 接口源碼如下,主要有 2 個(gè)方法需要注意:

          • supports:當(dāng)前 HandlerAdapter 是否支持 handler,其內(nèi)部主要就是判 HandlerAdapter 是否能夠處理 handler 的調(diào)用
          • handle:其內(nèi)部負(fù)責(zé)調(diào)用 handler 的來(lái)處理用戶的請(qǐng)求,返回返回一個(gè) ModelAndView 對(duì)象
          public interface HandlerAdapter {

           boolean supports(Object handler);

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

          }

          常見(jiàn)的實(shí)現(xiàn)類:

          • RequestMappingHandlerAdapter:其內(nèi)部用來(lái)調(diào)用@RequestMapping 標(biāo)注的方法

          1.6、ModelAndView:模型和視圖

          這個(gè)對(duì)象中主要用來(lái)存放視圖的名稱和共享給客戶端的數(shù)據(jù)。

          public class ModelAndView {

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

           /*模型,用來(lái)存放共享給客戶端的數(shù)據(jù)*/
           @Nullable
           private ModelMap model;

          }

          1.7、ViewResolver:視圖解析器

          這個(gè)是框架提供的,不需要咱們自己開(kāi)發(fā),它負(fù)責(zé)視圖解析,根據(jù)視圖的名稱得到對(duì)應(yīng)的視圖對(duì)象(View)。

          ViewResolver 接口源碼

          public interface ViewResolver {

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

          }

          這個(gè)接口有很多實(shí)現(xiàn)類,比如 jsp 的、freemarker、thymeleaf 的等,他們都有各自對(duì)應(yīng)的 ViewResolver。

          而比較常的實(shí)現(xiàn)類是InternalResourceViewResolver,這個(gè)大家應(yīng)該比較熟悉吧,目前為止我們前面的文章用到的都是這個(gè)視圖解析器,用來(lái)處理 jsp 格式的視圖頁(yè)面,帶大家再回顧一下這個(gè)類的配置,如下

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

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

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

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

          step3:判斷當(dāng)前項(xiàng)目是否存在 jstl 所需的類,如果是,則返回 JstlView 類型的視圖,否則返回 InternalResourceView 類型的視圖,這兩個(gè)視圖的 render 方法最終會(huì)通過(guò)request.getRequestDispatcher(path).forward(request, response)進(jìn)行頁(yè)面的跳轉(zhuǎn),跳轉(zhuǎn)的路徑是:InternalResourceViewResolver 的前綴 prefix + viewName+InternalResourceViewResolver 的后綴 prefix

          1.8、View:視圖

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

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

          View 接口常見(jiàn)的 2 個(gè)實(shí)現(xiàn)類

          • RedirectView:負(fù)責(zé)重定向的,內(nèi)部通過(guò)response.sendRedirect(url)進(jìn)行頁(yè)面重定向
          • InternalResourceViewResolver:負(fù)責(zé)頁(yè)面跳轉(zhuǎn)的,內(nèi)部通過(guò)request.getRequestDispatcher(path).forward(request, response)進(jìn)行頁(yè)面的跳轉(zhuǎn)

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

          負(fù)責(zé)處理異常的,HandlerExceptionResolver 接口有個(gè)resolveException方法,用來(lái)解析異常,返回異常情況下對(duì)應(yīng)的 ModelAndView 對(duì)象

          public interface HandlerExceptionResolver {

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

          }

          1.10、HttpMessageConverter:http 報(bào)文轉(zhuǎn)換器

          將請(qǐng)求報(bào)文轉(zhuǎn)換為 Java 對(duì)象,或?qū)?Java 對(duì)象轉(zhuǎn)換為響應(yīng)報(bào)文,在處理@RequestBody、RequestEntity、@ResponseBody、ResponseEntity 的時(shí)候會(huì)用到

          public interface HttpMessageConverter<T{

           /**
            * 是否可以將請(qǐng)求保溫讀取給方法參數(shù)指定的類型
            */

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

           /**
            * 是否可以將響應(yīng)的保溫轉(zhuǎn)換為方法參數(shù)指定的類型輸出
            */

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

           /**
            * 當(dāng)前轉(zhuǎn)換器支持的類型
            */

           List<MediaType> getSupportedMediaTypes();

           /**
            * 當(dāng)前轉(zhuǎn)換器支持的類型
            */

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

           /**
            * 將http保溫轉(zhuǎn)換為給定的類型,然后返回
            */

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

           /**
            * 將給定的對(duì)象t,轉(zhuǎn)換為http報(bào)文輸出到客戶端
            */

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

          }

          2、處理流程:源碼解析

          2.1、請(qǐng)求到達(dá)入口:doDispatch

          springmvc 的所有請(qǐng)求,最終都會(huì)到達(dá)org.springframework.web.servlet.DispatcherServlet#doDispatch這個(gè)方法,整個(gè)請(qǐng)求的大致處理過(guò)程都在這個(gè)方法中,咱們從這個(gè)方法開(kāi)始分析,源碼如下,大家注意代碼中的注釋,帶有標(biāo)號(hào),比如 ①、②、③ 這樣需要的注釋,大家需要注意了,這些是關(guān)鍵的步驟,稍后會(huì)對(duì)這些步驟做詳細(xì)的說(shuō)明

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

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

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

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

                      //②:根據(jù)請(qǐng)求獲取HandlerExecutionChain對(duì)象
                      mappedHandler = getHandler(processedRequest);
                      //如果沒(méi)有找到處理器,就404了
                      if (mappedHandler == null) {
                          noHandlerFound(processedRequest, response);
                          return;
                      }

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

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

                      //⑤:調(diào)用handler實(shí)際處理請(qǐng)求,獲取ModelAndView對(duì)象,這里會(huì)調(diào)用HandlerAdapter#handle方法處理請(qǐng)求,其內(nèi)部會(huì)調(diào)用handler來(lái)處理具體的請(qǐng)求
                      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                      //判斷異步請(qǐng)求不是已經(jīng)開(kāi)始了,開(kāi)始了就返回了
                      if (asyncManager.isConcurrentHandlingStarted()) {
                          return;
                      }
                      //如果mv對(duì)象中沒(méi)有視圖 & DispatcherServlet配置了默認(rèn)的視圖,則給mv安排一個(gè)默認(rèn)的視圖
                      applyDefaultViewName(processedRequest, mv);

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

          下面我們來(lái)對(duì)上面帶有編號(hào)的步驟進(jìn)行分析。

          2.2、①:解析 multipart 類型的請(qǐng)求

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

          checkMultipart(request)源碼

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

          2.3、②:根據(jù)請(qǐng)求獲取 HandlerExecutionChain 對(duì)象

          //②:根據(jù)請(qǐng)求獲取HandlerExecutionChain對(duì)象
          mappedHandler = getHandler(processedRequest);

          getHandler(processedRequest)源碼如下,遍歷所有的處理器映射器HandlerMapping,調(diào)用他們的getHandler方法得到能夠處理當(dāng)前請(qǐng)求的HandlerExecutionChain對(duì)象,這個(gè)對(duì)象中包含了 3 個(gè)信息

          • handler:請(qǐng)求處理器,通常就是我們自定義的 controller 對(duì)象及方法
          • interceptorList:攔截器,當(dāng)前請(qǐng)求匹配到的攔截器列表
          • interceptorIndex:攔截器索引,用來(lái)記錄執(zhí)行到第幾個(gè)攔截器了
          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這個(gè)類的源碼,也是最常用的一個(gè) HandlerMapping,它會(huì)根據(jù)@RequestMapping來(lái)找到能夠處當(dāng)前請(qǐng)求的處理器,RequestMappingHandlerMapping#getHandler 方法查找得到的 HandlerExecutionChain 對(duì)象中的 handler 類型為HandlerMethod,代碼在下面這個(gè)位置

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

          HandlerMethod 對(duì)象中包含了能夠處理請(qǐng)求的 bean 及方法信息

          2.4、③:根據(jù)處理器獲取 HandlerAdapter

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

          getHandlerAdapter方法源碼,遍歷HandlerAdapter列表,找到能夠處理當(dāng)前 handler 的HandlerAdapter,如果沒(méi)找到會(huì)報(bào)錯(cuò)

          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類型的對(duì)象,RequestMappingHandlerAdapter這個(gè)類會(huì)根據(jù)HandlerMethod提供的信息,通過(guò)反射調(diào)用@RequestMapping 標(biāo)注的方法。

          2.5、④:調(diào)用攔截器的 preHandle 方法

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

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

          • 循環(huán)調(diào)用攔截器的preHandle方法
          • 如果某個(gè)攔截器的preHandle方法返回 false,則反向依次調(diào)用那些 preHandle 方法返回 ture 的攔截器的 afterCompletion 方法;這句話有點(diǎn)繞,比如有 3 個(gè)攔截器,1、2 的 preHandler 返回了 true,而 3 返回的是 false,那么這里將按照 2、1 的順序調(diào)用他們的 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);
                  //調(diào)用攔截器的preHandle方法
                  if (!interceptor.preHandle(request, response, this.handler)) {
                      //如果攔截器返回false,則反向依次調(diào)用那些preHandle方法返回ture的攔截器的afterCompletion方法
                      triggerAfterCompletion(request, response, null);
                      return false;
                  }
                  //記錄當(dāng)前攔截器執(zhí)行的位置
                  this.interceptorIndex = i;
              }
              return true;
          }

          triggerAfterCompletion方法源碼如下,通過(guò)攔截器當(dāng)前執(zhí)行的位置interceptorIndex逆向調(diào)用攔截器的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、⑤:調(diào)用 handler 實(shí)際處理請(qǐng)求,獲取 ModelAndView 對(duì)象

          2.6.1、過(guò)程

          //⑤:調(diào)用handler實(shí)際處理請(qǐng)求,獲取ModelAndView對(duì)象,這里會(huì)調(diào)用HandlerAdapter#handle方法處理請(qǐng)求,其內(nèi)部會(huì)調(diào)用handler來(lái)處理具體的請(qǐng)求
          mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          ha.handler 方法內(nèi)部通通過(guò)程會(huì)走到RequestMappingHandlerAdapter#invokeHandlerMethod方法,這個(gè)方法內(nèi)部會(huì)通過(guò)反射調(diào)用@RequestMapping 標(biāo)注的方法,這個(gè)方法內(nèi)部代碼比較復(fù)雜,咱們就不進(jìn)去了,這里說(shuō)一下這個(gè)方法主要做了 3 個(gè)非常重要的事情:

          • step1:組裝目標(biāo)方法需要的參數(shù)
          • step2:通過(guò)反射調(diào)用處理請(qǐng)求的目標(biāo)方法,獲取方法的返回值
          • step3:對(duì)方法的返回值進(jìn)行處理

          下面來(lái)細(xì)說(shuō)一下這 3 個(gè)步驟,這些地方有好東西,大家集中注意力了。

          2.6.2、step1:組裝目標(biāo)方法需要的參數(shù):HandlerMethodArgumentResolver

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

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

          獲取方法需要的參數(shù)值,會(huì)用到HandlerMethodArgumentResolver這個(gè)對(duì)象,叫做:處理器方法參數(shù)解析器,用來(lái)解析請(qǐng)求,得到方法需要的參數(shù),大家看一下這個(gè)接口,源碼如下,主要有 2 個(gè)方法

          • supportsParameter:是否能夠解析 parameter 指定的參數(shù)
          • resolveArgument:通過(guò)請(qǐng)求和 parameter 參數(shù)解析得到參數(shù)的值
          public interface HandlerMethodArgumentResolver {

           //判斷當(dāng)前解析器是否能處理這個(gè)parameter這個(gè)參數(shù),也就是說(shuō)是否能夠?qū)⒄?qǐng)求中的數(shù)據(jù)轉(zhuǎn)換為parameter指定的參數(shù)的值
           boolean supportsParameter(MethodParameter parameter);

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

          }

          這個(gè)接口有很多實(shí)現(xiàn)類,列幾個(gè)比較熟悉的,當(dāng)大家想知道 springmvc 可以接收哪些類型的參數(shù),以及這些參數(shù)有什么特點(diǎn)的時(shí)候,可以去看看這些類的源碼,你會(huì)秒懂的

          實(shí)現(xiàn)類對(duì)應(yīng)的控制器參數(shù)說(shuō)明
          PathVariableMapMethodArgumentResolver@PathVariable 標(biāo)注參數(shù)從 url 中提取參數(shù)的值
          RequestHeaderMethodArgumentResolver@RequestHeader 標(biāo)注參數(shù)從 http 頭中提取參數(shù)值
          RequestParamMethodArgumentResolver@RequestParam 標(biāo)注參數(shù)http 請(qǐng)求參數(shù)中獲取值
          RequestResponseBodyMethodProcessor@RequestBody 標(biāo)注參數(shù)提取 body 數(shù)據(jù),轉(zhuǎn)換為參數(shù)類型
          ServletResponseMethodArgumentResolverServletResponse、OutputStream、Writer 這 3 種類型的參數(shù)這幾種類型用來(lái)控制 http 請(qǐng)求的響應(yīng)輸出流
          HttpEntityMethodProcessorHttpEntityHttpEntity 類型的參數(shù)HttpEntity 中包含了 http 請(qǐng)求頭和 body 的所有信息
          ExpressionValueMethodArgumentResolver@Value 標(biāo)注的參數(shù)spel 表達(dá)式,從 spring 容器中獲取值
          MapMethodProcessor參數(shù)為 Map 或者子類型-
          ModelMethodProcessor參數(shù)為 org.springframework.ui.Model 或子類型-
          ModelAttributeMethodProcessor@ModelAttribute 標(biāo)注的參數(shù)-

          2.6.3、step2:通過(guò)反射調(diào)用目標(biāo)方法

          也就是調(diào)用 controller 中的@RequestMapping 標(biāo)注的方法,代碼位置

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

          對(duì)應(yīng)的源碼如下,這個(gè)方法 springmvc 框架中主要有 2 個(gè)地方會(huì)調(diào)用

          • 第 1 個(gè)地方是:調(diào)用處理請(qǐng)求的實(shí)際方法的時(shí)候
          • 第 2 個(gè)地方是:方法有異常的時(shí)候,異常解析器里面也會(huì)用到這個(gè)方法,稍后后面會(huì)講
          public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                                      Object... providedArgs)
           throws Exception 
          {
              //1.通過(guò)反射調(diào)用目標(biāo)方法,內(nèi)部會(huì)組裝目標(biāo)方法需要的參數(shù)
              Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

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

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

          大家注意,上面代碼中這部分代碼,如下,會(huì)對(duì)反射調(diào)用的結(jié)果 returnValue 進(jìn)行處理

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

          進(jìn)入handleReturnValue方法內(nèi)部去看一下,最終代碼在下面這個(gè)位置

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

          這個(gè)方法的源碼如下

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

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

          這里關(guān)鍵的信息要看HandlerMethodReturnValueHandler接口,這個(gè)接口用來(lái)處理返回值,看一下其源碼,包含 2 個(gè)方法

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

           boolean supportsReturnType(MethodParameter returnType);

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

          }

          此接口有很多實(shí)現(xiàn)類,如下圖,圖下的表格中會(huì)列出常見(jiàn)的一些及說(shuō)明,建議大家抽空,都點(diǎn)開(kāi)看看其源碼

          實(shí)現(xiàn)類說(shuō)明
          ViewNameMethodReturnValueHandler返回值為視圖名稱時(shí)的解析器
          MapMethodProcessor返回值為 Map 的解析器
          StreamingResponseBodyReturnValueHandler返回值為 ResponseEntity 類型時(shí)的解析器
          DeferredResultMethodReturnValueHandler返回值為 DeferredResult 類型時(shí)的解析器,表示異步請(qǐng)求
          CallableMethodReturnValueHandler返回值為 Callable 類型時(shí)的解析器,表示異步請(qǐng)求
          ModelMethodProcessor返回值為 Model 類型時(shí)的解析器
          ModelAndViewMethodReturnValueHandler返回值為 ModelAndView 類型時(shí)的解析器
          RequestResponseBodyMethodProcessor方法上標(biāo)注有@ResponseBody 注解時(shí)返回值的解析器
          HttpEntityMethodProcessor返回值為 HttpEntity 類型但是非 RequestEntity 類型時(shí)的解析器

          這里找一個(gè)比較有代表性的,帶大家看一下,就以RequestResponseBodyMethodProcessor來(lái)說(shuō)一下,這個(gè)會(huì)處理@RequestBody標(biāo)注的方法,抽取其 2 個(gè)關(guān)鍵方法的代碼,如下

          //判斷類上或者目標(biāo)方法上是否有@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:標(biāo)注為請(qǐng)求已處理,因?yàn)楫?dāng)前handleReturnValue方法會(huì)直接將結(jié)果輸出到客戶端,所以后續(xù)就不需要再進(jìn)行視圖渲染了,表示請(qǐng)求已經(jīng)被處理了
              mavContainer.setRequestHandled(true);
              ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
              ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

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

          上面代碼中,這里大家需要注意handleReturnValue方法,這個(gè)方法內(nèi)部會(huì)直接將結(jié)果輸出,后續(xù)就沒(méi)有視圖渲染的事情了,所以這里會(huì)調(diào)用mavContainer.setRequestHandled(true),表示請(qǐng)求已經(jīng)處理了。

          2.7、⑥:調(diào)用攔截器的 postHandle 方法

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

          mappedHandler.applyPostHandle源碼如下,逆序調(diào)用攔截器的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、過(guò)程

           //⑦:處理分發(fā)結(jié)果,渲染視圖(包含了正常處理和異常情況的處理),將結(jié)果輸出到客戶端
           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:如果有異常,進(jìn)行全局異常處理
                  mv = processHandlerException(request, response, handler, exception);
                  errorView = (mv != null);
              }

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

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

          這個(gè)方法主要干了 3 個(gè)事情

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

          下面來(lái)解析這 3 個(gè)步驟

          2.8.2、step1:⑦-1:如果有異常,進(jìn)行全局異常處理

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

          processHandlerException方法源碼,主要是遍歷異常處理器HandlerExceptionResolverresolveException來(lái)處理異常,稍后會(huì)說(shuō)一下這個(gè)接口

          org.springframework.web.servlet.DispatcherServlet#processHandlerException

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

              // 調(diào)用處理器異常解析器解析異常,得到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對(duì)象中(request.setAttribute)
                  WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
                  return exMv;
              }

              throw ex;
          }

          HandlerExceptionResolver 接口:處理器異常解析器,內(nèi)部就只有一個(gè)方法,用來(lái)解析異常的,得到一個(gè) ModelAndView 對(duì)象。

          public interface HandlerExceptionResolver {

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

          }

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

          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:調(diào)用視圖解析器解析視圖名稱得到視圖View對(duì)象
                  view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
              } else {
                  view = mv.getView();
              }

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

          此方法干了 2 件事

          • ⑦-2-1:調(diào)用視圖解析器解析視圖名稱得到視圖 View 對(duì)象
          • ⑦-2-2:調(diào)用視圖的 render 方法渲染視圖,將結(jié)果輸出到客戶端

          下面進(jìn)去細(xì)看一下

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

          resolveViewName方法源碼如下,遍歷視圖解析器,解析視圖名稱,得到視圖對(duì)象 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:調(diào)用視圖的 render 方法渲染視圖,將結(jié)果輸出到客戶端
          //⑦-2-2:調(diào)用視圖的render方法渲染視圖,將結(jié)果輸出到客戶端
          view.render(mv.getModelInternal(), request, response);

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

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

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

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

              // 調(diào)用request.getRequestDispatcher(path)得到RequestDispatcher對(duì)象
              RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

              //實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)
              if (useInclude(request, response)) {
                  rd.include(request, response);
              }else {
                  rd.forward(request, response);
              }
          }

          2.8.3、step3:⑦-3:調(diào)用攔截器的 afterCompletion 方法

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

          mappedHandler.triggerAfterCompletion方法的源碼如下,反向調(diào)用攔截器的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);
                  }
              }
          }

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

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

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

          2、DispatcherServlet 根據(jù)該 URI,調(diào)用 HandlerMapping 獲得該 Handler 配置的所有相關(guān)的對(duì)象(包括 Handler 對(duì)象以及 Handler 對(duì)象對(duì)應(yīng)的攔截器),最后以 HandlerExecutionChain 執(zhí)行鏈對(duì)象的形式返回

          4、DispatcherServlet 根據(jù)獲得的 Handler,選擇一個(gè)合適的 HandlerAdapter

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

          6、提取 Request 中的模型數(shù)據(jù),填充 Handler 入?yún)ⅲ_(kāi)始執(zhí)行 Handler(Controller)方法,處理請(qǐng)求,在填充 Handler 的入?yún)⑦^(guò)程中,根據(jù)你的配置,Spring 將幫你做一些額外的工作:

          1. HttpMessageConveter:將請(qǐng)求消息(如 Json、xml 等數(shù)據(jù))轉(zhuǎn)換成一個(gè)對(duì)象,將對(duì)象轉(zhuǎn)換為指定的類型信息

          2. 數(shù)據(jù)轉(zhuǎn)換:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)轉(zhuǎn)換。如 String 轉(zhuǎn)換成 Integer、Double 等

          3. 數(shù)據(jù)格式化:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)格式化。如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等

          4. 數(shù)據(jù)驗(yàn)證:驗(yàn)證數(shù)據(jù)的有效性(長(zhǎng)度、格式等),驗(yàn)證結(jié)果存儲(chǔ)到 BindingResult 或 Error 中

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

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

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

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

          11、將渲染結(jié)果返回給客戶端

          4、小結(jié)

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

          5、案例代碼

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

          6、SpringMVC 系列

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

          7、更多好文章

          1. Spring 高手系列(共 56 篇)
          2. Java 高并發(fā)系列(共 34 篇)
          3. MySql 高手系列(共 27 篇)
          4. Maven 高手系列(共 10 篇)
          5. Mybatis 系列(共 12 篇)
          6. 聊聊 db 和緩存一致性常見(jiàn)的實(shí)現(xiàn)方式
          7. 接口冪等性這么重要,它是什么?怎么實(shí)現(xiàn)?
          8. 泛型,有點(diǎn)難度,會(huì)讓很多人懵逼,那是因?yàn)槟銢](méi)有看這篇文章!

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

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

          瀏覽 24
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  特黄特色免费大片 | 国产麻豆精品免费 | 成人影音先锋AV免费电影 | 日本综合久久 | 伊人久大香蕉 |