SpringMVC常见组件之HandlerMethodArgumentResolver解析-1

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringMVC常见组件之HandlerMethodArgumentResolver解析-1

在前面我们分析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;
}


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

如下所示,有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接口,可以处理返回结果。



3.png

4.png

5.png


@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。


023658f1038e439da55da1b24ea2b7d0.png

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

// 获取参数的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如下所示

99e768ac5e5747e08bdf7f7cb46526fe.png

如果方法参数为String testSimple(Integer age),那么namedValueInfo如下所示


90b84ce8b3164ababd23701b6b6fcc5b.png

如果方法参数为String testPath(@PathVariable Integer id),那么namedValueInfo如下所示

【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 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。

SpringMVC常见组件之HandlerMethodArgumentResolver解析-2https://developer.aliyun.com/article/1382042

目录
相关文章
|
21小时前
|
Java 开发者 Spring
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
6 2
|
23小时前
|
存储 缓存 Java
深入解析Spring框架中的ReflectionUtils
深入解析Spring框架中的ReflectionUtils
8 1
|
2天前
|
Java 数据库连接 Spring
Spring 整合 MyBatis 底层源码解析
Spring 整合 MyBatis 底层源码解析
|
2天前
|
前端开发 安全 Java
Spring EL表达式:概念、特性与应用深入解析
Spring EL表达式:概念、特性与应用深入解析
|
2天前
|
调度
SpringMVC----执行流程+底层解析
SpringMVC----执行流程+底层解析
|
1天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
1天前
|
Java 容器 Spring
Spring5源码解析5-ConfigurationClassPostProcessor (上)
Spring5源码解析5-ConfigurationClassPostProcessor (上)
|
1天前
|
Java 测试技术 应用服务中间件
Spring 与 Spring Boot:深入解析
Spring 与 Spring Boot:深入解析
8 0
|
1天前
|
XML Java 开发者
“掌握Spring IoC和AOP:30道面试必备问题解析!“
“掌握Spring IoC和AOP:30道面试必备问题解析!“
9 0
|
1天前
|
XML Java 数据格式
SpringMVC的XML配置解析-spring18
SpringMVC的XML配置解析-spring18

热门文章

最新文章

推荐镜像

更多