1. 程式人生 > >精盡Spring MVC原始碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver

精盡Spring MVC原始碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.2.4.RELEASE > > 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html) ## HandlerAdapter 元件 HandlerAdapter 元件,處理器的介面卡。因為處理器 `handler` 的型別是 Object 型別,需要有一個呼叫者來實現 `handler` 是怎麼被執行。Spring 中的處理器的實現多變,比如使用者的處理器可以實現 Controller 介面或者 HttpRequestHandler 介面,也可以用 `@RequestMapping` 註解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器。所以這裡需要一個處理器介面卡,由它去執行處理器 由於 HandlerMapping 元件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模組,依次進行分析: - [**《HandlerAdapter 元件(一)之 HandlerAdapter》**](https://www.cnblogs.com/lifullmoon/p/14137467.html) - [**《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》**](https://www.cnblogs.com/lifullmoon/p/14137483.html) - [**《HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver》**](https://www.cnblogs.com/lifullmoon/p/14137494.html) - [**《HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler》**](https://www.cnblogs.com/lifullmoon/p/14137508.html) - [**《HandlerAdapter 元件(五)之 HttpMessageConverter》**](https://www.cnblogs.com/lifullmoon/p/14137520.html) ## HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver 本文是接著[**《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》**](https://www.cnblogs.com/lifullmoon/p/14137483.html)一文來分享 **HandlerMethodArgumentResolver** 元件。在 `HandlerAdapter` 執行處理器的過程中,具體的執行過程交由 `ServletInvocableHandlerMethod` 物件來完成,其中需要先通過 **HandlerMethodArgumentResolver** 引數解析器從請求中解析出方法的入參,然後再通過反射機制呼叫對應的方法。 ### 回顧 先來回顧一下 `ServletInvocableHandlerMethod` 在哪裡呼叫引數解析器的,可以回到 [**《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》**](https://www.cnblogs.com/lifullmoon/p/14137483.html) 中 **InvocableHandlerMethod** 小節下面的 `getMethodArgumentValues` 方法,如下: ```java protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 獲得方法的引數 MethodParameter[] parameters = getMethodParameters(); // 無參,返回空陣列 if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } // 將引數解析成對應的型別 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { // 獲得當前遍歷的 MethodParameter 物件,並設定 parameterNameDiscoverer 到其中 MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // <1> 先從 providedArgs 中獲得引數。如果獲得到,則進入下一個引數的解析,預設情況 providedArgs 不會傳參 args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // <2> 判斷 resolvers 是否支援當前的引數解析 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { // 執行解析,解析成功後,則進入下一個引數的解析 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; } ``` - `<2>` 處,在獲取到 Method 方法的所有引數物件,依次處理,根據 `resolvers` 判斷是否支援該引數的處理,如果支援則進行引數轉換 - `resolvers` 為 HandlerMethodArgumentResolverComposite 組合物件,包含了許多的引數解析器 ### HandlerMethodArgumentResolver 介面 `org.springframework.web.method.support.HandlerMethodArgumentResolver`,方法引數解析器 ```java public interface HandlerMethodArgumentResolver { /** * 是否支援解析該引數 */ boolean supportsParameter(MethodParameter parameter); /** * 解析該引數 */ @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; } ``` ### 類圖
因為請求入參的場景非常多,所以 HandlerMethodArgumentResolver 的實現類也非常多,上面僅列出了部分實現類,本文也**僅**分析上面圖中右側常見的幾種引數場景 ### HandlerMethodArgumentResolverComposite `org.springframework.web.method.support.HandlerMethodArgumentResolverComposite`,實現 HandlerMethodArgumentResolver 介面,複合的 HandlerMethodArgumentResolver 實現類 #### 構造方法 ```java public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { /** * HandlerMethodArgumentResolver 陣列 */ private final List argumentResolvers = new LinkedList<>(); /** * MethodParameter 與 HandlerMethodArgumentResolver 的對映,作為快取 */ private final Map argumentResolverCache = new ConcurrentHashMap<>(256); } ``` - `argumentResolvers`:HandlerMethodArgumentResolver 陣列。這就是 Composite 複合~ - `argumentResolverCache`:MethodParameter 與 HandlerMethodArgumentResolver 的對映,作為**快取**。因為,MethodParameter 是需要從 `argumentResolvers` 遍歷到適合其的解析器,通過快取後,無需再次重複遍歷 在[**《HandlerAdapter 元件(一)之 HandlerAdapter》**](https://www.cnblogs.com/lifullmoon/p/14137467.html)的**RequestMappingHandlerAdapter**小節的 `getDefaultArgumentResolvers` 方法中可以看到,預設的 `argumentResolvers` 有哪些 HandlerMethodArgumentResolver 實現類,注意這裡是有順序的新增哦 #### getArgumentResolver `getArgumentResolver(MethodParameter parameter)` 方法,獲得方法引數對應的 HandlerMethodArgumentResolver 物件,方法如下: ```java @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { // 優先從 argumentResolverCache 快取中,獲得 parameter 對應的 HandlerMethodArgumentResolver 物件 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { // 獲得不到,則遍歷 argumentResolvers 陣列,逐個判斷是否支援。 for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { // 如果支援,則新增到 argumentResolverCache 快取中,並返回 if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; } ``` 很簡單,先從`argumentResolverCache`快取中獲取,沒有獲取到則遍歷 `argumentResolvers`,如果支援該引數則該 HandlerMethodArgumentResolver 物件並快取起來 **注意**,往 `argumentResolvers` 新增的順序靠前,則優先判斷是否支援該引數哦~ #### supportsParameter 實現 `supportsParameter(MethodParameter parameter)` 方法,如果能獲得到對應的 HandlerMethodArgumentResolver 引數處理器,則說明支援處理該引數,方法如下: ```java @Override public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; } ``` #### resolveArgument 實現 `resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)` 方法,解析出指定引數的值,方法如下: ```java @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 獲取引數解析器 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first."); } /** * 進行解析 * * 基於 @RequestParam 註解 * {@link org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveArgument} * 基於 @PathVariable 註解 * {@link org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver#resolveArgument} */ return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } ``` 很簡單,獲取到該方法引數對應的 HandlerMethodArgumentResolver 引數處理器,然後呼叫其 `resolveArgument` 執行解析 ### AbstractNamedValueMethodArgumentResolver `org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver`,實現 ValueMethodArgumentResolver 介面,基於名字獲取值的HandlerMethodArgumentResolver 抽象基類。例如說,`@RequestParam(value = "username")` 註解的引數,就是從請求中獲得 `username` 對應的引數值。