《学会 SpringMVC 系列 · 剖析入参处理》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 入参处理
      • 学前准备与回顾
      • @RequestBody 入参处理
      • @RequestParam 入参处理
    • 自定义用法
      • 自定义入参解析器
      • 自定义入参转换器
    • 总结陈词

CSDN.gif

写在前面的话

通过上一篇博文《学会 SpringMVC 系列 · 剖析篇(上)》的学习,大致了解了SpringMVC请求流程的代码走向。由于篇幅所限,没有介绍的十分详尽,接下来几篇博文,将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《学会 SpringMVC 系列 · 剖析初始化》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


入参处理

学前准备与回顾

本篇 SpringMVC 源码分析系列文章,继续使用 《搭建拥有数据交互的 SpringBoot 》博文搭建的 SpringBoot3.x 项目为基础,以此学习相关源码,对应 SpringMVC 版本为 6.1.11。
通过《学会 SpringMVC 系列 · 剖析篇(上)》的分析,我们先总结回顾一下。

【一次请求的主链路节点】
DispatcherServlet#doDispatch(入口方法)
DispatcherServlet#getHandler(根据path找到对应的HandlerExecutionChain
DispatcherServlet#getHandlerAdapter(根据handle找到对应的HandlerAdapter
HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)
AbstractHandlerMethodAdapter#handle(核心逻辑)
HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)

【核心handle方法的主链路节点】
RequestMappingHandlerAdapter#handleInternal(入口方法)
RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)
ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)
InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)
InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)
InvocableHandlerMethod#doInvoke(实际执行,3.1.2)
HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)


@RequestBody 入参处理

先以最常见的@RequestBody为示例,展开介绍。

@ResponseBody
@RequestMapping("/studyJson")
public ZyTeacherInfo studyJson(@RequestBody ZyTeacherInfo teacherInfo) {teacherInfo.setTeaName("战神");return teacherInfo;
}

【运行流程】
1、先拿到方法的形参列表,根据形参长度创建一个空数组,用来存储后续处理完的最终参数。

MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];

image.png
2、进入HandlerMethodArgumentResolverComposite#getArgumentResolver,先判断缓存是否存在,不存在就遍历参数解析器列表,依次判断其supportsParameter方法是否匹配,匹配则返回该解析器。
image.png
image.png
3、由于是@RequestBody入参,会找到 RequestResponseBodyMethodProcessor,它的判定比较简单,就是判断是否包含RequestBody注解。匹配成功则加入缓存,然后返回该处理器。
image.png
image.png
4、接着进入HandlerMethodArgumentResolverComposite#resolveArgument,会再调用getArgumentResolver取一次,这时候肯定是从缓存拿到的了(不清楚为什么取两次)。
总之拿到了 RequestResponseBodyMethodProcessor,紧接着执行它的resolveArgument方法。
image.png
image.png
5、再调用HandlerMethodArgumentResolverComposite#readWithMessageConverters,看方法名字就知道这是利用参数转换器进行实际的消息处理。
image.png
6、这边获取转换器列表,遍历调用canRead方法,看是否满足,这边找到了FastJsonHttpMessageConverter,然后调用其read方法利用JSON.parseObject方法转换为对象。
image.png
image.png
image.png
7、数据拿到了,流程开始返回,返回到getMethodArgumentValues这边,可以看到拿到的已经是处理后的对象了。
再就是返回到InvocableHandlerMethod#invokeForRequest,准备执行 doInvoke(args)
到此,入参流程基本告一段落。
image.png
image.png

【总结一下,链路流程】
InvocableHandlerMethod#getMethodArgumentValues(入参处理方法的入口)
HandlerMethodArgumentResolverComposite#getArgumentResolver(找合适的参数解析器)
RequestResponseBodyMethodProcessor#supportsParameter(匹配入参解析器)
RequestResponseBodyMethodProcessor#resolveArgument(执行入参处理器)
RequestResponseBodyMethodProcessor#readWithMessageConverters(找入参转换器)
AbstractHttpMessageConverter#canRead(匹配入参转换器)
FastJsonHttpMessageConverter#read(执行入参转换器实际逻辑)


@RequestParam 入参处理

先以最常见的@RequestParam为示例,展开介绍。

@ResponseBody@RequestMapping("/study")public String study(@RequestParam("msg") String name) {String msg = "Hello, Spring Boot 3!" + name;log.warn("study方法内的实际逻辑:" + msg);return msg;}

【运行流程】
1、先拿到方法的形参列表,根据形参长度创建一个空数组,用来存储后续处理完的最终参数。

MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];

image.png
2、进入HandlerMethodArgumentResolverComposite#getArgumentResolver,先判断缓存是否存在,不存在就遍历参数解析器列表,依次判断其supportsParameter方法是否匹配,匹配则返回该解析器。

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

image.png
3、这里由于入参是@RequestParam,所以匹配上RequestParamArgumentResolver,它的supportsParameter方法很简单,就不贴了,看名字也像。
image.png
4、接下来再进入HandlerMethodArgumentResolverComposite#resolveArgument,这里再进依次第二步的getArgumentResolver方法,很明显,这次从缓存获取(不明白为什么取两次)。
获取到入参处理器不为空的时候,就执行它的resolveArgument方法。

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

image.png
image.png
5、开始执行RequestParamArgumentResolver#resolveArgument,这里执行的是它父类AbstractNamedValueMethodArgumentResolver的resolveArgument方法,再进入它自身的resolveName方法。
这里可以看到核心获取逻辑其实就是 request.getParameterValues(name),很简单。
image.png
6、取到之后直接返回了,返回到InvocableHandlerMethod#invokeForRequest,准备执行 doInvoke(args)
可以看到,和@RequestBody不同,没有再去找什么入参转换器。
image.png

【总结一下,运行流程】
InvocableHandlerMethod#getMethodArgumentValues(入参处理方法的入口)
HandlerMethodArgumentResolverComposite#resolveArgument(找入参解析器)
RequestParamArgumentResolver#supportsParameter(匹配到入参解析器)
AbstractNamedValueMethodArgumentResolver#resolveArgument(调父类解析方法)
RequestParamArgumentResolver#resolveName(调自身解析方法,完成解析动作)


自定义用法

前面介绍了两种入参解析流程,在开展自定义逻辑之前,容我们先整理一下。
以 @RequestBody 和 @ResponseBody 的方法为例,可以挖掘出一些关键点:

  • RequestResponseBodyMethodProcessor#readWithMessageConverters(入参解析)
  • FastJsonHttpMessageConverter#read(入参转换)
  • RequestResponseBodyMethodProcessor#handleReturnValue(出参解析)
  • AbstractMessageConverterMethodProcessor#writeWithMessageConverters(出参解析)
  • FastJsonHttpMessageConverter#write(出参转换)

这几个步骤就是我们后续可以针对入参和出参处理部分,添加自定义逻辑的地方。
本篇主要分析入参,接下来介绍一下入参解析器和入参转换器的自定义。


自定义入参解析器

关键词:ArgumentResolvers、RequestResponseBodyMethodProcessor

【技术说明】
1、ArgumentResolvers 主要负责将 HTTP 请求中的参数解析为 Controller 方法的参数。
2、当客户端发送请求时,Spring MVC 将请求中的参数解析为方法的参数。ArgumentResolvers 允许你自定义解析规则,以支持各种类型的参数,包括基本类型、复杂对象、路径变量等。例如,@RequestParam、@PathVariable 注解都是通过参数解析器来解析请求中的参数的。
3、要实现自定义参数解析,只要实现 HandlerMethodArgumentResolver 接口,并且实现 supportsParameter 和resolveArgument方法,然后配置类中添加一下即可。

【实现示例】
Step1、自定义一个 MyHandlerMethodArgumentResolver,实现 HandlerMethodArgumentResolver 接口。

@Slf4j
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {/*** 用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument*/@Overridepublic boolean supportsParameter(MethodParameter parameter) {return Student.class.isAssignableFrom(parameter.getParameterType());}/*** 真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象* 用途:仅仅用于测试,解析请求体内容,比如name#张三,age#20,将内容解析组装成Student对象再返回*/@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {String str = getRequestBody(webRequest);String[] split = str.split(",");String name = split[0].split("#")[1];String age = split[1].split("#")[1];return Student.builder().name(name).age(Integer.parseInt(age)).id(1).build();}/*** 从请求体获取内容* 也可以参考RequestResponseBodyMethodProcessor的读取方式*/private String getRequestBody(NativeWebRequest webRequest) throws IOException {HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);assert request != null;BufferedReader reader = request.getReader();StringBuilder sb = new StringBuilder();char[] buf = new char[1024];int rd;while ((rd = reader.read(buf)) != -1) {sb.append(buf, 0, rd);}return sb.toString();}
}

Step2、再SpringMVC的配置类中,添加该入参解析器:

@Slf4j
public class CustomConfig implements WebMvcConfigurer {/*** 添加入参处理器*/@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new MyHandlerMethodArgumentResolver());}
}

Step3、编写测试类

/*** 测试自定义入参解析器*/
@ResponseBody
@RequestMapping("/studyJsonCustom")
public Student studyJsonCustom(Student student) {student.setEmail("战神");return student;
}

Step4、启动项目,验证一下效果
image.png

【示例的源码分析】
基本流程和前面一致,这边不展开赘述。
可以看到寻找匹配的参数解析器的时候,可选项多了一个。
image.png
image.png
另外,注意的是,如果都没找到符合的,比如用基础类型,会使用RequestParamMethodArgumentResolver,具体不展开。
image.png

【实战补充】
上述示例只是为了帮助理解,真实开发中,更多自定义入参解析器的情况是:
添加自定义注解,并为其指定特定功能,例如添加可以同时兼容form 和 json 的场景、或支持复杂数据自动解析等等。
值得一提的是,解析器可以注册这个,但最终只会生效一个,如果都找不到符合的,都会有兜底的方案,要适当注意一下添加的顺序。


自定义入参转换器

关键词:AbstractHttpMessageConverter

【技术说明】
作用:Message Converters 主要负责将 Controller 方法的返回值转换为 HTTP 响应的内容。
工作原理:当 Controller 方法返回一个对象时,Spring MVC 使用消息转换器将该对象转换为 HTTP 响应体的内容。消息转换器负责将 Java 对象转换为特定的媒体类型,例如 JSON、XML、HTML 等。Spring 提供了各种内置的消息转换器来支持不同的数据格式。
示例:如果你的 Controller 方法返回一个对象,Spring MVC 将根据请求的 Accept 头部信息和返回值类型选择适当的消息转换器,将对象转换为对应的媒体类型。

【示例说明】
由于篇幅受限,这部分内容和其他入参相关实战部分,一起放在下一篇继续介绍,


总结陈词

此篇文章介绍了SpringMVC 入参处理相关的分析,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/391525.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大模型系列】Video-LLaVA(2023.12)

Paper&#xff1a;https://arxiv.org/pdf/2311.10122v2Github&#xff1a;https://github.com/PKU-YuanGroup/Video-LLaVAHuggingface&#xff1a;https://huggingface.co/spaces/LanguageBind/Video-LLaVAAuthor&#xff1a;Bin Lin et al. 北大袁粒团队 文章目录 1 Video-LLa…

各地级市能源消费总量、夜间灯光值数据(2000-2022年)

全国各地级市能源消费总量、夜间灯光值数据&#xff08;2000-2022年&#xff09; 数据年限&#xff1a;2000-2022年 数据格式&#xff1a;excel 数据内容&#xff1a;337个地级市能源消费总量、夜间灯光值数据&#xff0c;包括城市、省份、年份、夜间灯光值&#xff08;总和&am…

基于pytorch的steam游戏评分的线性回归问题分析

前言 相信已经暑假一个月的大家肯定并不陌生上面这个学习软件()&#xff0c;面对琳琅满目的游戏总是让人不知道挑选什么&#xff0c;这时候一个游戏的评分往往便成为了一个玩家选择下载的原因&#xff0c;那么今天我们就来研究研究&#xff0c;steam上一个游戏的种种数据&…

【window10/window11】解决任务管理器有进程无法强制结束情况

以管理员身份启动控制台窗体&#xff0c;然后从任务管理器中查询到你要结束的进程名&#xff0c;然后运行以下命令&#xff08;UniAccessAgent.exe替换成你要结束的进程&#xff09;&#xff1a; wmic process where nameUniAccessAgent.exe delete 此方法可以解决在任务管理…

快速体验LLaMA-Factory 私有化部署和高效微调Llama3模型(曙光超算互联网平台异构加速卡DCU)

序言 本文以 LLaMA-Factory 为例&#xff0c;在超算互联网平台SCNet上使用异构加速卡AI 显存64GB PCIE&#xff0c;私有化部署Llama3模型&#xff0c;并对 Llama3-8B-Instruct 模型进行 LoRA 微调、推理和合并。 快速体验基础版本&#xff0c;请参考另一篇博客&#xff1a;快…

Animate软件基础:在时间轴中标识动画

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

React--》掌握styled-components重塑React样式管理

想象一下&#xff0c;如果你的React组件不仅能自描述其逻辑&#xff0c;还能直接声明自己的样式&#xff0c;这种“所见即所得”的编程体验是不是让人心动不已&#xff1f;styled-components正是这样一把钥匙&#xff0c;它彻底颠覆了我们对React样式管理的传统认知&#xff0c…

CH571F蓝牙orUSB摇杆鼠标

演示视频&#xff1a; 短视频刷个爽 程序基本上是基于官方的例程上改的&#xff0c;用到的例程有&#xff1a;蓝牙的HID_Mouse,USB的CompoundDev&#xff0c;还有ADC&#xff0c;按键中断。 主要原理 就是ADC采集采集摇杆电压&#xff0c;通过蓝牙HID或者USB的HID发送给电脑或…

Java中操作文件

认识⽂件 我们先来认识狭义上的⽂件(file)。针对硬盘这种持久化存储的I/O设备&#xff0c;当我们想要进⾏数据保存时&#xff0c; 往往不是保存成⼀个整体&#xff0c;⽽是独⽴成⼀个个的单位进⾏保存&#xff0c;这个独⽴的单位就被抽象成⽂件的概 念&#xff0c;就类似办公桌…

Parallels Desktop19让你的Mac无缝运行Windows!

大家好&#xff0c;我是你们的科技小伙伴&#xff0c;今天我要给大家安利一款神奇的软件——Parallels Desktop 19虚拟机。这款产品真的是让我眼前一亮&#xff0c;用起来简直不能更爽&#xff01; 让我们来聊聊为什么我们需要一个虚拟机。 想象一下&#xff0c;你是一个Mac用…

多租户系统数据隔离方案

目录 前言 数据行 数据表 基于业务场景 基于数据量 数据库 数据源表 动态数据源 前言 多租户系统是一种将多个客户的数据和应用程序分开的系统&#xff0c;每个客户被视为一个独立的租户&#xff0c;互不干扰。实现多租户系统的关键之一是确保数据的隔离。 数据隔离的…

[云原生]三、Kubernetes(1.18)

主要内容: 1、kubernetes 简介 2、kubernetes 集群搭建  方式搭建  二进制方式搭建 3、 kubeadm kubernetes 核心技术  YAML 文件详解  kubectl 命令工具  Pod  Label  Controller 控制器 …

职业教育大数据实验实训室建设应用案例

大数据作为一种重要的信息技术&#xff0c;对各行各业产生了深远的影响。职业教育作为培养应用型人才的摇篮&#xff0c;建设大数据实验实训室&#xff0c;对于提高学生的数据分析能力和解决实际问题的能力具有重要意义。唯众作为一家专注于教育技术领域的企业&#xff0c;凭借…

从零开始:MySQL安装与配置完全指南

前言 哇&#xff0c;终于进入到令人激动的MySQL环节了 它可以说是你编程生涯中的最佳朋友&#xff0c;因为它总是能存储你的数据&#xff0c;从不说&#xff1a;“我忘记了你的信 息”。而且&#xff0c;它是免费的&#xff0c;不像一些昂贵的数据库&#xff0c;它从不让你的…

量产部落SM2258XT开卡软件,SM2258XT主控128G SSD固态卡死修复

故障现象&#xff1a;连接此固态硬盘后电脑就会卡死&#xff0c;拔掉重新连接概率性显示盘符&#xff0c;显示了之后也不能正常操作&#xff0c;一点击打开&#xff0c;电脑就立马卡死。 解决过程&#xff1a;下载了很多款量产工具&#xff0c;都不能开卡成功&#xff0c;点击…

Elasticsearch 未授权访问漏洞

Elasticsearch 未授权访问漏洞 ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java开发的&#xff0c;并作为Apache许可条款下的开放源码发布&#xff0c;是当前流行的企业级搜索…

iAppv3无白银会员使用SDK

前言 一个实用小技巧分享给大家。 工具 iapp&#xff1a;百度或点我获取 Mt管理器&#xff1a;百度或点我获取 教程 1.移出“项目路径/apk/lib/”内的全部文件 2.在iapp内测试打包&#xff0c;打包完成后直接返回&#xff0c;不要安装 3.在mt管理器里面点击“项目路径/b…

【课程总结】Day18:Seq2Seq的深入了解

前言 在上一章【课程总结】Day17&#xff08;下&#xff09;&#xff1a;初始Seq2Seq模型中&#xff0c;我们初步了解了Seq2Seq模型的基本情况及代码运行效果&#xff0c;本章内容将深入了解Seq2Seq模型的代码&#xff0c;梳理代码的框架图、各部分组成部分以及运行流程。 框…

【大模型系列】LanguageBind(ICLR2024.01)

Paper&#xff1a;https://arxiv.org/abs/2310.01852Github&#xff1a;https://github.com/PKU-YuanGroup/LanguageBindHuggingface&#xff1a;https://huggingface.co/spaces/LanguageBind/LanguageBindAuthor&#xff1a;Bin Zhu et al. 北大袁粒团队 文章目录 1 LanguageB…

入门mem0.NET

入门mem0.NET 安装包 如果你的项目使用了EntityFrameworkCore,那么你可以跟随这个教程走 <ItemGroup><PackageReference Include"mem0.NET" Version"0.1.7" /><PackageReference Include"mem0.NET.Qdrant" Version"0.1.7…