Spring 註解面面通 之 @RequestParam參數綁定源碼解析

  Spring MVC中使用HandlerMethodArgumentResolver策略接口來定義處理器方法參數解析器,@RequestParam使用的是RequestParamMapMethodArgumentResolverRequestParamMethodArgumentResolver,接下來一塊兒來深刻了解一下其源碼實現。java

  類結構web

在這裏插入圖片描述

在這裏插入圖片描述

  類解析spring

  HandlerMethodArgumentResolverAbstractNamedValueMethodArgumentResolver是解析策略的上層定義和抽象,關於這兩個類能夠參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》中的解析。數組

  RequestParamMapMethodArgumentResolverRequestParamMethodArgumentResolver則是用來針對不用類型的方法參數的解析。ide

  1) RequestParamMapMethodArgumentResolver實現了HandlerMethodArgumentResolversupportsParameter(...)resolveArgument(...)方法。網站

  RequestParamMapMethodArgumentResolver相對比較簡單,但在某些條件成立的狀況下才會使用此類進行解析:ui

  ① 方法參數由@RequestParam註解註釋。this

  ② 方法參數類型必須是Map類型。url

  ③ @RequestParam註解的name不能有值。spa

  resolveArgument(...)在解析參數時,從NativeWebRequestHttpServletRequest的包裝)中獲取全部參數,針對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類型時,@RequestParamname屬性不能爲空。

  ③ 方法參數若不是Map類型時,均可以處理。

  當處理@RequestPart註解時,需在某些條件成立的狀況下才會使用此類進行解析:

  ① 方法參數不可由@RequestPart註解註釋。

  ② 方法參數類型爲org.springframework.web.multipart.MultipartFileorg.springframework.web.multipart.MultipartFile集合、org.springframework.web.multipart.MultipartFile數組、javax.servlet.http.Partjavax.servlet.http.Part集合或javax.servlet.http.Part數組。

​  ③ 一個簡單類型的方法參數,包括:booleanbytecharshortintlongfloatdoubleEnum.classCharSequence.classNumber.class、Date.class、URI.class、URL.classLocale.classClass.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使用頻率會愈來愈普遍。

  若文中存在錯誤和不足,歡迎指正!

相關文章
相關標籤/搜索