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

news2024/9/25 15:24:08

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

文章目录

    • 写在前面的话
    • SpringMVC 出参处理
      • 学前准备与回顾
      • @ResponseBody 出参处理
      • 自定义出参用法
    • 总结陈词

CSDN.gif

写在前面的话

上一篇博文《学会 SpringMVC 系列 · 剖析入参处理》的学习,大致了解了SpringMVC请求流程中的入参处理环节,接下来介绍出参处理相关分析。后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。

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


SpringMVC 出参处理

学前准备与回顾

与入参处理一致,本篇 SpringMVC 源码分析系列文章,继续使用 《搭建拥有数据交互的 SpringBoot 》博文搭建的 SpringBoot3.x 项目为基础,以此学习相关源码,对应 SpringMVC 版本为 6.1.11。另外,为保证知识连贯性,继续总结和回顾一下主体流程。

【一次请求的主链路节点】
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)

Tips:基于上方内容,本篇出参处理将继续从HandlerMethodReturnValueHandlerComposite展开。


@ResponseBody 出参处理

由于目前大部分场景都是前后端分离开发模式,后端较少返回视图页面,基本都采用 @ResponseBody 返回数据。
这里也以最常见的@ResponseBody为示例,展开介绍。

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

【运行流程】
1、先跑一下接口,忽略之前的逻辑,断点在出参处理的入口处,如下所示。
这里 returnValue 是前面接口实际返回的数据,returnType 是返回值类型(MethodParameter)。
image.png
2、接着进入 selectHandler 方法,这个按字面意思,就是查找合适的的处理器,这里又可以看到熟悉的配方。
遍历 HandlerMethodReturnValueHandler 的列表,依次执行其 supportsReturnType 判断是否满足。
这里还有一个异步判定逻辑,暂不展开。
image.png
3、这里找到 RequestResponseBodyMethodProcessor,要求类或方法包含@ResponseBody即可。
image.png
4、找到合适的处理器后,返回执行 RequestResponseBodyMethodProcessor 的 handleReturnValue方法。
image.png
5、该方法往下走到 writeWithMessageConverters,看名字就是使用消息转换器进行write。
这里再次遍历消息转换器列表,依次执行canWrite方法判定,由于示例项目有引入了fastjson相关依赖,这边匹配到了
FastJsonHttpMessageConverter。由于这步代码比较多一些,下方贴一下源代码。
image.png

// 遍历 HttpMessageConverter 出参转换器列表
for (HttpMessageConverter<?> converter : this.messageConverters) {
	GenericHttpMessageConverter genericConverter =
			(converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null);

    // 匹配符合要求的出参转换器,这里找到了FastJsonHttpMessageConverter
	if (genericConverter != null ?
			((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
			converter.canWrite(valueType, selectedMediaType)) {

        // 再正式write之前,还会先调用ResponseBodyAdvice列表的beforeBodyWrite方法
		body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
				(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
				inputMessage, outputMessage);
		if (body != null) {
			Object theBody = body;
			LogFormatUtils.traceDebug(logger, traceOn ->
					"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
			addContentDispositionHeader(inputMessage, outputMessage);

            // 正式转换数据
			if (genericConverter != null) {
				genericConverter.write(body, targetType, selectedMediaType, outputMessage);
			}
			else {
				((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Nothing to write: null body");
			}
		}
		return;
	}
}

6、这里在执行 FastJsonHttpMessageConverter#write 之前,会先执行 RequestResponseBodyAdviceChain 的beforeBodyWrite方法,遍历 ResponseBodyAdvice 列表,依次执行 supports 方法进行匹配。
注意,如果有自定义的ResponseBodyAdvice此时就可以触发,这个也是一个扩展点。

private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
		Class<? extends HttpMessageConverter<?>> converterType,
		ServerHttpRequest request, ServerHttpResponse response) {
	for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
		if (advice.supports(returnType, converterType)) {
			body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
					contentType, converterType, request, response);
		}
	}
	return body;
}

7、处理完上述步骤后,正式使用FastJsonHttpMessageConverter进行write转换数据,最后是走到下方,直接输出流返回。
image.png

【总结一下,链路流程】
HandlerMethodReturnValueHandlerComposite#handleReturnValue(出参处理的入口)
HandlerMethodReturnValueHandlerComposite#selectHandler(找出参处理器)
RequestResponseBodyMethodProcessor#supportsReturnType(判断出参匹配)
RequestResponseBodyMethodProcessor#handleReturnValue(出参处理逻辑)
AbstractMessageConverterMethodProcessor#writeWithMessageConverters(找出参转换器处理)
FastJsonHttpMessageConverter#canWrite(匹配出参转换器)
RequestResponseBodyAdviceChain#beforeBodyWrite(执行write前允许修改body)
FastJsonHttpMessageConverter#write(执行实际出参转换)


自定义出参用法

通过上述源码分析,大概看出来,如果需要自定义出参行为,可以从三个层面下手:返回值处理器、返回值消息转换器、响应前拦截
对应的关键词:HandlerMethodReturnValueHandler、MessageConverters、 ResponseBodyAdvice

Tips:由于篇幅所限,这几个扩展点后续章节单独介绍。


总结陈词

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

CSDN_END.gif

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

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

相关文章

知乎问答广告开户收费,知乎广告投放价格!

知乎&#xff0c;作为中国最大的知识分享社区&#xff0c;凭借其庞大的用户基础、高质量的内容生态和精准的数据分析能力&#xff0c;成为了众多企业争相投放广告的热门平台。如何在知乎上精准投放广告&#xff0c;实现品牌曝光与业绩增长&#xff0c;成为了众多企业面临的难题…

LeNet5模型搭建

文章目录 LeNet1 搭建模型2 训练模型3 测试模型3.1 预测一3.2 预测二 LeNet LeNet 诞生于 1994 年&#xff0c;是最早的卷积神经网络之一&#xff0c;并且推动了深度学习领域的发展。自从 1988 年开始&#xff0c;在许多次成功的迭代后&#xff0c;这项由 Yann LeCun 完成的开拓…

阿里云ECS之AMD实例

阿里云ECS之AMD实例 计算型实例规格族c8a关于AMD实例的使用AMD的应用场景 关于AMD实例&#xff0c;AMD实例也是云服务器ECS中的一种&#xff0c;AMD实例是阿里云新推出的重磅产品&#xff0c;基于AMD EPYCTM微处理器架构&#xff0c;该架构属于x86架构。目前已知的AMD实例规格族…

【RISC-V设计-09】- RISC-V处理器设计K0A之CIC

【RISC-V设计-09】- RISC-V处理器设计K0A之CIC 文章目录 【RISC-V设计-09】- RISC-V处理器设计K0A之CIC1.简介2.顶层设计3.端口说明4.代码设计5.仲裁代码6.总结 1.简介 核内中断控制器&#xff08;Core Interrupt Controller&#xff0c;简称CIC&#xff09;是管理和仲裁中断的…

SQL注入实例(sqli-labs/less-18)

0、初始页面 先使用brup爆破密码&#xff0c;账号admin&#xff0c;密码admin 1、确定闭合字符 判断注入点在post请求参数的User-agent处 闭合字符为单引号 2、爆库名 3、爆表名 4、爆列名 5、查询最终目标 在index.php中有这么一句 $insert"INSERT INTO security.uage…

MySQL基础——数据库客户端,数据库服务器,数据库(目录)

什么是数据库 具体&#xff1a;mysqld服务器结合内部的存储引擎所实现对数据库文件中的数据增删查改的一套方案 ——在磁盘或内存中存储特定结构组织的数据----在磁盘中存储的一套数据方案&#xff0c; 你给我字段或要求&#xff0c;我直接给你结果就行 整体&#xff1a;用户通…

gradio之进度条

输出控件显示进度&#xff0c;进度结束显示控件结果 import gradio as gr import timedef slowly_reverse(word, progressgr.Progress()):progress(0, desc"Starting")time.sleep(1)progress(0.05)new_string ""for letter in progress.tqdm(word, desc&…

概率论原理精解【9】

文章目录 集类拓扑空间基 参考文献 集类 C是一个集类&#xff08;以G的某些子集为元素的集合称为G的集类&#xff09;。 A i ∈ C , ∩ i 1 n A i ∈ C , 此为有限交封闭 C 所得集类 C ∩ f A_i \in C,\cap_{i1}^nA_i \in C,此为有限交封闭C所得集类C_{\cap f} Ai​∈C,∩i1n…

2024年黑龙江公安招聘报名流程(建议电脑)

2024年黑龙江省公安机关人民警察专项招录公告&#xff08;2810人&#xff09; 报名时间&#xff1a;2024年8月16日9:00至8月20日17:00 查询资格审查结果时间&#xff1a;2024年8月16日9:00至8月20日17:00 缴费确认时间&#xff1a;2024年8月16日9:00至8月21日17:00 网上打印准考…

程序员保碗之策

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 1. **持续深化技术实力&#xff1a;从基础到前沿**夯实基础紧跟技术前沿跨领域融合 2. **提升实战能力&#xff1a;从项目到产品**参与实际项目自主实践产品思维 3. **建立个人品牌与影响力&#xff…

代码随想录算法训练营day36:动态规划04:1049.最后一块石头的重量II;494.目标和;474.一和零

1049.最后一块石头的重量II 力扣题目链接(opens new window) 有一堆石头&#xff0c;每块石头的重量都是正整数。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果如下&…

WEB应用(十五)---文件包含

文件包含的概念 在各种开发语言中都提供了内置的文件包含函数&#xff0c;可以使得开发人员在一个代码文件中直接包含&#xff08;引入&#xff09;另外一个代码文件。 由于文件包含可以达到复用和方便修改的目的&#xff0c;在代码设计中常常使用。 大多数情况下&#xff0…

构建生成工具cmake的使用(2)

一.制作库文件 Linux下动态库是以 .lib开头&#xff0c;以 .so为结尾。静态库以 .lib 开头&#xff0c;.a为结尾。 Windows下动态库以dll为结尾&#xff0c;以 .lib为结尾 1.1.cmake编写生成动态库 我们想把add.cc和sub.cc打成一个动态库 …

Spring Boot 3.x Rest API最佳实践之统一响应结构

上一篇&#xff1a;Spring Boot 3.x Rest API最佳实践之API实现 下一篇&#xff1a;Spring Boot 3.x Rest API统一异常处理最佳实践 前面我们完成了电商示例API的设计和简单实现&#xff0c;本小节在此基础上完成统一响应结构的实战。 文章目录 定义Response响应体拦截Rest A…

【Day04】0基础微信小程序入门-学习笔记

文章目录 基础加强学习目标自定义组件1. 创建与引用2. 样式3. 数据、方法和属性4. 数据监听器5. 纯数据字段6. 组件生命周期6.1 created6.2 attached&#xff08;使用最多&#xff09;6.3 detached6.4 定义生命周期函数 7. 组件所在页面的生命周期7.1 定义使用7.2 生成随机的RG…

【QGroundControl二次开发】九. QGC地面站替换自定义MAVLINK协议

一. 生成自定义mavlink协议 具体操作参考之前文章->【QGroundControl二次开发】四.QGC自定义MAVLink消息 -------------------------下面为具体操作--------------------------- 在mavlink源码下的mavlink-master\message_definitions\v1.0\common.xml加入自定义协议&…

(javaweb)请求响应postman

目录 一.请求响应概述 二.请求参数的接收及封装 1.postman 2. 简单参数 3.实体参数 4.数组集合参数 5.日期参数 6.JSON参数 二.设置响应数据 一.请求响应概述 1.Tomcat又称为servlet容器 前端浏览器发起请求携带http请求数据&#xff0c;web服务器负责请求协议的解析&a…

网站开发涉及到的技术内容介绍——后端PHP(1)

一、PHP简介 PHP(全称:Hypertext Preprocessor (超文本预处理器))是一种创建动态交互性网站的服务器端脚本语言( PHP代码可以放在HTML文档中的任何位置;且PHP 脚本是在服务器上运行,然后将纯 HTML 结果发送回浏览器)且PHP 是免费的,并且使用非常广泛。同类的后端语言有…

Element学习(布局组件、案例操作)(4)

1、页面整体的布局 2、找到这种布局对应的代码&#xff08;复制——>粘贴到标签<div>中&#xff09; <el-container><el-header>Header</el-header><el-container><el-aside width"200px">Aside</el-aside><el-main…

8.8C++作业

在类结构体中&#xff0c;运用 成员函数或友元函数 实现算术运算符重载 #include <iostream>using namespace std;class Stu {friend const Stu operator/(const Stu &R,const Stu &L);friend const Stu operator%(const Stu &R,const Stu &L); private…