在前面我们分析SpringMVC常见组件之HandlerAdapter分析中提到过如下过程

RequestMappingHandlerAdapter.invokeAndHandle(webRequest, mavContainer);
--ServletInvocableHandlerMethod.invokeAndHandle(webRequest, mavContainer);
---`Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);`
---Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

其中最重要的一步就是在​​InvocableHandlerMethod​​中解析请求传输参数,方法源码如下所示:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}

Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}

这里可以看到​​args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);​​其是通过查找相应的参数解析器来获取请求中传输数据。这是整个流程中核心的一步,也是在反射调用目标方法前必要的一步。

【1】HandlerMethodArgumentResolver

源码如下所示,其是一个策略接口主要用来将请求中的参数值解析到目标方法参数中。提供了两个方法让子类实现:supportsParameter用来判断当前参数解析器是否能够处理参数,resolveArgument方法处理参数然后返回结果。

public interface HandlerMethodArgumentResolver {

// 当前参数解析器是否支持当前参数对象
boolean supportsParameter(MethodParameter parameter);

// 返回一个解析好的参数值(对象),可能为null
// mavContainer提供了获取request中model的方式;
// 当需要进行数据绑定和类型转换的时候,WebDataBinderFactory提供了创建WebDataBinder方式
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

我们再来看一下其家族树图示

SpringMVC常见组件之HandlerMethodArgumentResolver解析_java
如下所示,有34个类,值得一提的是某些参数解析器还实现了返回结果处理器的接口,如ModelMethodProcessor。

  • MapMethodProcessor
  • ErrorsMethodArgumentResolver
  • PathVariableMapMethodArgumentResolver
  • AbstractNamedValueMethodArgumentResolver
  • RequestHeaderMethodArgumentResolver
  • RequestAttributeMethodArgumentResolver
  • RequestParamMethodArgumentResolver
  • AbstractCookieValueMethodArgumentResolver
  • ServletCookieValueMethodArgumentResolver
  • ExpressionValueMethodArgumentResolver
  • SessionAttributeMethodArgumentResolver
  • MatrixVariableMethodArgumentResolver
  • PathVariableMethodArgumentResolver
  • RequestHeaderMapMethodArgumentResolver
  • ModelMethodProcessor
  • ModelAttributeMethodProcessor
  • ServletModelAttributeMethodProcessor
  • ServletResponseMethodArgumentResolver
  • SessionStatusMethodArgumentResolver
  • RequestParamMapMethodArgumentResolver
  • PrincipalMethodArgumentResolver
  • ContinuationHandlerMethodArgumentResolver
  • AbstractMessageConverterMethodArgumentResolver
  • RequestPartMethodArgumentResolver
  • AbstractMessageConverterMethodProcessor
  • RequestResponseBodyMethodProcessor
  • HttpEntityMethodProcessor

  • AbstractWebArgumentResolverAdapter
  • ServletWebArgumentResolverAdapter
  • UriComponentsBuilderMethodArgumentResolver
  • HandlerMethodArgumentResolverComposite
  • ServletRequestMethodArgumentResolver
  • RedirectAttributesMethodArgumentResolver
  • MatrixVariableMapMethodArgumentResolver

解析器与参数类型表格

如下表格中“是否解析返回结果”,也就是说其同时实现了HandlerMethodReturnValueHandler接口,可以处理返回结果。

参数解析器

解析返回结果

参数类型

MapMethodProcessor

​是​

Map

ErrorsMethodArgumentResolver


Errors

PathVariableMapMethodArgumentResolver


​@PathVariable​​ Map

PathVariableMethodArgumentResolver


​@PathVariable !Map​​​ 或者 ​​@PathVariable("指定名字") Map​

RequestHeaderMethodArgumentResolver


​@RequestHeader !Map​

RequestHeaderMapMethodArgumentResolver


​@RequestHeader Map​

RequestAttributeMethodArgumentResolver


​@RequestAttribute​

RequestParamMethodArgumentResolver


​@RequestParam("指定名字") Map​

或者​​@RequestParam !Map​​或者​​MultipartFile​​ 类型​​Part​​类型

或者​​BeanUtils.isSimpleProperty​​也就是简单类型

RequestParamMapMethodArgumentResolver


​@RequestParam Map​

AbstractCookieValueMethodArgumentResolver

ServletCookieValueMethodArgumentResolver


​@CookieValue​

ExpressionValueMethodArgumentResolver


​@Value​

SessionAttributeMethodArgumentResolver


​@SessionAttribute​

MatrixVariableMethodArgumentResolver


​@MatrixVariable !Map​​​ 或者 ​​@MatrixVariable ("指定名字") Map​

MatrixVariableMapMethodArgumentResolver


​@MatrixVariable Map​

ModelMethodProcessor

​是​

Model

ModelAttributeMethodProcessor

ServletModelAttributeMethodProcessor


​@ModelAttribute​

或者非简单类型​​!BeanUtils.isSimpleProperty​

ServletResponseMethodArgumentResolver


​ServletResponse​​​或者​​OutputStream​​​或者​​Writer​

SessionStatusMethodArgumentResolver


SessionStatus

PrincipalMethodArgumentResolver


Principal

RequestPartMethodArgumentResolver


​@RequestPart​​​ 或者​​MultipartFile​​​或者​​Part​

RequestResponseBodyMethodProcessor

​是​

​@RequestBody​

HttpEntityMethodProcessor

​是​

HttpEntity 或者 RequestEntity

AbstractWebArgumentResolverAdapter

ServletWebArgumentResolverAdapter


返回结果不为​​WebArgumentResolver.UNRESOLVED​​且是合适的参数类型

​HandlerMethodArgumentResolverComposite​


将参数解析工作交给内部的argumentResolvers

RedirectAttributesMethodArgumentResolver


RedirectAttributes

ContinuationHandlerMethodArgumentResolver


参数名字为​​kotlin.coroutines.Continuation​​,解析直接返回null

UriComponentsBuilderMethodArgumentResolver


UriComponentsBuilder或者ServletUriComponentsBuilder

ServletRequestMethodArgumentResolver


WebRequest

或者ServletRequest

或者MultipartRequest

或者HttpSession

或者Class<?> pushBuilder

或者Principal不带注解

或者InputStream

或者Reader

或者HttpMethod

或者Locale

或者TimeZone

或者ZoneId

​@PathVariable Map​​​表示标注了@PathVariable 注解且参数是Map类型并且没有为参数指定名称如​​在这里@PathVariable("map") Map map​​​。​​@PathVariable !Map​​​表示参数使用了​​@PathVariable​​ 注解但是参数类型不是Map。

如上表格所示,这里XXXXProcessor 表示实现了参数解析器和返回结果处理器。而XXXXResolver是单纯的参数解析器。

接下来我们着重分析几个常见的参数解析器。

【2】HandlerMethodArgumentResolverComposite

通过将处理动作委派给内部注册的一系列​​HandlerMethodArgumentResolvers​​来实现功能。其内部有两个常量如下:

private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);

其首先应用了组合模式,无论​​InvocableHandlerMethod​​​调用​​HandlerMethodArgumentResolverComposite​​​还是单个具体的​​HandlerMethodArgumentResolver​​,其行为都是一致的。

什么行为?第一是判断是否支持当前参数类型也就是supportsParameter方法;第二就是解析参数的方法resolveArgument。

其次应用了委派/策略模式,​​InvocableHandlerMethod​​​在解析参数的时候根本不知道也不关心具体的​​HandlerMethodArgumentResolver​​​是谁,只要将动作扔给​​HandlerMethodArgumentResolverComposite​​就可以拿到参数值(结果)。

① supportsParameter

如下所示,​​HandlerMethodArgumentResolverComposite​​​将判断是否支持参数的动作交给了具体的​​HandlerMethodArgumentResolver​​​ 。其首先从​​argumentResolverCache​​​根据​​parameter​​​获取一个​​HandlerMethodArgumentResolver​​​ ,如果不为null直接返回。如果为null则从​​argumentResolvers​​​获取一个支持当前​​parameter​​​的​​HandlerMethodArgumentResolver​​​ 放入​​argumentResolverCache​​然后返回。

@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}

② resolveArgument

如下所示,​​HandlerMethodArgumentResolverComposite​​​将解析参数的动作交给了具体的​​HandlerMethodArgumentResolver​​​ 。调用​​getArgumentResolver​​​方法获取一个​​HandlerMethodArgumentResolver​​​ 然后进行解析,如果获取不到​​HandlerMethodArgumentResolver​​​ 则抛出异常​​IllegalArgumentException​​。

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

【3】AbstractNamedValueMethodArgumentResolver

① 概述

一个抽象基类,用于从命名值解析方法参数。请求参数、请求头和路径变量是命名值的示例。每个都可能有一个名称、一个必需的标志和一个默认值。子类定义如何处理以下信息:

  • 获取方法参数的命名值信息
  • 将名称解析为参数值
  • 当参数值必须的时候,处理参数值为空的情况
  • 可选地处理已解析的值

将创建一个WebDataBinder,以便在解析的参数值与方法参数类型不匹配时对其应用类型转换。

​什么是“命名值信息”​​​?其实是​​NamedValueInfo​​,源码如下所以包含name、required以及defaultValue三个属性。

protected static class NamedValueInfo {
private final String name;
private final boolean required;
@Nullable
private final String defaultValue;
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}

其子类图示如下所示,主要用来处理请求头、请求属性、会话属性、Spring表达式( @Value)、URL路径变量、CookieValue及其他。这里面最重要也最常见的就是​​RequestParamMethodArgumentResolver​​​。
SpringMVC常见组件之HandlerMethodArgumentResolver解析_参数解析器_02

其提供了如下抽象方法让子类实现:

// 获取参数的NamedValueInfo 对象
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);

// 解析参数名称
@Nullable
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception;

② 方法的默认实现

​handleMissingValue​​​默认抛出​​ServletRequestBindingException​​异常,可以让子类重写。

protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing argument '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}

​handleNullValue​​​方法默认实现,如果参数类型Boolean则返回false,否则如果参数类型是​​paramType.isPrimitive​​​抛出​​IllegalStateException​​异常。

@Nullable
private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
}
else if (paramType.isPrimitive()) {
throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
"' is present but cannot be translated into a null value due to being declared as a " +
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
}
}
return value;
}

​handleResolvedValue​​则默认是个空方法。

protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
}

需要注意的是在createNamedValueInfo后还调用了updateNamedValueInfo,我们看其默认实现:

private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
// 如果name为空,则将参数的名字赋予name
if (info.name.isEmpty()) {
name = parameter.getParameterName();
if (name == null) {
throw new IllegalArgumentException(
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
"] not specified, and parameter name information not found in class file either.");
}
}
// 如果默认值为ValueConstants.DEFAULT_NONE,则赋予null
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}

OK,接下来我们看一下入口核心方法resolveArgument。

③ 模板方法resolveArgument

其是一个典型的模板方法模式,方法类型使用了final避免被子类覆盖。定义好算法的过程,其中某些步骤为抽象方法让子类实现。

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();

Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}

Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}

if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
}

handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

return arg;
}

方法源码解释如下:

  • ① 获取NamedValueInfo 信息包括参数name,是否必须,默认值;
  • ② 尝试解析嵌套的参数得到具体的MethodParameter ;
  • ③ 解析参数的name,这里会判断是否为SpEL表达式如​​@Value("${com.jane.name}" )String name​​​,那么返回​​@Value​​;
  • ④ 根据参数的name解析参数值;
  • ⑤ 如果参数值为null,则尝试根据默认值、是否必须等获取,最终调用​​handleNullValue​​方法;
  • ⑥ 如果参数值为​​""​​​且默认值不为null,则再次尝试调用​​resolveEmbeddedValuesAndExpressions​​(用来处理SpEL表达式);
  • ⑦ 如果binderFactory不为null,则创建WebDataBinder–这个过程会进行数据绑定器的初始化;
  • ⑧ 如果类型不匹配则尝试进行类型转换;
  • ⑨ 抛出​​MethodArgumentConversionNotSupportedException​​​或者​​TypeMismatchException​​;
  • ⑩ handleMissingValue处理缺值单​​required==true&&defaultValue==null​​的情况
  • (11) handleResolvedValue处理解析得到的value,该方法在PathVariableMethodArgumentResolver中有实现。

如果方法参数为​​String testSpEL(@Value("${com.jane.name}" )String name)​​​,那么​​namedValueInfo​​​如下所示
SpringMVC常见组件之HandlerMethodArgumentResolver解析_java_03
如果方法参数为​​​String testSimple(Integer age)​​​,那么​​namedValueInfo​​​如下所示
SpringMVC常见组件之HandlerMethodArgumentResolver解析_spring mvc_04
如果方法参数为​​​String testPath(@PathVariable Integer id)​​​,那么​​namedValueInfo​​​如下所示
SpringMVC常见组件之HandlerMethodArgumentResolver解析_参数类型_05

【4】RequestParamMethodArgumentResolver

解析 ​​@RequestParam("指定名字") Map​​​ 或者​​@RequestParam !Map​​​或者​​MultipartFile​​​ 类型(使用MultipartResolver)​​Part​​​类型(使用Servlet3.0 api)或者​​BeanUtils.isSimpleProperty​​​也就是简单类型的参数。如果参数类型是​​@RequestParam Map​​那么将会使用RequestParamMapMethodArgumentResolver来处理。

如果目标参数类型不匹配,那么将会使用WebDataBinder尝试进行类型转换。

① supportsParameter

public boolean supportsParameter(MethodParameter parameter) {
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;
}
}
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;
}
}
}

方法源码表示其主要支持如下类型参数:

  • ​@RequestParam("指定名字") Map map​​;
  • ​@RequestParam !Map​​​,也就是标注了​​@RequestParam​​注解非Map类型参数 ;
  • ​MultipartFile​​​类型或者​​Part​​类型或者对应集合|数组类型;
  • 简单类型

这里我们看一下什么是简单类型。

public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}

public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}

也就是说参数类型是简单类型或者简单类型的数组。这里首先限定了非Void,然后下面任何一种类型都属于简单类型:

  • ​Primitive Type​​​或者其包装类型如​​java.lang.Boolean、java.lang.Character、java.lang.Byte、java.lang.Short、java.lang.Integer、java.lang.Long、java.lang.Float、java.lang.Double​​。注意java.lang.Void不被认为是简单类型。
  • java.lang.Enum
  • java.lang.CharSequence
  • java.lang.Number
  • java.util.Date
  • java.time.temporal.Temporal
  • java.net.URI
  • java.net.URL
  • java.util.Locale
  • java.lang.Class

② RequestParamNamedValueInfo

​RequestParamMethodArgumentResolver​​​.​​RequestParamNamedValueInfo​​​,其是​​RequestParamMethodArgumentResolver​​的静态内部类继承自NamedValueInfo 。

private static class RequestParamNamedValueInfo extends NamedValueInfo {
// name="",required=false,defaultValue=ValueConstants.DEFAULT_NONE
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
//name=annotation.name(),required=annotation.required(),defaultValue=annotation.defaultValue()
public RequestParamNamedValueInfo(RequestParam annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}

可以看到该类提供了两个构造函数直接调用了super构造函数,分别定义两种(是否有RequestParam注解)NamedValueInfo 实例。

③ resolveArgument

​RequestParamMethodArgumentResolver​​​自身没有​​resolveArgument​​​,其提供了​​createNamedValueInfo​​​、​​resolveName​​​、​​handleMissingValue​​​以及​​contributeMethodArgument​​​方法供父类​​AbstractNamedValueMethodArgumentResolver​​使用。

@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;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.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;
}

方法解释如下:

  • ① 尝试获取HttpServletRequest请求,如果不为null则继续判断请求是否为​​MultipartHttpServletRequest​​​请求或者内容类型是否以​​multipart/​​​开头,如果是,则继续尝试进行Multipart或者Part解析返回对应的单个对象或者集合类型或者数组类型;如果解析结果​​mpArg != MultipartResolutionDelegate.UNRESOLVABLE​​​则直接返回。通俗点将,这里会尝试进行文件上传解析返回​​MultipartFile对象​​​或者​​MultipartFile[]​
  • ② 尝试获取MultipartRequest请求,如果不为null,则尝试解析获取​​List<MultipartFile> files​​然后赋予arg;
  • ③ 如果arg为null,则尝试进行最基本的参数解析--​​String[] paramValues=request.getParameterValues(name);​
  • ④ 返回arg。

④ handleMissingValue处理缺值情况

@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());
}
}

根据参数不同类型,分别抛出MultipartException、MissingServletRequestPartException或者MissingServletRequestParameterException异常。其中这里有我们常见的​​Current request is not a multipart request​​。

【5】ServletModelAttributeMethodProcessor

如下图所示其继承自​​ModelAttributeMethodProcessor​​​,实现了​​HandlerMethodArgumentResolver​​​, ​​HandlerMethodReturnValueHandler​​​两个接口,也就是说其提供了参数解析、返回结果处理两种功能。
SpringMVC常见组件之HandlerMethodArgumentResolver解析_java_06

① supportsParameter

如下所示,如果参数标注了​​@ModelAttribute​​注解或者参数非简单类型,则返回true。

@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

② ServletModelAttributeMethodProcessor的几个方法

在正式分析参数解析前,我们先看一下ServletModelAttributeMethodProcessor的几个方法。其提供了createAttribute、getRequestValueForAttribute、getUriTemplateVariables、createAttributeFromRequestValue、bindRequestParameters以及resolveConstructorArgument六个方法。

① createAttribute

方法如下所示,首先从从​​URI Template Variables​​​解析​​attributeName​​,如果得不到value则从request中解析。如果得到的value不为null,则尝试获取attribute(需要进行类型转换)。如果得到的value为null,则委托给基类处理。

@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {

String value = getRequestValueForAttribute(attributeName, request);
if (value != null) {
Object attribute = createAttributeFromRequestValue(
value, attributeName, parameter, binderFactory, request);
if (attribute != null) {
return attribute;
}
}

return super.createAttribute(attributeName, parameter, binderFactory, request);
}

该方法是一个模板方法,分别调用了​​getRequestValueForAttribute​​​、​​createAttributeFromRequestValue​​​以及父类的​​createAttribute​​方法。

② getRequestValueForAttribute

@Nullable
protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
Map<String, String> variables = getUriTemplateVariables(request);
String variableValue = variables.get(attributeName);
if (StringUtils.hasText(variableValue)) {
return variableValue;
}
String parameterValue = request.getParameter(attributeName);
if (StringUtils.hasText(parameterValue)) {
return parameterValue;
}
return null;
}

protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
@SuppressWarnings("unchecked")
Map<String, String> variables = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (variables != null ? variables : Collections.emptyMap());
}

方法如上所示首先从请求域中获取​​HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE​​​属性值​​Map<String, String> variables​​​,然后尝试从variables解析attributeName,如果得到值则返回。如果得不到值则采用基本的​​request.getParameter(attributeName);​​解析值,然后返回值或者null。

String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + “.uriTemplateVariables”

③ createAttributeFromRequestValue

当获取到属性值后,将会创建一个Model Attribute。直白点就是获取一个DataBinder 得到conversionService 然后尝试进行类型转换,将转换后的值(是一个复杂类型哦,比如SysUser)返回或者返回null。

@Nullable
protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,
MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)
throws Exception {

DataBinder binder = binderFactory.createBinder(request, null, attributeName);
ConversionService conversionService = binder.getConversionService();
if (conversionService != null) {
TypeDescriptor source = TypeDescriptor.valueOf(String.class);
TypeDescriptor target = new TypeDescriptor(parameter);
if (conversionService.canConvert(source, target)) {
return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
}
}
return null;
}

④ resolveConstructorArgument

解析构造参数对象,首先调用基类的​​resolveConstructorArgument​​​方法,如果得到value不为null,则返回。然后尝试从servletRequest获取属性​​HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;​​​的值​​Map<String, String> uriVars​​​,然后尝试从​​uriVars​​​中解析​​paramName​​获取值返回。

@Override
@Nullable
public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request)
throws Exception {

Object value = super.resolveConstructorArgument(paramName, paramType, request);
if (value != null) {
return value;
}
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
if (servletRequest != null) {
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
@SuppressWarnings("unchecked")
Map<String, String> uriVars = (Map<String, String>) servletRequest.getAttribute(attr);
return uriVars.get(paramName);
}
return null;
}

⑤ bindRequestParameters

绑定请求参数,这时从请求参数到解析后的目标参数非常重要的一步。如下所示其转换为ServletRequestDataBinder 然后调用​​servletBinder.bind(servletRequest);​​方法进行处理。

@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}

这里我们可以先回顾一下​​ServletRequestDataBinder​​​的继承树示意图
SpringMVC常见组件之HandlerMethodArgumentResolver解析_参数类型_07
关于​​​.ServletRequestDataBinder#bind​​​更多详细过程可以参考博文SpringMVC常见组件之DataBinder数据绑定器分析。

③ 父类核心方法resolveArgument

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 首先校验 mavContainer binderFactory 不能为null
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
//根据目标方法参数获取参数名字
String name = ModelFactory.getNameForParameter(parameter);
// 获取@ModelAttribute注解信息
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
// 目标方法参数对应的值对象
Object attribute = null;
// 数据绑定结果,通常保存Erros
BindingResult bindingResult = null;
// 直接从model中获取
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance--很重要的一步哦
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
else {
attribute = ex.getTarget();
}
bindingResult = ex.getBindingResult();
}
}
//尝试进行数据绑定、类型转换、数据校验
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}

// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);

return attribute;
}

方法解释如下

  • ① 校验mavContainer、binderFactory是否存在;
  • ② 获取参数名字,如果标注了​​@ModelAttribute​​且其value不为null ,那么参数名字为value值;否则按照通用规则获取参数名;
  • ③ 如果ModelAttribute注解不为null,则告诉mavContainer当前对象绑定;
  • ④ 如果mavContainer有当前name(前面确定的参数名字,也就是key),那么从model中获取值对象attribute;
  • ⑤ !④否则​​createAttribute(name, parameter, binderFactory, webRequest);​​获取一个值对象attribute;
  • ⑥ 如果bindingResult为null(也就是前面没有抛出异常),那么执行如下步骤:
  • ⑦ 创建​​WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name)​​;
  • ⑧ 如果​​binder.getTarget() != null​​​,且未进行过绑定,那么调用​​bindRequestParameters(binder, webRequest);​​为target对象属性赋值;
  • ⑨ 调用​​validateIfApplicable(binder, parameter);​​判断是否需要校验,需要校验则调用校验器进行处理;
  • ⑩ 如果绑定结果有error,且方法参数有Errors类型但是没有紧跟此处的目标参数(也就是非​​SysUser User ,Errors errors​​格式),将会抛出异常BindException
  • (11) 如果参数类型与值对象attribute类型不合适,那么尝试进行转换;
  • (12)​​bindingResultModel​​​ 数据放入​​mavContainer​​​的​​model​​​中。​​bindingResultModel​​​ 数据实例如下所示:
    SpringMVC常见组件之HandlerMethodArgumentResolver解析_ide_08
    下面我们看下这个过程中createAttribute方法。

① createAttribute

我们先回顾一下其子类ServletModelAttributeMethodProcessor的createAttribute方法:

  • ① 尝试从URI Template Variables中获取参数name对应的值value;
  • ② 如果①获取的value不为空则返回,否则尝试从request中获取参数name对应的value;
  • ③ 如果前面获取的value不为null,则尝试创建一个值对象attribute不为null则返回;
  • ④ 调用父类的createAttribute方法
protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

MethodParameter nestedParameter = parameter.nestedIfOptional();
Class<?> clazz = nestedParameter.getNestedParameterType();

Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
if (parameter != nestedParameter) {
attribute = Optional.of(attribute);
}
return attribute;
}

父类的createAttribute方法如上所示,首先获取原始的具体的MethodParameter ,然后获取其Class对象,拿到其构造器后反射实例化一个空对象attribute 。

② validateValueIfApplicable

如果可以,那么进行校验。方法会获取参数的注解,检测是否能够进行校验。如果可以校验,那么会调用当前数据绑定器拥有的校验器进行校验。如果校验结果有异常信息将会放到​​BindingResult​​中。

protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter,
Class<?> targetType, String fieldName, @Nullable Object value) {

for (Annotation ann : parameter.getParameterAnnotations()) {
Object[] validationHints = determineValidationHints(ann);
if (validationHints != null) {
for (Validator validator : binder.getValidators()) {
if (validator instanceof SmartValidator) {
try {
((SmartValidator) validator).validateValue(targetType, fieldName, value,
binder.getBindingResult(), validationHints);
}
catch (IllegalArgumentException ex) {
// No corresponding field on the target class...
}
}
}
break;
}
}
}

那么如何检测当前是否能够进行校验呢?如下所示如果注解是​​Validated​​​类型或者注解名字以​​Valid​​开头,那么就可以进行校验。

@Nullable
private Object[] determineValidationHints(Annotation ann) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
if (hints == null) {
return new Object[0];
}
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
return null;
}

【6】PathVariableMethodArgumentResolver

路径变量解析器,也就是解析URI中的参数值赋予方法参数。如下图所示当url为​​/testUser/1​​​时,这里方法参数id为1。
SpringMVC常见组件之HandlerMethodArgumentResolver解析_参数类型_09

① supportsParameter

方法如下所示,其支持解析两种类型:

  • ​@PathVariable !Map​​​也就是标注了​​@PathVariable​​注解并且类型不是Map;
  • ​@PathVariable("指定名字") Map map​​​ 也就是说标注了​​@PathVariable​​​注解并且类型是Map且​​@PathVariable​​注解value值不为空
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}

② resolveName

方法如下所示,从request获取key为​​HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE​​属性得到一个Map类型的uriTemplateVars 。然后尝试从uriTemplateVars 解析属性name得到value返回,如果uriTemplateVars 为null则直接返回null。

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

​HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE​​是什么呢?

​String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";​​​。如下图所示:
SpringMVC常见组件之HandlerMethodArgumentResolver解析_参数解析器_10

什么时候将URL上的路径变量值放到请求中的呢?有两个地方(其实就是针对两大类HandlerMapping):

第一个地方

AbstractUrlHandlerMapping.lookupHandler() 
-->AbstractUrlHandlerMapping.buildPathExposingHandler
-->chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
-->preHandle()方法
-->exposeUriTemplateVariables(this.uriTemplateVariables, request);
-->request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);

第二个地方是

AbstractHandlerMethodMapping.lookupHandlerMethod()
->RequestMappingInfoHandlerMapping.handleMatch()
>RequestMappingInfoHandlerMapping.extractMatchDetails()
>request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);

③ PathVariableMapMethodArgumentResolver

我们顺带看一下其姊妹类PathVariableMapMethodArgumentResolver。如下所示其解析​​@PathVariable Map​​也就是说标注了@PathVariable注解,注解value为空,参数类型为Map。

@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}

其解析值就更简单了,拿到uriTemplateVars后直接根据是否为空选择返回​​new LinkedHashMap<>(uriTemplateVars)​​​或者​​Collections.emptyMap();​

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

@SuppressWarnings("unchecked")
Map<String, String> uriTemplateVars =
(Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

if (!CollectionUtils.isEmpty(uriTemplateVars)) {
return new LinkedHashMap<>(uriTemplateVars);
}
else {
return Collections.emptyMap();
}
}