Spring MVC的請求處理邏輯

2023-03-14 15:01:38

當大家瞭解瞭如何編寫一個簡單的Spring MVC程式後,大家心中應該會有一些好奇:這背後到底發生了什麼?

Spring MVC是怎麼把這些功能串聯起來的?我們只是寫了一個控制器而已,HTTP請求是怎麼轉換為控制器方法的呼叫的?結果又是怎麼變成JSON的.....啊這小夥伴們是不是已經混亂了!?

接下來讓我們看看這背後究竟發生了什麼。

請求的處理流程

現代Java Web專案在處理HTTP請求時基本都遵循一樣的規範,即Java Servlet規範(JSR 340)。其處理流程都是Servlet容器(例如Tomcat或Jetty)收到一個請求,接著找到合適的Servlet進行處理,隨後返回響應。

在SpringMVC中,這個處理請求的Servlet就是前面提到過的DispatcherServlet
根據設定,Servlet容器會將指定的請求都交由它來處理,在收到請求後,DispatcherServlet會在Spring容器中找到合適的處理器(大部分情況下是控制器,即帶有@Controller註解的類)來處理請求,處理結果經過檢視模版後得到呈現(render)的響應內容,最後再返回給使用者,具體流程如下圖所示:

  • DispatcherServlet 接收到使用者端傳送的請求。
  • DispatcherServlet 收到請求,呼叫HandlerMapping 處理器對映器
  • HandleMapping 根據請求URL 找到對應的handler 以及處理器 攔截器,返回給DispatcherServlet
  • DispatcherServlet 根據handler 呼叫HanderAdapter 處理器介面卡。
  • HandlerAdapter 根據handler 執行處理器,也就是我們controller層寫的業務邏輯,並返回一個ModeAndView
  • HandlerAdapter 返回ModeAndView 給DispatcherServlet
  • DispatcherServlet 呼叫 ViewResolver 檢視解析器來 來解析ModeAndView
  • ViewResolve 解析ModeAndView 並返回真正的view 給DispatcherServlet
  • DispatcherServlet 將得到的檢視進行渲染,填充到request域中
  • 返回給使用者端響應結果。

1.DispatcherServlet的初始化

Servlet繼承圖

既然DispatcherServlet是一個Servlet的實現,那就會遵循其生命過程,例如會在建立後進行初始化。

HttpServletBean.init()方法呼叫了子類的方法FrameworkServlet.initServletBean(),其中做了Web應用上下文的初始化,用的就是initWebApplicationContext()。

在初始化Web應用上下文或者是上下文更新時,都會呼叫DispatcherServlet.onRefresh(),而這個方法就一句話,直接呼叫initStrategies(),就是在初始化Spring MVC的很多特殊型別的Bean

Spring MVC中的特殊Bean型別

Bean型別 說明
MultipartResolver 用來解析Multipart請求的解析器,通常是上傳檔案的請求,MultipartResolver這層抽象的背後會有多種實現,例如基於Commons FileUpload
LocaleResolver 和語言環境有關的解析器,通常用於國際化相關的場景中,包含時區、語言等多種資訊
ThemeResolver 主題(Theme)解析器,選擇應用程式的外觀介面,主題通常是一組靜態資源
HandlerMapping 用於將請求對映到處理器上,過程中還包括各種前置與後置處理,兩個主要的實現類是RequestMappingHandlerMapping和SimpleUrlHandlerMapping
Handler Adapter 用於觸發執行處理器,通過這層抽象,DispatcherServler可以不用關心具體如何執行呼叫
HandlerExceptionResolver 例外處理解析器
ViewResolver 用於將字串形式的檢視名稱轉化為具體的View,RequestToViewNameTranslator會根據請求資訊轉換對應的檢視名稱
FlashMapManager 用於存取在請求暫存的輸入與輸出資訊,通常會用在重定向時

2.請求的處理過程

DispatcherServlet在收到請求後,會交由doService()方法來進行處理,其中包含了兩個主要的步驟:

  • 第一步,向請求中設定Spring MVC相關的一些屬性
  • 第二步,呼叫doDispatch(request, response);將請求分派給具體的處理器執行實際的處理。

在這裡說一下,DispatcherServlet用到的設計模式是委派模式

委派模式:幹活算你的(普通員工),功勞算我的(一些專案經理,他們不幹活!)
例如:老闆(Boss)給專案經理(Leader)下達任務,專案經理會根據實際情況給每個員工派發任務,待員工把任務完成後,再由專案經理向老闆彙報結果

下表是doService()方法中設定到HttpServletRequest裡的幾個屬性:

屬性名 說明
WEB_APPLICATION_CONTEXT_ATTRIBUTE WebApplicationContext,Web的應用上下文
LOCALE_RESOLVER_ATTRIBUTE 處理請求時可能會需要用到的LocaleResolver,如果沒有國際化需求,可以忽略它
THEME_RESOLVER_ATTRIBUTE 用來決定使用哪個顯示主題的ThemeResolver,如果沒有這個需求,也可以忽略它
THEME_SOURCE_ATTRIBUTE 用來獲取主題的ThemeSource,預設是當前的WebApplicationContext
INPUT_FLASH_MAP_ATTRIBUTE 上個請求傳遞過來暫存到FlashMapManager裡的FlashMap
OUTPUT_FLASH_MAP_ATTRIBUTE 用來向後傳遞的FlashMap中的暫存資訊
FLASH_MAP_MANAGER_ATTRIBUTE 如果當前存在FlashMapManager,則將它設定到請求裡

doDispatch()方法的大致處理邏輯如下圖,DispatcherServlet會嘗試根據請求來找到合適的處理器,再通過HandlerAdapter來執行處理器的邏輯,經過前置處理、處理器處理和後置處理等多個步驟,最終返回一ModelAndView。

Request MappingHandlerAdapter是專門用來呼叫@RequestMapping註解標記的處理器的。在處理結果的那一步,如果有異常就處理異常,例如交給專門的HandlerExceptionResolver來處理;如果沒有異常就看ModelAndView,不為空則呈現具體的檢視,不存在也不用擔心,因為請求可能已經處理完成了。

在呼叫處理器邏輯處理的過程中,針對方法的返回值,會呼叫HandlerMethodReturnValueHandler進行處理——根據不同的情況,會有不同實現來做處理。

例如,加了@ResponseBody的方法,返回值就直接被RequestResponseBodyMethodProcessor處理掉了,選擇合適的HttpMessageConverter將物件直接序列化為相映的內容;而返回字串作為檢視名的情況,則是由ViewNameMethodReturnValueHandler來處理的。