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

news2024/9/20 0:50:58

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

文章目录

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

CSDN.gif

写在前面的话

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

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 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
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Student.class.isAssignableFrom(parameter.getParameterType());
    }

    /**
     * 真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象
     * 用途:仅仅用于测试,解析请求体内容,比如name#张三,age#20,将内容解析组装成Student对象再返回
     */
    @Override
    public 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 {

    /**
     * 添加入参处理器
     */
    @Override
    public 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.coloradmin.cn/o/1967836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】巧用缺省参数与函数重载:提升编程效率的秘密武器

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间 本章将分享缺省参数与函数重载相关知识&#xff0c;为了更加深入学习C打下了坚实的基础。本章重点在于缺省参数与函数重载使用前提与注意事项 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1…

[CTFHub]ret2text-入土为安的第十二天

checksec pwn ida fn F5 main 点(_int64)v4 v大小为0x70栈基指针0x8返回地址 secure()

软件测试--python基础

一、python基础 (1)第一个python (2)python解释器 (3)基础语法 ①字面量 什么是字面量 常用的值类型 字符串 ②注释 ③变量 什么是变量 变量的特征 变量的目的是存储运行过程的数据 存储的目的是为了&#xff1a;重复使用 ④数据类型 type()语句 变量有类型吗&#xff1f;…

如何选择高品质SD存储卡—高耐用度、防水、防动、抗冲击

SD卡&#xff08;Secure Digital Memory Card&#xff09;是一种广泛使用的存储器件&#xff0c;因其快速的数据传输速度、可热插拔的特性以及较大的存储容量&#xff0c;广泛应用于各种场景&#xff0c;例如在便携式设备如智能手机、平板电脑、运动相机等&#xff0c;用于存储…

68.游戏分析工具设计以及更改辅助中存在的界面问题

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;67.利用FreeLibrary函数实现无痕注入的核心代码 分析工具主要做的是 游戏公共内容分析&…

【前端 · 面试 】TCP 总结(一)—— 概述

最近我在做前端面试题总结系列&#xff0c;感兴趣的朋友可以添加关注&#xff0c;欢迎指正、交流。 争取每个知识点能够多总结一些&#xff0c;至少要做到在面试时&#xff0c;针对每个知识点都可以侃起来&#xff0c;不至于哑火。 image 前言 我们常常会听到“ TCP 三次握手、…

注册中心--Eureka

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring Cloud实战&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.项目问题 2.解决URL问题 2.1解决思路 2.2注册中心 2.3 CAP理…

LeetCode3. 无重复字符的最长子串(java实现)

今天分享的题目是LeetCode3. 无重复字符的最长子串&#xff0c;来看题目描述&#xff1a; 无重复的最长子串&#xff0c;题目有可能有些小伙伴没读太懂&#xff0c;其实就是找到不重复的最长子串&#xff0c;比如eg3&#xff0c;pwwk&#xff0c;那么w出现了两次就不符合要求。…

SpringBoot中的server.context-path

一、问题引入 书接上回&#xff0c;SpringBoot 在 idea中的 .idea和 .iml文件-CSDN博客&#xff0c;我在boot-test的测试项目中使用的 SpringBoot版本为 1.3.5.RELEASE&#xff0c;新项目 cps-task中使用的版本为 2.4.8&#xff0c;造成了连接异常&#xff0c;问题很好解决&…

一文看懂Java反射、注解、UML图和Lambda表达式

反射 定义: 反射是 java 开发语言的特征之一&#xff0c;它允许 java 程序对自身进行检查(自审)&#xff0c;并能直接操作程序内部属性&#xff0c;即就是将类中的各种成分映射成一个 java 对象&#xff0c;利用反射技术可以对一个类进行解剖&#xff0c;将各个组成部分映射成…

c++STL容器中vector的使用,模拟实现及迭代器使用注意事项和迭代器失效问题

目录 前言&#xff1a; 1.vector的介绍及使用 1.2 vector的使用 1.2 1 vector的定义 1.2 2 vector iterator&#xff08;迭代器&#xff09;的使用 1.2.3 vector 空间增长问题 1.2.4 vector 增删查改 1.2.5vector 迭代器失效问题。 2.vector模拟实现 2.1 std::vect…

RAG 革命:NVIDIA 工作站如何成为企业 AI 的秘密武器

在深圳的一家科技初创公司&#xff0c;首席技术官李梅正在向她的团队展示一个令人兴奋的新项目。“看这个&#xff0c;” 她指着屏幕上的实时演示说&#xff0c;“我们刚刚用公司的技术文档训练了一个 AI 助手&#xff0c;它现在可以回答任何关于我们产品的问题&#xff0c;而且…

鸿蒙系统开发【网络管理】

网络管理 介绍 此Demo展示如何查询网络详情、域名解析、网络状态监听等功能。 效果预览&#xff1a; 使用说明&#xff1a; 1.启动应用&#xff0c;在点击检查网络、网络详情、网络连接信息后&#xff0c;展示对应的信息&#xff1b; 2.在域名解析的模块下&#xff0c;输入…

一款功能强大的免费开源卸载工具

BCUninstaller&#xff0c;也称为Bulk Crap Uninstaller&#xff08;简称BCU&#xff09;&#xff0c;是一款免费且开源的Windows平台专用程序卸载工具。它的主要功能是帮助用户高效地批量卸载不需要的应用程序和组件&#xff0c;从而优化系统性能。 BCUninstaller功能特点 批…

8.1 tomcat+jdk

接着昨天的 npm run serve 构建项目 npm run build ls ls dist/ vim dist/index.html [rootweb eleme_web]# cd /usr/local/nginx/conf/ [rootweb conf]# ls 将静态的项目移动到nginx中 [rootweb nginx]# cd conf.d/ [rootweb conf.d]# ls qd.conf [rootweb conf.…

有关 AI 与产品经理的三个暴论

最近跟朋友聊 AI 聊得挺多。往往是不管做什么的&#xff0c;凡是跟互联网有联系的&#xff0c;都挺兴奋&#xff0c;都在讨论 AI。 聊着聊着&#xff0c;有三个暴论浮出水面&#xff0c;跟各位分享。暴论归暴论&#xff0c;道理还是在的。 1. AI 带来的新技术&#xff0c;会带…

2024年中国大模型能力评测:谁是AI领域的真王者?

2024年中国大模型能力评测深度解析 随着人工智能技术的飞速发展&#xff0c;中国的大模型正迎来前所未有的机遇与挑战。近期发布的《2024年中国大模型能力评测》报告&#xff0c;揭示了这一领域的最新趋势、发展瓶颈以及未来方向。本文将基于该报告&#xff0c;带您深入了解中国…

ssh免密认证配置

一.首先要做root用户在sshd服务中的允许 [root172 ~]# vim /etc/ssh/sshd_configPermitRootLogin yes 二.监控动作 watch -n 1 "tail -n 3 /etc/passwd /etc/group;ls -l /home" 三.用户建立时的命令 1.纯净的实验环境 [root172 ~]# rm -fr ~/.ssh/ 2.生成密钥…

【初阶数据结构篇】冒泡排序和快速排序(中篇)

文章目录 冒泡排序和快速排序前言代码位置冒泡排序快速排序递归法实现hoare版本挖坑法lomuto前后指针递归法复杂度分析 非递归法实现 冒泡排序和快速排序 前言 本篇以排升序为例 代码位置 gitee 冒泡排序 动图理解 作为第一个接触的排序算法&#xff0c;冒泡排序想必大…

Monaco 使用 SelectionRange

Monaco 中有个展开选择的功能&#xff0c;默认如果我们选择 function&#xff0c;扩展选择就会选择到行尾&#xff0c;再扩展就会选中整个函数&#xff0c;效果如下&#xff1a; Monaco 可以自定义选择范围&#xff0c;通过 registerSelectionRangeProvider 注册 selectionRang…