首頁 > 科技

阿里首發!架構師必知的Spring MVC架構詳解,確定不來了解一下?

2021-06-08 18:48:19

一. RequestMappingHandlerAdapter初始化

首先通過類圖認識一下RequestMappingHandlerAdapter。

由類圖可知RequestMappingHandlerAdapter實現了InitializingBean介面,因此在容器載入RequestMappingHandlerAdapter時會執行其實現的afterPropertiesSet()方法。該方法如下所示。

afterPropertiesSet()方法依次為RequestMappingHandlerAdapter載入ControllerAdviceBean相關內容,載入參數解析器,載入返回結果處理器。

其中參數解析器和返回結果處理器預設情況下會將SpringMVC框架提供的和使用者自定義的一併進行載入,而載入ControllerAdviceBean相關內容實際就是先獲取容器中所有由@ControllerAdvice註解修飾的bean,然後將這些bean的由@ModelAttribute註解和@InitBinder註解修飾的方法載入到RequestMappingHandlerAdapter中,最後再判斷由@ControllerAdvice註解修飾的bean是否實現了RequestBodyAdvice或ResponseBodyAdvice介面,如果是,則將該bean也載入到RequestMappingHandlerAdapter中。initControllerAdviceCache()方法如下所示。

由@ControllerAdvice註解修飾的bean是功能增強型Controller,通常搭配@ModelAttribute註解和@InitBinder註解使用,可以實現:全局資料繫結;全局資料預處理;全局異常處理。

小節:RequestMappingHandlerAdapter初始化時會將解析參數,參數預處理,執行結果處理等步驟需要用到的bean進行載入。

二. RequestMappingHandlerAdapter參數解析

先通過DispatcherServlet中的doDispatch()方法觀察handler執行的入口。

handler的執行發生在HandlerAdapter介面聲明的handle()方法中,由前面類圖可知,RequestMappingHandlerAdapter繼承於抽象類AbstractHandlerMethodAdapter,該抽象類實現了HandlerAdapter介面,並在其實現的handle()方法中呼叫了其聲明的抽象方法handleInternal(),因此handler的執行入口是RequestMappingHandlerAdapter的handleInternal()方法,該方法如下所示。

繼續看invokeHandlerMethod()的實現。

invokeHandlerMethod()方法有點長,所做的事情可以概括為首先為執行handler準備各種必要條件(參數解析器,返回值處理器等),然後執行handler,最後更新模型Model並生成ModelAndView物件。這裡特別說明一下上面源碼中的ModelAndViewContainer物件,該物件的源碼的註釋翻譯如下。

記錄與模型Model和檢視View相關的由HandlerMethodArgumentResolvers和HandlerMethodReturnValueHandlers在handler方法執行中做出的相關決策。

要理解上面的話,先看一下ModelAndViewContainer的成員變數。

即ModelAndViewContainer主要功能如下。

維護模型Model:非重定向場景下使用defaultModel,重定向場景下且ignoreDefaultModelOnRedirect為true時使用redirectModel;維護檢視View:如果是字元串類型則是邏輯檢視;維護是否是重定向場景:通過redirectModelScenario屬性欄位進行標識;維護handler是否執行完:通過requestHandled屬性欄位進行標識。因為在後面參數解析和返回結果處理的過程中會將ModelAndViewContainer物件傳遞來傳遞去,其實這個物件做的事情大部分是儲存資料和記錄狀態以方便最後獲取ModelAndView物件,所以在這裡先對其進行簡要說明。

回到invokeHandlerMethod()方法,handler的實際執行是發生在ServletInvocableHandlerMethod物件的invokeAndHandle()方法中,繼續看這個方法的實現。

invokeAndHandle()方法中,呼叫了ServletInvocableHandlerMethod的父類InvocableHandlerMethod的invokeForRequest()來完成參數解析和執行handler。其實現如下。

到這裡,終於看到了參數解析和執行handler的方法。本小節主要學習參數解析過程,本小節正文開始。

getMethodArgumentValues()方法是InvocableHandlerMethod提供的方法,該方法完成兩個事情:

獲取需要執行的handler方法的參數資訊,包括:參數名,參數類型,參數註解(如果有的話)等,得到的是一個MethodParameter[]陣列,每一個handler方法的參數資訊都會封裝成一個MethodParameter物件並加入到MethodParameter[]陣列中;將handler方法的參數資訊與request送入參數解析器,通過參數解析器將需要的參數從request中獲取出來。getMethodArgumentValues()的實現如下所示。

InvocableHandlerMethod的resolvers成員變數是一個HandlerMethodArgumentResolverComposite物件,該物件和其它參數解析器一樣實現了HandlerMethodArgumentResolver介面,下面看一下其實現的resolveArgument()方法。

在上面resolveArgument()方法中呼叫了參數解析器的resolveArgument()方法,而不同參數解析器的resolveArgument()方法實現存在一定差別。

在日常的web開發中,使用@RequestParam,@PathVariable,@RequestBody註解較多,不同註解修飾的參數對應的參數解析器不盡相同,首先看一下這三種註解對應的參數解析器的類圖。

由類圖可以知道,@RequestParam註解和@PathVariable註解對應的參數解析器繼承於AbstractNamedValueMethodArgumentResolver抽象類,該抽象類實現了HandlerMethodArgumentResolver介面,因此這兩個註解對應的參數解析器呼叫resolveArgument()方法實際是呼叫其父類實現的resolveArgument()方法。而@RequestBody註解對應的參數解析器RequestResponseBodyMethodProcessor對其父類實現的resolveArgument()方法進行了重寫,故該註解對應的參數解析器呼叫resolveArgument()方法時實際是呼叫其本身重寫的方法。

先看一下AbstractNamedValueMethodArgumentResolver對resolveArgument()方法的實現。

上面源碼中的resolveName()方法是AbstractNamedValueMethodArgumentResolver聲明的一個抽象方法,@RequestParam註解和@PathVariable註解對應的參數解析器均對其進行了實現,具體的實現邏輯比較簡單,這裡不再贅述,總之該方法可以從request中獲取指定名稱的參數。

參數獲取到之後,還會通過WebDataBinderFactory創建WebDataBinder物件,在創建WebDataBinder物件的同時還會呼叫所有由@InitBinder註解修飾的方法來初始化WebDataBinder物件,最後使用WebDataBinder來對剛剛從request中獲取到的參數進行類型轉換和繫結(即先將arg參數按照我們的預期進行類型轉換,然後再將arg指向轉換後的值)。

關於WebDataBinder如何完成類型轉換和繫結,將會在後面的一篇文章中專門進行學習,這裡不再進行深入。

下面再看一下RequestResponseBodyMethodProcessor對resolveArgument()方法的實現。

readWithMessageConverters()方法實際就是呼叫SpringMVC框架提供的(或使用者實現的)訊息轉換器來完成請求體到參數物件的繫結,並且如果使用了@Valid或@Validated註解來進行參數校驗,則會在readWithMessageConverters()方法獲取到參數物件後創建WebDataBinder來完成參數校驗,如果校驗不通過則會拋出MethodArgumentNotValidException全局異常,通過處理該異常可以對參數校驗不過的情況進行統一處理。下面再看一下readWithMessageConverters()的實現。

在RequestResponseBodyMethodProcessor的readWithMessageConverters()方法中呼叫了其父類AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters()方法,其實現如下所示。

在AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters()方法中,會迴圈遍歷所有訊息轉換器並判斷其是否可以處理當前請求,同一個請求只會有一個訊息轉換器可以處理。

訊息轉換器讀取請求體前,會先獲取所有適配當前handler的RequestBodyAdvice介面,然後迴圈遍歷這些介面並執行其beforeBodyRead()方法,以完成一些定製處理,同理在訊息轉換器完成轉換後還會迴圈遍歷這些介面並執行其afterBodyRead()方法,這裡使用了Spring的AOP:如果是由@ControllerAdvice註解修飾的RequestBodyAdvice介面(切面),那麼會根據@ControllerAdvice註解的配置資訊來判斷該切片是否適配當前handler,如果是不由@ControllerAdvice註解修飾的RequestBodyAdvice介面(切面),則直接判斷該切片適配當前handler。

至此,從request中解析@RequestParam,@PathVariable和@RequestBody註解修飾的參數的流程基本分析完畢。

小節:RequestMappingHandlerAdapter在解析參數前會根據需要執行的handler方法生成一個ServletInvocableHandlerMethod物件,然後為該物件載入參數解析器,在進行參數解析時,會遍歷所有載入的參數解析器並判斷遍歷到的參數解析器能否解析當前參數,如果能,則使用該參數解析器完成參數解析。

三. RequestMappingHandlerAdapter執行handler方法

在上一小節中已經知道,handler方法的參數獲取和執行發生在InvocableHandlerMethod的invokeForRequest()方法中,再給出該方法的源碼如下所示。

上一小節主要是學習getMethodArgumentValues()方法做了什麼事情,本小節開始學習doInvoke()方法如何執行handler。doInvoke()方法實現如下所示。

小節:handler方法的執行是通過反射的方式實現的。

四. RequestMappingHandlerAdapter處理返回結果

在第二小節中已經知道,返回值處理是發生在ServletInvocableHandlerMethod物件的invokeAndHandle()方法中,再給出其實現如下所示。

ServletInvocableHandlerMethod物件的成員變數returnValueHandlers是一個HandlerMethodReturnValueHandlerComposite物件,下面看一下該物件的handleReturnValue()方法。

在handleReturnValue()方法中先是根據返回值類型匹配對應的返回值處理器,然後用匹配到的返回值處理器對返回值進行處理。

基於SpringMVC框架進行web開發時,比較標準的返回值類型應該為ResponseEntity<T>,該類是由Spring提供的一個繼承於HttpEntity<T>的響應實體類,ResponseEntity<T>相較於HttpEntity<T>增加了響應狀態碼的成員變數,SpringMVC框架專門提供了一個ResponseEntity<T>對應的返回值處理器HttpEntityMethodProcessor。

同時,如果返回值類型是一般類型或者自定義類型,但是handler方法或者handler由@ResponseBody註解修飾,那麼這種情況對應的返回值處理器是第二小節中出現過的RequestResponseBodyMethodProcessor。

那麼現在以HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor這兩個返回值處理器為例,對返回值處理器如何處理返回值進行學習。

首先看一下這兩個返回值處理器的類圖。

HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor均實現了HandlerMethodReturnValueHandler介面,該介面聲明瞭一個用於判斷返回值處理器是否可以處理當前返回值的方法supportsReturnType()。HttpEntityMethodProcessor對該方法的實現如下。

RequestResponseBodyMethodProcessor對該方法的實現如下。

特別說明一下,每次迴圈遍歷返回值處理器時時,HttpEntityMethodProcessor會比RequestResponseBodyMethodProcessor先被遍歷到,因此如果handler類或handler方法既由@ResponseBody註解修飾同時返回值類型還是ResponseEntity,那麼還是會使用HttpEntityMethodProcessor來處理該返回值。

HandlerMethodReturnValueHandler介面還聲明瞭一個方法為handleReturnValue(),該方法會實際的執行處理返回值的邏輯。HttpEntityMethodProcessor對該方法的實現如下。

RequestResponseBodyMethodProcessor

對該方法的實現如下。

可以發現,HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor在handleReturnValue()方法中做了同樣的事情:先將ModelAndViewContainer物件的handler方法是否執行標誌設定為true,然後基於HttpServletRequest和HttpServletResponse物件創建ServletServerHttpRequest和ServletServerHttpResponse物件,最後呼叫公共父類AbstractMessageConverterMethodProcessor的writeWithMessageConverters()方法處理返回值。

其中HttpEntityMethodProcessor多做了兩件事情:設定響應頭和設定響應碼。最後看一下writeWithMessageConverters()的實現。

在writeWithMessageConverters()方法中,主要是獲取響應體媒體類型,然後呼叫訊息轉換器將返回值按照期望的媒體類型寫入響應體,在寫入前還會執行ResponseBodyAdvice切面對返回值實現一些定製操作。

小節:RequestMappingHandlerAdapter處理返回值首先會根據返回值類型匹配相應的返回值處理器來處理返回值,在返回值處理器中通常是先獲取響應體的媒體類型,然後呼叫訊息轉換器將返回值按照期望的媒體類型寫入響應體。

總結

RequestMappingHandlerAdapter是基於SpringMVC框架進行web開發使用頻次最高的元件之一,RequestMappingHandlerAdapter在handler方法執行前會使用SpringMVC框架提供的和使用者實現的參數解析器從request中獲取handler方法需要的參數,然後基於反射方式呼叫handler方法,最後會使用SpringMVC框架提供的和使用者實現的返回值處理器將handler方法的返回值寫入響應體中。


IT145.com E-mail:sddin#qq.com