精盡Spring MVC原始碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver
阿新 • • 發佈:2020-12-18
> 該系列文件是本人在學習 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` 對應的引數值。