关于spring:深入分析-SpringMVC-参数解析器

5次阅读

共计 9869 个字符,预计需要花费 25 分钟才能阅读完成。

后面和大家聊了自定义 SpringMVC 参数解析器,同时咱们也剖析了几个比较简单的参数解析器,置信大家对于 SpringMVC 中的参数解析器应该曾经有了肯定的理解,如果还没看过的小伙伴能够先看看:SpringBoot 中如何自定义参数解析器?。

不过我置信很多小伙伴真正纳闷的是像上面这种接口,参数是怎么解析的:

@GetMapping("/hello2")
public void hello2(String name) {System.out.println("name =" + name);
}

抑或者像上面这种接口,参数是怎么解析的:

@GetMapping("/hello/{id}")
public void hello3(@PathVariable Long id) {System.out.println("id =" + id);
}

这是咱们日常中最常见的参数定义形式,置信很多小伙伴对此很感兴趣。因为这块波及到一个十分宏大的类 AbstractNamedValueMethodArgumentResolver,因而这里我独自写了一篇文章来和大家分享这个问题。

在正式分享之前,咱们先来整体看看参数解析器都有哪些。

1. 参数解析器

HandlerMethodArgumentResolver 就是咱们口口声声说的参数解析器,它的实现类还是蛮多的,因为每一种类型的参数都对应了一个参数解析器:

为了了解不便,咱们能够将这些参数解析器分为四大类:

  • xxxMethodArgumentResolver:这就是一个一般的参数解析器。
  • xxxMethodProcessor:不仅能够当作参数解析器,还能够解决对应类型的返回值。
  • xxxAdapter:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。
  • HandlerMethodArgumentResolverComposite:这个看名字就晓得是一个组合解析器,它是一个代理,具体代理其余干活的那些参数解析器。

大抵上能够分为这四类,其中最重要的当然就是前两种了。

2. 参数解析器概览

接下来咱们来先来大略看看这些参数解析器别离都是用来干什么的。

MapMethodProcessor

这个用来解决 Map/ModelMap 类型的参数,解析实现后返回 model。

PathVariableMethodArgumentResolver

这个用来解决应用了 @PathVariable 注解并且参数类型不为 Map 的参数,参数类型为 Map 则应用 PathVariableMapMethodArgumentResolver 来解决。

PathVariableMapMethodArgumentResolver

见上。

ErrorsMethodArgumentResolver

这个用来解决 Error 参数,例如咱们做参数校验时的 BindingResult。

AbstractNamedValueMethodArgumentResolver

这个用来解决 key/value 类型的参数,如申请头参数、应用了 @PathVariable 注解的参数以及 Cookie 等。

RequestHeaderMethodArgumentResolver

这个用来解决应用了 @RequestHeader 注解,并且参数类型不是 Map 的参数(参数类型是 Map 的应用 RequestHeaderMapMethodArgumentResolver)。

RequestHeaderMapMethodArgumentResolver

见上。

RequestAttributeMethodArgumentResolver

这个用来解决应用了 @RequestAttribute 注解的参数。

RequestParamMethodArgumentResolver

这个性能就比拟广了。应用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有应用任何注解的根本类型(Long、Integer)以及 String 等,都应用该参数解析器解决。须要留神的是,如果 @RequestParam 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由 RequestParamMapMethodArgumentResolver 实现。

RequestParamMapMethodArgumentResolver

见上。

AbstractCookieValueMethodArgumentResolver

这个是一个父类,解决应用了 @CookieValue 注解的参数。

ServletCookieValueMethodArgumentResolver

这个解决应用了 @CookieValue 注解的参数。

MatrixVariableMethodArgumentResolver

这个解决应用了 @MatrixVariable 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则应用 MatrixVariableMapMethodArgumentResolver 来解决。

MatrixVariableMapMethodArgumentResolver

见上。

SessionAttributeMethodArgumentResolver

这个用来解决应用了 @SessionAttribute 注解的参数。

ExpressionValueMethodArgumentResolver

这个用来解决应用了 @Value 注解的参数。

ServletResponseMethodArgumentResolver

这个用来解决 ServletResponse、OutputStream 以及 Writer 类型的参数。

ModelMethodProcessor

这个用来解决 Model 类型参数,并返回 model。

ModelAttributeMethodProcessor

这个用来解决应用了 @ModelAttribute 注解的参数。

SessionStatusMethodArgumentResolver

这个用来解决 SessionStatus 类型的参数。

PrincipalMethodArgumentResolver

这个用来解决 Principal 类型参数,这个松哥在后面的文章中和大家介绍过了([SpringBoot 中如何自定义参数解析器?]())。

AbstractMessageConverterMethodArgumentResolver

这是一个父类,当应用 HttpMessageConverter 解析 requestbody 类型参数时,相干的解决类都会继承自它。

RequestPartMethodArgumentResolver

这个用来解决应用了 @RequestPart 注解、MultipartFile 以及 Part 类型的参数。

AbstractMessageConverterMethodProcessor

这是一个工具类,不承当参数解析工作。

RequestResponseBodyMethodProcessor

这个用来解决增加了 @RequestBody 注解的参数。

HttpEntityMethodProcessor

这个用来解决 HttpEntity 和 RequestEntity 类型的参数。

ContinuationHandlerMethodArgumentResolver

AbstractWebArgumentResolverAdapter

这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。

ServletWebArgumentResolverAdapter

这个给父类提供 request。

UriComponentsBuilderMethodArgumentResolver

这个用来解决 UriComponentsBuilder 类型的参数。

ServletRequestMethodArgumentResolver

这个用来解决 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数。

HandlerMethodArgumentResolverComposite

这个看名字就晓得是一个组合解析器,它是一个代理,具体代理其余干活的那些参数解析器。

RedirectAttributesMethodArgumentResolver

这个用来解决 RedirectAttributes 类型的参数,RedirectAttributes 松哥在之前的文章中和大家介绍过:SpringMVC 中的参数还能这么传递?涨姿态了!。

好了,各个参数解析器的大抵性能就给大家介绍完了,接下来咱们抉择其中一种,来具体说说它的源码。

3.AbstractNamedValueMethodArgumentResolver

AbstractNamedValueMethodArgumentResolver 是一个抽象类,一些键值对类型的参数解析器都是通过继承它实现的,它里边定义了很多这些键值对类型参数解析器的公共操作。

AbstractNamedValueMethodArgumentResolver 中也是利用了很多模版模式,例如它没有实现 supportsParameter 办法,该办法的具体实现在不同的子类中,resolveArgument 办法它倒是实现了,咱们一起来看下:

@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;
}
  1. 首先依据以后申请获取一个 NamedValueInfo 对象,这个对象中保留了参数的三个属性:参数名、参数是否必须以及参数默认值。具体的获取过程就是先去缓存中拿,缓存中如果有,就间接返回,缓存中如果没有,则调用 createNamedValueInfo 办法去创立,将创立后果缓存起来并返回。createNamedValueInfo 办法是一个模版办法,具体的实现在子类中。
  2. 接下来解决 Optional 类型参数。
  3. resolveEmbeddedValuesAndExpressions 办法是为了解决注解中应用了 SpEL 表达式的状况,例如如下接口:
@GetMapping("/hello2")
public void hello2(@RequestParam(value = "${aa.bb}") String name) {System.out.println("name =" + name);
}

参数名应用了表达式,那么 resolveEmbeddedValuesAndExpressions 办法的目标就是解析出表达式的值,如果没用到表达式,那么该办法会将原参数一成不变返回。

  1. 接下来调用 resolveName 办法解析出参数的具体值,这个办法也是一个模版办法,具体的实现在子类中。
  2. 如果获取到的参数值为 null,先去看注解中有没有默认值,而后再去看参数值是否是必须的,如果是,则抛异样进去,否则就设置为 null 即可。
  3. 如果解析进去的参数值为空字符串 "",则也去 resolveEmbeddedValuesAndExpressions 办法中走一遭。
  4. 最初则是 WebDataBinder 的解决,解决一些全局参数的问题,WebDataBinder 松哥在之前的文章中也有介绍过,传送门:@ControllerAdvice 的三种应用场景。

大抵的流程就是这样。

在这个流程中,咱们看到次要有如下两个办法是在子类中实现的:

  • createNamedValueInfo
  • resolveName

在加上 supportsParameter 办法,子类中一共有三个办法须要咱们重点剖析。

那么接下来咱们就以 RequestParamMethodArgumentResolver 为例,来看下这三个办法。

4.RequestParamMethodArgumentResolver

4.1 supportsParameter

@Override
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;}
    }
}
public static boolean isSimpleProperty(Class<?> type) {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));
}

从 supportsParameter 办法中能够十分不便的看出反对的参数类型:

  1. 首先参数如果有 @RequestParam 注解的话,则分两种状况:参数类型如果是 Map,则 @RequestParam 注解必须配置 name 属性,否则不反对;如果参数类型不是 Map,则间接返回 true,示意总是反对(想想本人平时应用的时候是不是这样)。
  2. 参数如果含有 @RequestPart 注解,则不反对。
  3. 查看下是不是文件上传申请,如果是,返回 true 示意反对。
  4. 如果后面都没能返回,则应用默认的解决方案,判断是不是简略类型,次要就是 Void、枚举、字符串、数字、日期等等。

这块代码其实很简略,反对谁不反对谁,高深莫测。

4.2 createNamedValueInfo

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
    return (ann != null ? new RequestParamNamedValueInfo(ann) : new 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());
    }
}

获取注解,读取注解中的属性,结构 RequestParamNamedValueInfo 对象返回。

4.3 resolveName

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

这个办法思路也比拟清晰:

  1. 后面两个 if 次要是为了解决文件上传申请。
  2. 如果不是文件上传申请,则调用 request.getParameterValues 办法取出参数返回即可。

整个过程还是比拟 easy 的。小伙伴们能够在此基础之上自行剖析 PathVariableMethodArgumentResolver 的原理,也很容易。

5. 小结

明天次要和小伙伴们梳理了 SpringMVC 参数解析器的整个体系,对于这些解析器在何时被配置,在何时被调用,松哥在前面的文章中会和大家持续剖析。好啦,明天就说这么多。

正文完
 0