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

小架构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.dtcms.com/a/290987.html

相关文章:

  • 昇腾310P软件安装说明
  • java和ptyhon对比
  • 网络编程 示例
  • A316-HF-DAC-V1:专业USB HiFi音频解码器评估板技术解析
  • Linux 文件操作详解:结构、系统调用、权限与实践
  • C语言-字符串数组
  • DL00691-基于深度学习的轴承表面缺陷目标检测含源码python
  • 【STM32】485接口原理
  • Jmeter如何做接口测试?
  • soft_err错误
  • 【C语言进阶】结构体练习:通讯录
  • OCR 赋能发票管理系统:守护医疗票据合规,让管理更智能
  • Milvus:开源向量数据库的初识
  • 第17章 基于AB实验的增长实践——沉淀想法:实验记忆
  • 基于deepseek的LORA微调
  • react-window 大数据列表和表格数据渲染组件之虚拟滚动
  • Neo4j graph database
  • 剖析Sully.ai:革新医疗领域的AI助手功能启示
  • 20. TaskExecutor与ResourceManager心跳
  • HTML前端颜色渐变动画完整指南
  • 处理excel/wps表格中数值格式的警告的工具和脚本
  • Python 进阶(五): Excel 基本操作
  • Android系统更新实现--OTA空中升级
  • Selenium 处理表单、弹窗与文件上传:从基础到实战
  • 杰发科技AC7840——硬件crc使用
  • 7月21号打卡
  • 破解哈希极化:基于主动路径规划的智算网络负载均衡方案
  • Shell 编程基础入门从认识到实战
  • OSS文件上传(三):断点续传
  • C语言关键字深度解析:从入门到精通