Spring MVC中使用HandlerMethodArgumentResolver
策略接口來定義處理器方法參數解析器,@RequestParam
使用的是RequestParamMapMethodArgumentResolver
和RequestParamMethodArgumentResolver
,接下來一起來深入瞭解一下其源碼實現。
類結構
類解析
HandlerMethodArgumentResolver
和AbstractNamedValueMethodArgumentResolver
是解析策略的上層定義和抽象,關於這兩個類可以參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》中的解析。
RequestParamMapMethodArgumentResolver
和RequestParamMethodArgumentResolver
則是用來針對不用類型的方法參數的解析。
1) RequestParamMapMethodArgumentResolver
實現了HandlerMethodArgumentResolver
的supportsParameter(...)
和resolveArgument(...)
方法。
RequestParamMapMethodArgumentResolver
相對比較簡單,但在某些條件成立的情況下才會使用此類進行解析:
① 方法參數由@RequestParam
註解註釋。
② 方法參數類型必須是Map
類型。
③ @RequestParam
註解的name
不能有值。
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
使用頻率會越來越廣泛。
若文中存在錯誤和不足,歡迎指正!