前面和大家聊了自定义 SpringMVC 参数解析器,同时我们也分析了几个比较简单的参数解析器,相信大家对于 SpringMVC 中的参数解析器应该已经有了一定的了解,如果还没看过的小伙伴可以先看看:SpringBoot 中如何自定义参数解析器?。
成都创新互联公司主要从事成都网站设计、成都网站建设、网页设计、企业做网站、公司建网站等业务。立足成都服务扬州,10多年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108
不过我相信很多小伙伴真正疑惑的是像下面这种接口,参数是怎么解析的:
- @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,因此这里我单独写了一篇文章来和大家分享这个问题。
在正式分享之前,我们先来整体看看参数解析器都有哪些。
HandlerMethodArgumentResolver 就是我们口口声声说的参数解析器,它的实现类还是蛮多的,因为每一种类型的参数都对应了一个参数解析器:
为了理解方便,我们可以将这些参数解析器分为四大类:
大致上可以分为这四类,其中最重要的当然就是前两种了。
接下来我们来先来大概看看这些参数解析器分别都是用来干什么的。
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 中的参数还能这么传递?涨姿势了!。
好了,各个参数解析器的大致功能就给大家介绍完了,接下来我们选择其中一种,来具体说说它的源码。
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;
- }
- @GetMapping("/hello2")
- public void hello2(@RequestParam(value = "${aa.bb}") String name) {
- System.out.println("name = " + name);
- }
参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。4. 接下来调用 resolveName 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。5. 如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。6. 如果解析出来的参数值为空字符串 "",则也去 resolveEmbeddedValuesAndExpressions 方法中走一遭。7. 最后则是 WebDataBinder 的处理,解决一些全局参数的问题,WebDataBinder 松哥在之前的文章中也有介绍过,传送门:@ControllerAdvice 的三种使用场景。
大致的流程就是这样。
在这个流程中,我们看到主要有如下两个方法是在子类中实现的:
在加上 supportsParameter 方法,子类中一共有三个方法需要我们重点分析。
那么接下来我们就以 RequestParamMethodArgumentResolver 为例,来看下这三个方法。
- @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 方法中可以非常方便的看出支持的参数类型:
- @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 对象返回。
- @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
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;
- }
这个方法思路也比较清晰:
整个过程还是比较 easy 的。小伙伴们可以在此基础之上自行分析 PathVariableMethodArgumentResolver 的原理,也很容易。
今天主要和小伙伴们梳理了 SpringMVC 参数解析器的整个体系,关于这些解析器在何时被配置,在何时被调用,松哥在后面的文章中会和大家继续分析。好啦,今天就说这么多。
本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。
分享文章:深入分析 SpringMVC 参数解析器
文章网址:http://www.csdahua.cn/qtweb/news15/482815.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网