天天看点

Spring 注解面面通 之 @RequestParam参数绑定源码解析

  Spring MVC中使用

HandlerMethodArgumentResolver

策略接口来定义处理器方法参数解析器,

@RequestParam

使用的是

RequestParamMapMethodArgumentResolver

RequestParamMethodArgumentResolver

,接下来一起来深入了解一下其源码实现。

  类结构

Spring 注解面面通 之 @RequestParam参数绑定源码解析
Spring 注解面面通 之 @RequestParam参数绑定源码解析

  类解析

  

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

使用频率会越来越广泛。

  若文中存在错误和不足,欢迎指正!