該系列檔案是本人在學習 Spring MVC 的原始碼程序中總結下來的,可能對讀者不太友好,請結合我的原始碼注釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他檔案請查看:《精盡 Spring MVC 原始碼分析 - 文章導讀》
HandlerAdapter 組件
HandlerAdapter 組件,處理器的配接器,因為處理器 handler 的型別是 Object 型別,需要有一個呼叫者來實作 handler 是怎么被執行,Spring 中的處理器的實作多變,比如用戶的處理器可以實作 Controller 介面或者 HttpRequestHandler 介面,也可以用 @RequestMapping 注解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器,所以這里需要一個處理器配接器,由它去執行處理器
由于 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模塊,依次進行分析:
- 《HandlerAdapter 組件(一)之 HandlerAdapter》
- 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》
- 《HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver》
- 《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》
- 《HandlerAdapter 組件(五)之 HttpMessageConverter》
HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver
本文是接著《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》一文來分享 HandlerMethodArgumentResolver 組件,在 HandlerAdapter 執行處理器的程序中,具體的執行程序交由 ServletInvocableHandlerMethod 物件來完成,其中需要先通過 HandlerMethodArgumentResolver 引數決議器從請求中決議出方法的入參,然后再通過反射機制呼叫對應的方法,
回顧
先來回顧一下 ServletInvocableHandlerMethod 在哪里呼叫引數決議器的,可以回到 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》 中 InvocableHandlerMethod 小節下面的 getMethodArgumentValues 方法,如下:
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,方法引數決議器
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 實作類
構造方法
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
/**
* HandlerMethodArgumentResolver 陣列
*/
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
/**
* MethodParameter 與 HandlerMethodArgumentResolver 的映射,作為快取
*/
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
}
argumentResolvers:HandlerMethodArgumentResolver 陣列,這就是 Composite 復合~argumentResolverCache:MethodParameter 與 HandlerMethodArgumentResolver 的映射,作為快取,因為,MethodParameter 是需要從argumentResolvers遍歷到適合其的決議器,通過快取后,無需再次重復遍歷
在《HandlerAdapter 組件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter小節的 getDefaultArgumentResolvers 方法中可以看到,默認的 argumentResolvers 有哪些 HandlerMethodArgumentResolver 實作類,注意這里是有順序的添加哦
getArgumentResolver
getArgumentResolver(MethodParameter parameter) 方法,獲得方法引數對應的 HandlerMethodArgumentResolver 物件,方法如下:
@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 引數處理器,則說明支持處理該引數,方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
resolveArgument
實作 resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,決議出指定引數的值,方法如下:
@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 = "https://www.cnblogs.com/lifullmoon/p/username") 注解的引數,就是從請求中獲得 username 對應的引數值,?? 明白了么?
AbstractNamedValueMethodArgumentResolver 的子類也有挺多了,我們僅分析它的兩個子類,如上面類圖的下面兩個:
- RequestParamMethodArgumentResolver:基于
@RequestParam注解( 也可不加該注解的請求引數 )的方法引數,詳情見下文 - PathVariableMethodArgumentResolver ,基于
@PathVariable注解的方法引數,詳情見下文
構造方法
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;
@Nullable
private final BeanExpressionContext expressionContext;
/**
* MethodParameter 和 NamedValueInfo 的映射,作為快取
*/
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
}
NamedValueInfo 內部類
AbstractNamedValueMethodArgumentResolver 的靜態內部類,代碼如下:
protected static class NamedValueInfo {
/**
* 名字
*/
private final String name;
/**
* 是否必填
*/
private final boolean required;
/**
* 默認值
*/
@Nullable
private final String defaultValue;
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = https://www.cnblogs.com/lifullmoon/p/defaultValue;
}
}
getNamedValueInfo
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
// <1> 從 namedValueInfoCache 快取中,獲得 NamedValueInfo 物件
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
// <2> 獲得不到,則創建 namedValueInfo 物件,這是一個抽象方法,子類來實作
namedValueInfo = createNamedValueInfo(parameter);
// <3> 更新 namedValueInfo 物件
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
// <4> 添加到 namedValueInfoCache 快取中
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
-
從
namedValueInfoCache快取中,獲得 NamedValueInfo 物件,獲取到則直接回傳 -
獲得不到,則呼叫
createNamedValueInfo(MethodParameter parameter)方法,創建 NamedValueInfo 物件,這是一個抽象方法,交由子類來實作 -
呼叫
updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info)方法,更新 NamedValueInfo 物件,方法如下:private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) { String name = info.name; if (info.name.isEmpty()) { // 【注意!!!】如果 name 為空,則使用引數名 name = parameter.getParameterName(); if (name == null) { throw new IllegalArgumentException( "Name for argument type [" + parameter.getNestedParameterType().getName() + "] not available, and parameter name information not found in class file either."); } } // 獲得默認值 String defaultValue = https://www.cnblogs.com/lifullmoon/p/(ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue); // 創建 NamedValueInfo 物件 return new NamedValueInfo(name, info.required, defaultValue); }如果名稱為空,則取引數名,獲取默認值,創建一個新的 NamedValueInfo 物件回傳
-
添加到
namedValueInfoCache快取中 -
回傳該 NamedValueInfo 物件
resolveArgument
resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,從請求中決議出指定引數的值
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// <1> 獲得方法引數對應的 NamedValueInfo 物件,
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// <2> 如果 parameter 是內嵌型別(Optional 型別)的,則獲取內嵌的引數,否則,還是使用 parameter 自身
MethodParameter nestedParameter = parameter.nestedIfOptional();
// <3> 如果 name 是占位符,則進行決議成對應的值
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
// 如果決議不到,則拋出 IllegalArgumentException 例外
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// <4> 決議 name 對應的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
// <5> 如果 arg 不存在,則使用默認值
if (arg == null) {
// <5.1> 使用默認值
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// <5.2> 如果是必填,則處理引數缺失的情況
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// <5.3> 處理空值的情況
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
// <6> 如果 arg 為空串,則使用默認值
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// <7> 資料系結相關
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
// <8> 處理決議的值
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
-
呼叫
getNamedValueInfo(MethodParameter parameter)方法,獲得方法引數對應的 NamedValueInfo 物件 -
如果
parameter是內嵌型別(Optional 型別)的,則獲取內嵌的引數,否則,還是使用parameter自身,一般情況下,parameter引數,我們不太會使用 Optional 型別,可以暫時忽略 -
呼叫
resolveStringValue(String value)方法,如果name是占位符,則進行決議成對應的值,方法如下:@Nullable private Object resolveStringValue(String value) { // 如果 configurableBeanFactory 為空,則不進行決議 if (this.configurableBeanFactory == null) { return value; } // 獲得占位符對應的值 String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value); // 獲取運算式處理器物件 BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); if (exprResolver == null || this.expressionContext == null) { return value; } // 計算運算式 return exprResolver.evaluate(placeholdersResolved, this.expressionContext); }這種用法非常小眾,從來沒用過,示例如下:
// Controller.java @RequestMapping("/hello3") public String hello3(@RequestParam(value = "https://www.cnblogs.com/lifullmoon/p/${server.port}") String name) { return "666"; } // application.properties server.port=8012此時,就可以發送
GET /hello3?8012=xxx請求 -
【重點】呼叫
resolveName(String name, MethodParameter parameter, NativeWebRequest request)抽象方法,決議引數名name對應的值,交由子類去實作 -
如果上面決議出來的引數值
arg為null,則使用默認值-
如果默認值非空,則呼叫
resolveStringValue(defaultValue)方法,決議默認值 -
如果是必填,則呼叫
handleMissingValue(handleMissingValue)方法,處理引數缺失的情況呼叫,也就是拋出指定的例外 -
呼叫
handleNullValue(String name, Object value, Class<?> paramType)方法,處理null值的情況,方法如下:@Nullable private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) { if (value =https://www.cnblogs.com/lifullmoon/p/= null) { if (Boolean.TYPE.equals(paramType)) { return Boolean.FALSE; } else if (paramType.isPrimitive()) { // 如果是基本型別則不能為 null throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name + "' is present but cannot be translated into a null value due to being declared as a " + "primitive type. Consider declaring it as object wrapper for the corresponding primitive type."); } } return value; }
-
-
否則,如果
arg為空字串,并且存在默認值,則和上面的5.1相同處理方式 -
資料系結相關,暫時忽略
-
呼叫
handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)方法,決議引數值的后置處理,空方法,子類可以覆寫,子類 PathVariableMethodArgumentResolver 會重寫該方法
代碼有點長,不過邏輯不難理解
RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver,實作 UriComponentsContributor 介面,繼承 AbstractNamedValueMethodArgumentResolver 抽象類,引數決議器 HandlerMethodArgumentResolver 的實作類,處理普通的請求引數
構造方法
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
/**
* 是否使用默認解決
*
* 這個變數有點繞,見 {@link #supportsParameter(MethodParameter)} 方法
*/
private final boolean useDefaultResolution;
public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
this.useDefaultResolution = useDefaultResolution;
}
public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
boolean useDefaultResolution) {
super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}
}
supportsParameter
實作 supportsParameter(MethodParameter parameter) 方法,判斷是否支持處理該方法入參,方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// <3> 有 @RequestParam 注解的情況
if (parameter.hasParameterAnnotation(RequestParam.class)) {
// <3.1> 如果是 Map 型別,則 @RequestParam 注解必須要有 name 屬性
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
// <3.2> 否則回傳 true
return true;
}
}
else {
// 如果有 @RequestPart 注解,回傳 false ,即 @RequestPart 的優先級 > @RequestParam
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
// 獲得引數,如果存在內嵌的情況
parameter = parameter.nestedIfOptional();
// <1> 如果 Multipart 引數,則回傳 true ,表示支持
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
// <2> 如果開啟 useDefaultResolution 功能,則判斷是否為普通型別
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
// 其它,不支持
else {
return false;
}
}
}
-
如果 Multipart 引數,則回傳 true ,表示支持呼叫
MultipartResolutionDelegate#isMultipartArgument(parameter)方法,如果 Multipart 引數,則回傳true,表示支持,代碼如下:public static boolean isMultipartArgument(MethodParameter parameter) { Class<?> paramType = parameter.getNestedParameterType(); return (MultipartFile.class == paramType || isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) || (Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter))); }上傳檔案相關型別
-
如果開啟
useDefaultResolution功能,則呼叫BeanUtils#isSimpleProperty(Class<?> clazz)方法,判斷是否為普通型別,代碼如下:public static boolean isSimpleProperty(Class<?> type) { Assert.notNull(type, "'type' must not be null"); return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType())); } public static boolean isSimpleValueType(Class<?> type) { return (type != void.class && type != Void.class && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); }那么
useDefaultResolution到底是怎么被賦值的呢?回到 RequestMappingHandlerAdapter 的getDefaultArgumentResolvers()的方法,精簡代碼如下:private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); // ... 省略許多 HandlerMethodArgumentResolver 的添加 // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }我們可以看到有兩個 RequestParamMethodArgumentResolver 物件,前者
useDefaultResolution為false,后者為useDefaultResolution為true,什么意思呢?優先將待有@RequestParam注解的請求引數給第一個 RequestParamMethodArgumentResolver 物件;其次,給中間省略的一大片引數決議器試試能不能決議;最后,使用第二個 RequestParamMethodArgumentResolver 兜底,處理剩余的情況, -
如果該方法引數有
@RequestParam注解的情況- 如果是 Map 型別,則
@RequestParam注解必須要有 name 屬性,是不是感覺有幾分靈異?答案在下面的 RequestParamMapMethodArgumentResolver 中揭曉 - 否則,回傳
true
- 如果是 Map 型別,則
createNamedValueInfo
實作父類的 createNamedValueInfo(MethodParameter parameter) 方法,創建 NamedValueInfo 物件,方法如下:
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
public RequestParamNamedValueInfo(RequestParam annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}
-
如果方法引數有
@RequestParam注解,則根據注解創建一個 RequestParamNamedValueInfo 物件,獲取注解中的name、required和defaultValue配置 -
否則,就創建一個空的 RequestParamNamedValueInfo 物件,三個屬性分別為,
空字串、false和ValueConstants.DEFAULT_NONE上面的 getNamedValueInfo 方法中講述到,
name為空字串沒有關系,會獲取方法的引數名說明:通過反射獲取方法的引數名,我們只能獲取到 arg0,arg1 的名稱,因為jdk8之后這些變數名稱沒有被編譯到class檔案中,編譯時需要指定
-parameters選項,方法的引數名才會記錄到class檔案中,運行時我們就可以通過反射機制獲取到,所以我們最好還是用@RequestParam注解來標注ValueConstants.DEFAULT_NONE則會設定為null
resolveName
實作 #resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,獲得引數的值,方法如下:
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
// 情況一,HttpServletRequest 情況下的 MultipartFile 和 Part 的情況
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
// 情況二,MultipartHttpServletRequest 情況下的 MultipartFile 的情況
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
// 情況三,普通引數的獲取
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
- 情況一、二,是處理引數型別為檔案
org.springframework.web.multipart.MultipartFile和javax.servlet.http.Part的引數的獲取,例如我們常用到 MultipartFile 作為引數就是在這里處理的 - 情況三,是處理普通引數的獲取,就是我們常見的 String、Integer 之類的請求引數,直接從請求中獲取引數值就好了
因為在《MultipartResolver 組件》中講過了會對請求進行處理,包括決議出引數,決議成對應的 HttpServletRequest 物件
獲得到引數值后,就可以準備開始通過反射呼叫對應的方法了
RequestParamMapMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver,實作 HandlerMethodArgumentResolver 介面,用于處理帶有 @RequestParam 注解,但是注解上沒有 name 屬性的 Map 型別的引數, HandlerMethodArgumentResolver 的實作類,代碼如下:
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(requestParam.name()));
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
// MultiValueMap 型別的處理
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
if (valueType == MultipartFile.class) { // MultipartFile 型別
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
}
else if (valueType == Part.class) { // Part 型別
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
result.add(part.getName(), part);
}
return result;
}
return new LinkedMultiValueMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
}
});
return result;
}
}
// 普通 Map 型別的處理
else {
Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
if (valueType == MultipartFile.class) { // MultipartFile 型別
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
}
else if (valueType == Part.class) { // Part 型別
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedHashMap<String, Part> result = new LinkedHashMap<>(parts.size());
for (Part part : parts) {
if (!result.containsKey(part.getName())) {
result.put(part.getName(), part);
}
}
return result;
}
return new LinkedHashMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
return result;
}
}
}
}
上面沒有仔細看,實際上是有點看不懂,不知道處理場景??就舉兩個例子吧
-
對于 RequestParamMapMethodArgumentResolver 類,它的效果是,將所有引數添加到 Map 集合中,示例如下:
// Controller.java @RequestMapping("/hello") public String hello4(@RequestParam Map<String, Object> map) { return "666"; }發送請求
GET /hello?name=yyy&age=20,name和age引數,就會都添加到map中 -
對于 RequestParamMethodArgumentResolver 類,它的效果是,將指定名字的引數添加到 Map 集合中,示例如下:
// Controller.java @RequestMapping("/hello") public String hello5(@RequestParam(name = "map") Map<String, Object> map) { return "666"; }發送請求
GET /hello4?map={"name": "yyyy", age: 20},map引數的元素則都會添加到方法引數map中,當然,要注意下,實際請求要 UrlEncode 編碼下引數,所以實際請求是GET /hello?map=%7b%22name%22%3a+%22yyyy%22%2c+age%3a+20%7d
PathVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver,實作 UriComponentsContributor 介面,繼承 AbstractNamedValueMethodArgumentResolver 抽象類,處理路徑引數
supportsParameter
實作 supportsParameter(MethodParameter parameter) 方法,判斷是否支持處理該方法引數,代碼如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// <1> 如果無 @PathVariable 注解
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
// <2> Map 型別,有 @PathVariable 注解,但是有 name 屬性
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
- 如果沒有
@PathVariable注解則直接回傳fasle,也就是說必須配置@PathVariable注解 - 如果還是 Map 型別,則需要
@PathVariable注解有name屬性,才回傳true,查看org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver就理解了,和上述的邏輯差不多 - 否則,直接回傳
true
createNamedValueInfo
實作 createNamedValueInfo(MethodParameter parameter) 方法,方法如下:
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
// 獲得 @PathVariable 注解
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
// 創建 PathVariableNamedValueInfo 物件
return new PathVariableNamedValueInfo(ann);
}
private static class PathVariableNamedValueInfo extends NamedValueInfo {
public PathVariableNamedValueInfo(PathVariable annotation) {
super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
}
}
必須要有 @PathVariable 注解,沒有的話拋出例外,然后根據注解創建 PathVariableNamedValueInfo 物件
resolveName
實作 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,從請求路徑中獲取方法引數的值,方法如下:
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
// 獲得路徑引數
Map<String, String> uriTemplateVars = (Map<String, String>) request.
getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
// 獲得引數值
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
handleResolvedValue
重寫 handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request) 方法,添加獲得的屬性值到請求的 View.PATH_VARIABLES 屬性種,方法如下:
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
// 獲得 pathVars
String key = View.PATH_VARIABLES;
int scope = RequestAttributes.SCOPE_REQUEST;
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
// 如果不存在 pathVars,則進行創建
if (pathVars == null) {
pathVars = new HashMap<>();
request.setAttribute(key, pathVars, scope);
}
// 添加 name + arg 到 pathVars 中
pathVars.put(name, arg);
}
具體用途還不清楚??
總結
在 HandlerAdapter 執行 HandlerMethod 處理器的程序中,會將該處理器封裝成 ServletInvocableHandlerMethod 物件,通過該物件來執行處理器,該物件通過反射機制呼叫對應的方法,在呼叫方法之前,借助 HandlerMethodArgumentResolver 引數決議器從請求中獲取到對應的方法引數值,因為你無法確認哪個引數值對應哪個引數,所以需要先通過它從請求中決議出引數值,一一對應,然后才能呼叫該方法,
HandlerMethodArgumentResolver 引數決議器的實作類非常多,采用了組合模式來進行處理,如果有某一個引數決議器支持決議該方法引數,則使用它從請求體中獲取到該方法引數的值,注意這里有一定的先后順序,因為是通過 LinkedList 保存所有的實作類,排在前面的實作類則優先處理,
本文分析了我們常用的 @RequestParam 和 @PathVariable 注解所對應的 HandlerMethodArgumentResolver 實作類,如下:
RequestParamMethodArgumentResolver:決議@RequestParam注解配置引數(名稱、是否必須、默認值),根據注解配置從請求獲取引數值PathVariableMethodArgumentResolver:決議@PathVariable注解配置的(名稱、是否必須),根據注解配置從請求路徑中獲取引數值
注意,關于方法引數為 Map 型別,應該如何配置,可以參考上面的 RequestParamMapMethodArgumentResolver 小節中的兩個示例
關于其他的 HandlerMethodArgumentResolver 實作類,感興趣的可以去看看
在接下來的《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》中講到 RequestResponseBodyMethodProcessor 既是 HandlerMethodReturnValueHandler 實作類,也是 HandlerMethodArgumentResolver 實作類,用于處理器 @RequestBody 和 @ResponseBody 兩個注解
參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/236909.html
標籤:Java
上一篇:一文搞懂什么是事務
下一篇:java~jar防止反編譯
