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使用频率会愈来愈普遍。

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

相关文章
相关标签/搜索