1. 程式人生 > >Spring MVC 處理一個請求的流程分析

Spring MVC 處理一個請求的流程分析

Spring MVC是Spring系列框架中使用頻率最高的部分。不管是Spring Boot還是傳統的Spring專案,只要是Web專案都會使用到Spring MVC部分。因此程式設計師一定要熟練掌握MVC部分。本篇部落格簡要分析Spring MVC處理一個請求的流程。 一個請求從客戶端發出到達伺服器,然後被處理的整個過程其實是非常複雜的。本部落格主要介紹請求到達伺服器被核心元件`DispatcherServlet`處理的整理流程(不包括Filter的處理流程)。 ## 1. 處理流程分析 Servlet處理一個請求時會呼叫service()方法,所以DispatcherServlet處理請求的方式也是從service()方法開始(debug的話建議從DispatcherServlet的service方法開始debug)。FrameworkServlet重寫了HttpServlet的service方法,這個service方法後面又呼叫了FrameworkServlet的processRequest()方法,processRequest()呼叫了DispatcherServlet的doService()方法,最後呼叫到DispatcherServlet的doDispatcher()方法。整合處理請求的方法呼叫流程如上,下面看下程式碼: ```java protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (HttpMethod.PATCH == httpMethod || httpMethod == null) { processRequest(request, response); } else { //這邊呼叫了HttpServlet的service()方法,但由於FrameWorkServle重寫了doGet、doPost等方法,所以最終還是會呼叫到processRequest方法 super.service(request, response); } } ``` 再看看FrameworkServlet的processRequest()方法。 ```java protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { //這邊呼叫DispatcherServlet的doService()方法 doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } publishRequestHandledEvent(request, response, startTime, failureCause); } } ``` doService()方法的具體內容會在後面講到,這邊描述下doDispatcher()的內容,參考了[部落格](http://www.cnblogs.com/fangjian0423/p/springMVC-dispatcherServlet.html): > 首先根據請求的路徑找到HandlerMethod(帶有Method反射屬性,也就是對應Controller中的方法),然後匹配路徑對應的攔截器,有了HandlerMethod和攔截器構造個HandlerExecutionChain物件。HandlerExecutionChain物件的獲取是通過HandlerMapping介面提供的方法中得到。有了HandlerExecutionChain之後,通過HandlerAdapter物件進行處理得到ModelAndView物件,HandlerMethod內部handle的時候,使用各種HandlerMethodArgumentResolver實現類處理HandlerMethod的引數,使用各種HandlerMethodReturnValueHandler實現類處理返回值。 最終返回值被處理成ModelAndView物件,這期間發生的異常會被HandlerExceptionResolver介面實現類進行處理。 總結下Spring MVC處理一個請求的過程: - 首先,搜尋應用的上下文物件 WebApplicationContext 並把它作為一個屬性(attribute)繫結到該請求上,以便控制器和其他元件能夠使用它。 - 將地區(locale)解析器繫結到請求上,以便其他元件在處理請求(渲染檢視、準備資料等)時可以獲取區域相關的資訊。如果你的應用不需要解析區域相關的資訊; - 將主題(theme)解析器繫結到請求上,以便其他元件(比如檢視等)能夠了解要渲染哪個主題檔案。同樣,如果你不需要使用主題相關的特性,忽略它即可如果你配置了multipart檔案處理器,那麼框架將查詢該檔案是不是multipart(分為多個部分連續上傳)的。若是,則將該請求包裝成一個 MultipartHttpServletRequest 物件,以便處理鏈中的其他元件對它做進一步的處理。關於Spring對multipart檔案傳輸處理的支援; - 為該請求查詢一個合適的處理器。如果可以找到對應的處理器,則與該處理器關聯的整條執行鏈(前處理器、後處理器、控制器等)都會被執行,以完成相應模型的準備或檢視的渲染如果處理器返回的是一個模型(model),那麼框架將渲染相應的檢視。若沒有返回任何模型(可能是因為前後的處理器出於某些原因攔截了請求等,比如,安全問題),則框架不會渲染任何檢視,此時認為對請求的處理可能已經由處理鏈完成了(這個過程就是doService()和doDispatcher()做的事情) 1、 首先使用者傳送請求——>DispatcherServlet,前端控制器收到請求後自己不進行處理,而是委託給其他的解析器進行處理,作為統一訪問點,進行全域性的流程控制; 2、 DispatcherServlet——>HandlerMapping,HandlerMapping將會把請求對映為HandlerExecutionChain物件(包含一個Handler處理器(頁面控制器)物件、多個HandlerInterceptor攔截器)物件,通過這種策略模式,很容易新增新的對映策略; 3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝為介面卡,從而支援多種型別的處理器,即介面卡設計模式的應用,從而很容易支援很多型別的處理器; 4、 HandlerAdapter——>處理器功能處理方法的呼叫,HandlerAdapter將會根據適配的結果呼叫真正的處理器的功能處理方法,完成功能處理;並返回一個ModelAndView物件(包含模型資料、邏輯檢視名); 5、 ModelAndView的邏輯檢視名——> ViewResolver,ViewResolver將把邏輯檢視名解析為具體的View,通過這種策略模式,很容易更換其他檢視技術; 6、 View——>渲染,View會根據傳進來的Model模型資料進行渲染,此處的Model實際是一個Map資料結構,因此很容易支援其他檢視技術; 7、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給使用者,到此一個流程結束。 ## 2. 請求流程圖 ![](https://img2020.cnblogs.com/blog/1775037/202003/1775037-20200319165758568-1682799887.png) 還是這個圖比較清楚。發現根據程式碼不太能把這個流程說清楚。而且整個流程很長,程式碼很多,我就不貼程式碼了。這裡根據這個圖再把整個流程中元件的功能總結下: - `DispatcherServlet`:核心控制器,所有請求都會先進入`DispatcherServlet`進行統一分發,是不是感覺有點像外觀模式的感覺; - `HandlerMapping`:這個元件的作用就是將使用者請求的URL對映成一個`HandlerExecutionChain`。這個`HandlerExecutionChain`是`HandlerMethod`和`HandlerInterceptor`的組合。Spring在啟動的時候會預設注入很多`HandlerMapping`元件,其中最常用的元件就是`RequestMappingHandlerMapping`。 上面的`HandlerMethod`和`HandlerInterceptor`元件分別對應我們Controller中的方法和攔截器。**攔截器會在HandlerMethod方法執行之前執行** - `HandlerAdapter`元件,這個元件的主要作用是用來對`HandlerMethod`中引數的轉換,對方法的執行,以及對返回值的轉換等等。這裡面涉及的細節就很多了,包括`HandlerMethodArgumentResolver`、`HandlerMethodReturnValueHandler` 、`RequestResponseBodyMethodProcessor` 、和`HttpMessageConvert`等元件。 當`HandlerAdapter`元件執行完成之後會得到一個`ModleAndView`元件,這個元件代表檢視模型。 - 得到`ModleAndView`後會執行攔截器的`postHandle`方法。 - 如果在上面的執行過程中發生任何異常,會由HandlerExceptionResolver進行統一處理。 - 最後模型解析器會對上面的到的`ModleAndView`進行解析,得到一個一個View返回給客戶端。在返回客戶端之前還會執行攔截器的`afterCompletion`方法