該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》git
HandlerAdapter 組件,處理器的適配器。由於處理器 handler
的類型是 Object 類型,須要有一個調用者來實現 handler
是怎麼被執行。Spring 中的處理器的實現多變,好比用戶的處理器能夠實現 Controller 接口或者 HttpRequestHandler 接口,也能夠用 @RequestMapping
註解將方法做爲一個處理器等,這就致使 Spring MVC 沒法直接執行這個處理器。因此這裏須要一個處理器適配器,由它去執行處理器github
因爲 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,因此將這部份內容拆分紅了五個模塊,依次進行分析:web
本文是接着《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》一文來分享 HandlerMethodArgumentResolver 組件。在 HandlerAdapter
執行處理器的過程當中,具體的執行過程交由 ServletInvocableHandlerMethod
對象來完成,其中須要先經過 HandlerMethodArgumentResolver 參數解析器從請求中解析出方法的入參,而後再經過反射機制調用對應的方法。spring
先來回顧一下 ServletInvocableHandlerMethod
在哪裏調用參數解析器的,能夠回到 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》 中 InvocableHandlerMethod 小節下面的 getMethodArgumentValues
方法,以下:express
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 組合對象,包含了許多的參數解析器緩存
org.springframework.web.method.support.HandlerMethodArgumentResolver
,方法參數解析器mvc
public interface HandlerMethodArgumentResolver { /** * 是否支持解析該參數 */ boolean supportsParameter(MethodParameter parameter); /** * 解析該參數 */ @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
由於請求入參的場景很是多,因此 HandlerMethodArgumentResolver 的實現類也很是多,上面僅列出了部分實現類,本文也僅分析上面圖中右側常見的幾種參數場景
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(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(MethodParameter parameter)
方法,若是能得到到對應的 HandlerMethodArgumentResolver 參數處理器,則說明支持處理該參數,方法以下:
@Override public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; }
實現 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
執行解析
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
,實現 ValueMethodArgumentResolver 接口,基於名字獲取值的HandlerMethodArgumentResolver 抽象基類。例如說,@RequestParam(value = "username")
註解的參數,就是從請求中得到 username
對應的參數值。😈 明白了麼?
AbstractNamedValueMethodArgumentResolver 的子類也有挺多了,咱們僅分析它的兩個子類,如上面類圖的下面兩個:
@RequestParam
註解( 也可不加該註解的請求參數 )的方法參數,詳情見下文@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); }
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 = defaultValue; } }
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 = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue); // 建立 NamedValueInfo 對象 return new NamedValueInfo(name, info.required, defaultValue); }
若是名稱爲空,則取參數名,獲取默認值,建立一個新的 NamedValueInfo 對象返回
添加到 namedValueInfoCache
緩存中
返回該 NamedValueInfo 對象
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 = "${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 == 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 會重寫該方法
代碼有點長,不過邏輯不難理解
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(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
註解的狀況
@RequestParam
註解必需要有 name 屬性,是否是感受有幾分靈異?答案在下面的 RequestParamMapMethodArgumentResolver 中揭曉true
實現父類的 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(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 做爲參數就是在這裏處理的由於在《MultipartResolver 組件》中講過了會對請求進行處理,包括解析出參數,解析成對應的 HttpServletRequest 對象
得到到參數值後,就能夠準備開始經過反射調用對應的方法了
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
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
,實現 UriComponentsContributor 接口,繼承 AbstractNamedValueMethodArgumentResolver 抽象類,處理路徑參數
實現 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
註解@PathVariable
註解有 name
屬性,才返回 true
,查看 org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver
就理解了,和上述的邏輯差很少true
實現 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(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(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 源碼分析》