当前位置: 首页 > backend >正文

小架构step系列21:参数和返回值的匹配

1 概述

从前面看到参数是通过HandlerMethodArgumentResolver进行匹配的,参数有各种各样的形式,比如带注解的、自定义类、基本类型、甚至ServletReqest之类的。返回值则是通过ReturnValueHandler来处理的,返回值也有比较多类型,比如String、Void、Model、自定义类等,如果不是前后端分离的,还可能带View之类的。本文了解一下参数和返回值的匹配原理。

2 参数匹配

2.1 参数匹配原理

遍历所有HandlerMethodArgumentResolver,如果Resolver的supportsParameter()接口返回值为true,则匹配此Resolver,不再看下面其它的Resolver。

// 源码位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {// 遍历所有argumentResolver,调用其supportsParameter()方法,如果返回值为true则匹配上此argumentResolverfor (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}

默认有27个Resolver:

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());resolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());if (KotlinDetector.isKotlinPresent()) {resolvers.add(new ContinuationHandlerMethodArgumentResolver());}// 注意自定义的ArgumentResolver位置if (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}resolvers.add(new PrincipalMethodArgumentResolver());resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor

注意:RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个。

2.2 常用的MethodArgumentResolver

下面列一下几个常用的MethodArgumentResolver:

// 源码位置:org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {// 参数前加了@RequestParam注解时,用此MethodArgumentResolver处理if (parameter.hasParameterAnnotation(RequestParam.class)) {// 如果参数是Map类型,则还需要在@RequestParam注解中指定映射字段名if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));}else {return true;}}else {// 参数前加了@RequestPart注解时,不用此MethodArgumentResolver处理if (parameter.hasParameterAnnotation(RequestPart.class)) {return false;}parameter = parameter.nestedIfOptional();// 参数类型为MultipartFile或Part时(可以List里放这些类型),用此MethodArgumentResolver处理if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {return true;}else if (this.useDefaultResolution) {// SpringMVC准备了两个RequestParamMethodArgumentResolver,一个useDefaultResolution=false,另外一个useDefaultResolution=true(倒数第二个Resolver)// 如果useDefaultResolution=true,则普通类型如Void、int/byte/long等基础类型可以用此MethodArgumentResolver处理// 详细类型参考BeanUtils.isSimpleProperty()方法return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());}else {return false;}}
}
// 源码位置:org.springframework.beans.BeanUtils
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));
}// 源码位置:org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {// 参数前加了@RequestParam注解、且参数类型为Map类型、也没有在@RequestParam注解指定字段映射名时,用此MethodArgumentResolver处理RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&!StringUtils.hasText(requestParam.name()));
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {// 参数前没有加@PathVariable注解,不用此MethodArgumentResolver处理if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;}// 参数前加了@PathVariable注解,且参数为Map类型时,需要在注解指定字段映射名,才用此MethodArgumentResolver处理if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);return (pathVariable != null && StringUtils.hasText(pathVariable.value()));}return true;
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsParameter
public boolean supportsParameter(MethodParameter parameter) {// 参数前加了@RequestBody注解,用此MethodArgumentResolver处理return parameter.hasParameterAnnotation(RequestBody.class);
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际由父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor提供supportsParameter()方法
public boolean supportsParameter(MethodParameter parameter) {// 两种情况用此MethodArgumentResolver处理:// 1. 参数前加了@ModelAttribute注解;// 2. 没有加参数注解,参数未非普通类型(普通类型参考上面BeanUtils.isSimpleProperty()的实现);// 注:有两个ServletModelAttributeMethodProcessor,一个annotationNotRequired=false,另一个annotationNotRequired=true(倒数第一个Resolver)return (parameter.hasParameterAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {// 参数是Servlet有关的类型,用此MethodArgumentResolver处理Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);
}
从上面可以看到如果有注解则优先匹配注解(如@RequestParam、@RequestPart、@RequestBody)。
在没有注解的时候,主要由RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor、ServletRequestMethodArgumentResolver、自定义Resolver处理。
  • 在Resolver列表的排序是:ServletRequestMethodArgumentResolver、自定义Resolver、RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor。
  • RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个,它们各自的第二个都置标记为true,才会处理没有注解的情况。
  • ServletRequestMethodArgumentResolver匹配的是Servlet有关的类型,如ServletRequest、InputStream等;
  • RequestParamMethodArgumentResolver匹配的是普通类型,如Void、Number、int/String基本类型等;
  • ServletModelAttributeMethodProcessor匹配的是非普通类型,也就是上面没匹配到的所有类型;

2.3 应用

在请求的时候,一般请求参数有两种形式:
  • Form表单形式:一个字段一个字段分开的方式,字段可以是对象结构,大部分ArgumentResolver都是用来匹配这种形式的。
  • JSON数据形式:所有数据封装到一个JSON对象当中,要匹配这种形式的参数,参数前必须加@RequestBody注解,由RequestResponseBodyMethodProcessor匹配。
如果强制请求参数用JSON数据形式,又不希望必须在参数前加@RequestBody注解(可能忘掉),则需要自行加一个自定义的ArgumentResolver,把所有情况都当JSON数据处理。

3 返回值匹配

3.1 返回值匹配原理

遍历所有ReturnValueHandler,如果Resolver的supportsReturnType()接口返回值为true,则匹配此Handler,不再看下面其它的Handler。
// 源码位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}// 遍历所有支持的ReturnValueHandler,调用supportsReturnType()接口,如果返回值为true,则选中此ReturnValueHandlerif (handler.supportsReturnType(returnType)) {return handler;}}return null;
}

默认提供了15个ReturnValueHandler:

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);handlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));handlers.add(new StreamingResponseBodyReturnValueHandler());handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new HttpHeadersReturnValueHandler());handlers.add(new CallableMethodReturnValueHandler());handlers.add(new DeferredResultMethodReturnValueHandler());handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));handlers.add(new ServletModelAttributeMethodProcessor(false));handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// 注意自定义的ReturnValueHandler的位置if (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));}else {handlers.add(new ServletModelAttributeMethodProcessor(true));}return handlers;
}org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor

3.2 常用的ReturnValueHandler

下面列一下几个常用的ReturnValueHandler:

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {// 返回值类型为ModelAndView,用此ReturnValueHandlerreturn ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}// 源码位置:org.springframework.web.method.annotation.ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {// 返回值类型为Model,用此ReturnValueHandlerreturn Model.class.isAssignableFrom(parameter.getParameterType());
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {// 返回值对象对应的类指定了@ResponseBody注解,或者返回值有指定@ResponseBody注解,用此ReturnValueHandlerreturn (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {// 返回值类型为View,用此ReturnValueHandlerreturn View.class.isAssignableFrom(returnType.getParameterType());
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();// 如果没有返回值或者返回值是字符串类型,则用此ReturnValueHandlerreturn (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际用父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor的实现
public boolean supportsReturnType(MethodParameter returnType) {// 返回值类型为ModelAttribute,或者在annotationNotRequired=true的时候不是普通类型// ServletModelAttributeMethodProcessor有两个,第二个(在列表最后一个)annotationNotRequired=truereturn (returnType.hasMethodAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}
ReturnValueHandler的匹配逻辑是比较清晰的,大部分都是按返回值类型,还有一种是按@ResponseBody、@ModelAttribute等注解。
  • 对于普通类型只能匹配Void和CharSequence,其它的如Number、Enum等是匹配不到的,会抛IllegalArgumentException("Unknown return value type: ")异常。
  • 如果是自定义的类型,则由ServletModelAttributeMethodProcessor来匹配,注意它也有两个,只有第二个才是匹配自定义类型的。
  • 也可以自定义ReturnValueHandler来匹配其它类型,此时会排在第二个ServletModelAttributeMethodProcessor前面,其它Handler的后面。

3.3 应用

返回值一般有带视图View和纯数据的两种方式:
  • 带视图View的方式是在服务器端渲染界面的方式,返回值类型指定为ModelAndView、View、字符串等类型;
  • 纯数据一般用JSON格式,此时需要由ServletModelAttributeMethodProcessor处理,即指定@ModelAttribute注解或自定义类型;

4 架构一小步

在前后端分离的情况下,可以选择两种规范:
1、数据格式方面
方案一:参数和返回值都强制统一用JSON数据格式;好处是统一,缺点是需要自定义一下MethodArgumentResolver;
方案二:参数用Form表单格式,返回值用JSON数据格式;好处是不需要自定义MethodArgumentResolver,缺点是参数和返回值需要分开理解,处理方式不统一;
2、在使用注解方面
方案一:所有参数不用注解;好处是不会出现漏注解的情况,缺点是需要JSON格式数据的适配;
方案二:所有参数都明确注解;好处是注解清晰不用自定义,缺点是容易漏标注解;
http://www.xdnf.cn/news/15931.html

相关文章:

  • FastAPI 中,数据库模型(通常使用 SQLAlchemy 定义)和接口模型(使用 Pydantic 定义的 schemas)的差异
  • 【智能协同云图库】智能协同云图库第二期:基于腾讯云 COS 对象存储—开发图片各功能模块
  • SQLite以及Room框架的学习:用SQLite给新闻app加上更完善的登录注册功能
  • 蜂窝物联网模组市场新展望:中国企业继续保持最强竞争力
  • 进阶向:基于Python的电脑硬件监控工具(GUI + 系统信息采集)
  • 51c大模型~合集157
  • 138. Java 泛型 - 通配符捕获Helper程序方法:类型安全解决方案
  • 二维码扫描登录流程详解
  • 【设计模式】迭代器模式 (游标(Cursor)模式)
  • JavaEE初阶第十期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(八)
  • WinUI3开发_Frame用法
  • 服务器设置国外IP无法访问对防御攻击有用吗?
  • 一文详解REST风格
  • 一个适合MCU的分级菜单框架
  • .NET SDK 9.0.200引入对SLNX解决方案文件的支持
  • django filter按两个属性 去重
  • Linux——自制shell命令行解释器
  • 【LeetCode 热题 100】208. 实现 Trie (前缀树)
  • 剖析Sully.ai:革新医疗领域的AI助手功能启示
  • ssms(SQL 查询编辑器) 添加快捷键 Ctrl+D(功能等于Ctrl+C + Ctrl+V),一步到位
  • Bun v1.2.19发布,node_modules隔离,sql比node快6倍
  • Kotlin 高阶函数初步学习
  • Laravel 系统版本查看及artisan管理员密码找回方法针对各个版本通用方法及原理-优雅草卓伊凡
  • 信息学奥赛一本通 1576:【例 2】选课 | 洛谷 P2014 [CTSC1997] 选课
  • 子网划分核心原理 (网络原理1)
  • [学习] Hilbert变换:从数学原理到物理意义的深度解析与仿真实验(完整实验代码)
  • 《通信原理》学习笔记——第五章
  • Spring 源码阅读(二) 核心概念解析 ApplicationContext、类型转化
  • 【PyTorch】图像二分类项目
  • 【JS逆向基础】数据库之mysql