Spring MVC中使用HandlerMethodArgumentResolver
策略接口來定義處理器方法參數解析器,@RequestParam
使用的是RequestParamMapMethodArgumentResolver
和RequestParamMethodArgumentResolver
,接下來一塊兒來深刻了解一下其源碼實現。java
類結構web
類解析spring
HandlerMethodArgumentResolver
和AbstractNamedValueMethodArgumentResolver
是解析策略的上層定義和抽象,關於這兩個類能夠參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》中的解析。數組
RequestParamMapMethodArgumentResolver
和RequestParamMethodArgumentResolver
則是用來針對不用類型的方法參數的解析。ide
1) RequestParamMapMethodArgumentResolver
實現了HandlerMethodArgumentResolver
的supportsParameter(...)
和resolveArgument(...)
方法。網站
RequestParamMapMethodArgumentResolver
相對比較簡單,但在某些條件成立的狀況下才會使用此類進行解析:ui
① 方法參數由@RequestParam
註解註釋。this
② 方法參數類型必須是Map
類型。url
③ @RequestParam
註解的name
不能有值。spa
resolveArgument(...)
在解析參數時,從NativeWebRequest
(HttpServletRequest
的包裝)中獲取全部參數,針對MultiValueMap
和普通Map
兩種參數類型進行處理:
① 參數類型爲MultiValueMap
時,返回LinkedMultiValueMap
實例,包含全部請求參數。
② 參數類型爲Map
時,返回LinkedHashMap
實例,包含全部請求參數。
package org.springframework.web.method.annotation; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** * 解析用@RequestParam註釋的Map類型方法參數,其中未指定請求參數名稱. * * 建立的Map包含全部請求參數名稱/值對. * 若是方法參數類型是MultiValueMap,那麼對於請求參數具備多個值的狀況, * 建立的映射包含全部請求參數和值. */ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver { /** * 方法參數檢查. * 方法參數由@RequestParam註釋,且@RequestParam的name屬性爲空. * 方法參數類型必須爲Map類型. */ @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 { Class<?> paramType = parameter.getParameterType(); // 獲取請求的全部參數. Map<String, String[]> parameterMap = webRequest.getParameterMap(); // 方法參數類型爲MultiValueMap. if (MultiValueMap.class.isAssignableFrom(paramType)) { MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size()); parameterMap.forEach((key, values) -> { for (String value : values) { result.add(key, value); } }); return result; } // 方法參數類型爲非MultiValueMap的Map類型. else { Map<String, String> result = new LinkedHashMap<>(parameterMap.size()); parameterMap.forEach((key, values) -> { if (values.length > 0) { result.put(key, values[0]); } }); return result; } } }
2) RequestParamMethodArgumentResolver
繼承自抽象AbstractNamedValueMethodArgumentResolver
(能夠參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》)。
RequestParamMethodArgumentResolver
除了能處理@RequestParam
註解外,還能夠處理@RequestPart
註解:
當處理@RequestParam
註解時,需在某些條件成立的狀況下才會使用此類進行解析:
① 方法參數由@RequestParam
註解註釋。
② 方法參數如果Map
類型時,@RequestParam
的name
屬性不能爲空。
③ 方法參數若不是Map類型時,均可以處理。
當處理@RequestPart
註解時,需在某些條件成立的狀況下才會使用此類進行解析:
① 方法參數不可由@RequestPart
註解註釋。
② 方法參數類型爲org.springframework.web.multipart.MultipartFile
、org.springframework.web.multipart.MultipartFile
集合、org.springframework.web.multipart.MultipartFile
數組、javax.servlet.http.Part
、javax.servlet.http.Part
集合或javax.servlet.http.Part
數組。
③ 一個簡單類型的方法參數,包括:boolean
、byte
、char
、short
、int
、long
、float
、double
、Enum.class
、CharSequence.class
、Number
.class、Date
.class、URI
.class、URL.class
、Locale.class
或Class.class
。
package org.springframework.web.method.annotation; import java.beans.PropertyEditor; import java.util.Collection; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.UriComponentsContributor; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.multipart.support.MultipartResolutionDelegate; import org.springframework.web.util.UriComponentsBuilder; /** * 解析用@RequestParam註釋的方法參數,MultipartFile類型的參數與 * Spring的{@link MultipartResolver}抽象結合使用,以及AAA類型的參數與Servlet 3.0多部分請求一塊兒使用. * 這個解析器也能夠在默認的解析模式下建立,在這種模式下,沒有用RequestParam註釋的簡單類型(int、long等)也被視爲請求參數,參數名從參數名派生. * * 若是方法參數類型是Map,則使用註釋中指定的名稱來解析請求參數字符串值. * 而後經過類型轉換將該值轉換爲Map,假設已經註冊了合適的Converter或PropertyEditor. * 或者,若是沒有指定請求參數名,則使用RequestParamMapMethodArgumentResolver以映射的形式提供對全部請求參數的訪問. * * 調用@WebDataBinder將類型轉換應用於還沒有與方法參數類型匹配的已解析請求頭值. */ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor { /** * String 類型描述符. */ private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); private final boolean useDefaultResolution; /** * @param useDefaultResolution 在默認解析模式下,一個簡單類型的方法參數, * 如BeanUtils.isSimpleProperty中定義的那樣,被視爲一個請求參數, * 即便它沒有被註釋,請求參數名是從方法參數名派生的. */ public RequestParamMethodArgumentResolver(boolean useDefaultResolution) { this.useDefaultResolution = useDefaultResolution; } /** * @param beanFactory 一個Bean工廠,用於解析默認值中的${…}佔位符和#{…}SpEL表達式,若是默認值不包含表達式,則爲null. * @param useDefaultResolution 在默認解析模式下,一個簡單類型的方法參數, * 如BeanUtils.isSimpleProperty中定義的那樣,被視爲一個請求參數, * 即便它沒有被註釋,請求參數名是從方法參數名派生的. */ public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory); this.useDefaultResolution = useDefaultResolution; } /** * 方法參數檢查: * 方法參數由@RequestParam註釋,且@RequestParam的name屬性爲空. * 方法參數類型爲Map類型. * 方法參數不可由@RequestPart註釋. */ @Override public boolean supportsParameter(MethodParameter parameter) { // 處理@RequestParam註解. if (parameter.hasParameterAnnotation(RequestParam.class)) { if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); return (requestParam != null && StringUtils.hasText(requestParam.name())); } else { return true; } } // 處理@RequestPart註解. else { if (parameter.hasParameterAnnotation(RequestPart.class)) { return false; } parameter = parameter.nestedIfOptional(); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { return true; } else if (this.useDefaultResolution) { return BeanUtils.isSimpleProperty(parameter.getNestedParameterType()); } else { return false; } } } /** * 建立NamedValueInfo. */ @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo()); } /** * 解析方法參數值. */ @Override @Nullable protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); // 解析多部分請求值. if (servletRequest != null) { Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } } Object arg = null; MultipartHttpServletRequest multipartRequest = request.getNativeRequest(MultipartHttpServletRequest.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; } /** * 處理參數缺失異常. */ @Override protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { if (servletRequest == null || !MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { throw new MultipartException("Current request is not a multipart request"); } else { throw new MissingServletRequestPartException(name); } } else { throw new MissingServletRequestParameterException(name, parameter.getNestedParameterType().getSimpleName()); } } @Override public void contributeMethodArgument(MethodParameter parameter, @Nullable Object value, UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) { Class<?> paramType = parameter.getNestedParameterType(); if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType || Part.class == paramType) { return; } RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ? parameter.getParameterName() : requestParam.name()); Assert.state(name != null, "Unresolvable parameter name"); if (value == null) { if (requestParam != null && (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE))) { return; } builder.queryParam(name); } else if (value instanceof Collection) { for (Object element : (Collection<?>) value) { element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element); builder.queryParam(name, element); } } else { builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value)); } } @Nullable protected String formatUriValue( @Nullable ConversionService cs, @Nullable TypeDescriptor sourceType, @Nullable Object value) { if (value == null) { return null; } else if (value instanceof String) { return (String) value; } else if (cs != null) { return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR); } else { return value.toString(); } } /** * 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
是用來處理Web請求頭中的信息,隨着網站的多樣和多元化,@RequestParam
使用頻率會愈來愈普遍。
若文中存在錯誤和不足,歡迎指正!