《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
- MessageConverters
- 技术说明
- 基础示例
- 执行过程
- 注意事项
- 源码知识回顾
- 总结陈词

写在前面的话
前几篇博文,大致了解了SpringMVC请求流程中的参数与返回值的源码分析,后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。
本篇文章先介绍一下 MessageConverters 相关内容。
相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
MessageConverters
技术说明
作用:MessageConverters 主要负责将 Controller 方法的返回值转换为 HTTP 响应的内容。
工作原理:当 Controller 方法返回一个对象时,Spring MVC 使用消息转换器将该对象转换为 HTTP 响应体的内容。消息转换器负责将 Java 对象转换为特定的媒体类型,例如 JSON、XML、HTML 等。Spring 提供了各种内置的消息转换器来支持不同的数据格式。
示例:如果你的 Controller 方法返回一个对象,Spring MVC 将根据请求的 Accept 头部信息和返回值类型选择适当的消息转换器,将对象转换为对应的媒体类型。
基础示例
描述:写了一个测试的效果,针对Student类型入参做了一个特定转换操作,部分代码见下方。
补充:要实现自定义消息转换器(入参和出参都适用),就继承 AbstractHttpMessageConverter,实现相应方法,有点类似参数解析器。
Step1、自定义消息转换器
public class MyMessageConverter extends AbstractHttpMessageConverter<Student> {/*** 新建一个我们自定义的媒体类型application/xxx-lw*/public MyMessageConverter() {super(new MediaType("application", "xxx-lw", StandardCharsets.UTF_8));}/*** 支持的类型*/@Overrideprotected boolean supports(Class<?> clazz) {return Student.class.isAssignableFrom(clazz);}/*** 入参处理器*/@Overrideprotected Student readInternal(Class<? extends Student> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {String str = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);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();}/*** 重写writeInternal ,处理如何输出数据到response。*/@Overrideprotected void writeInternal(Student user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {String outStr = "获取到名称为" + user.getName() + ",年龄" + user.getAge() + "岁的人";outputMessage.getBody().write(outStr.getBytes());}
}
Step2、配置消息转换器
//下方两个配置方式,保留一个,是JSON序列化解析的逻辑,和MyMessageConverter无关/*** 扩展原有方式实现*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {for (HttpMessageConverter<?> httpMessageConverter : converters) {if (MappingJackson2HttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())) {MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) httpMessageConverter;ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper();objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {@Overridepublic void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {jsonGenerator.writeString("");}});mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);}}converters.add(0, new MyMessageConverter());
}/*** 添加自定义消息转换器* BigInteger转String* NULL转空字符串*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();ObjectMapper objectMapper = SpringContextHolder.getBean(ObjectMapper.class);SimpleModule simpleModule = new SimpleModule();simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);objectMapper.registerModule(simpleModule);jackson2HttpMessageConverter.setObjectMapper(objectMapper);converters.add(0, jackson2HttpMessageConverter);
}
Step3、编写测试接口
/*** 测试自定义消息转换器*/
@RequestMapping("/stuTest")
public Student stuTest(@RequestBody Student stu) {return Student.builder().id(1).name(stu.getName()).age(stu.getAge()).email(null).build();
}
Step4、启动服务,PostMan测试效果,信息如下:
curl --location 'http://localhost:8083/stuTest' \
--header 'Content-Type: application/xxx-lw' \
--header 'Cookie: JSESSIONID=2BFDA24061FF974C50BECD540FB916D1' \
--data 'name#张三,age#20'
返回结果:获取到名称为张三,年龄20岁的人
执行过程
1、由于添加了@RequestBody,直接进入 RequestResponseBodyMethodProcessor#resolveArgument;
2、接着代码走到 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,选取合适的入参转换;
3、由于 MyMessageConverter 是首个转换器,supports 方法也满足,被触发其 read 方法,实际是 MyMessageConverter#readInternal;
4、接着执行核心业务方法;
5、接着代码走到 AbstractMessageConverterMethodArgumentResolver#writeWithMessageConverters,选取合适的出参转换;
6、这里 MyMessageConverter 继续被匹配,执行 write 方法,实际是 MyMessageConverter#writeInternal;
7、最后就是 outputMessage.getBody().flush(),流程结束。
Tips:这里看到read前后,有beforeBodyRead和afterBodyRead,也都是可以扩展的,write 只有 beforeBodyWrite 方法,因为写完就结束了。
【截图补充】
AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,可以看到注册的自定义消息转换器已经在第一个,判断符合要求后会执行。
可以看出找到符合的消息转换器,直接break了,代表消息转换器只会执行一个。

注意事项
如果接口入参去掉RequestBody注解,再测试一下。
那现象是进入自定义的入参解析器,不会进入入参转换器。但由于结果还是ResponseBody,因此还是会进入出参转换器。
这个也是很好理解的!
源码知识回顾
本篇为 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 和 @ResponseBody 场景】

总结陈词
本篇博文继请求链路源码分析后,继续介绍了MessageConverters的用法,它既可以用在入参处理,也可以用于返回值处理,挺方便的,欲知后事如何,请听下回分解。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关文章:
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
深度学习项目 -7-使用 Python 的手写数字识别
一、前言 该文章仅作为个人学习使用 二、正文 项目源代码:深度学习项目 - 使用 Python 进行手写数字识别 - DataFlair (data-flair.training) 数据集:https://drive.google.com/open?id1hJiOlxctFH3uL2yTqXU_1f6c0zLr8V_K Python 深…...
MySQL —— 库,数据类型 与 表
库与基础操作 1.1 查看数据库 使用 show databases; 可以查看当前 MySQL 目前有多少个数据库 5 rows 表示有 5 行,这里是表示的是有效的数据,不包括 第一行的指引 set 表示结果集合 0.01 sec 表示这个 sql 语句一共运行了0.01 秒,一般情况…...
Java重修笔记 第二十七天 匿名内部类
匿名内部类 1. 定义:无类名(底层自动分配类名“外部类名$1”),既是类也是对象,定义在外部类的局部位置,例如方法体和代码块中,通过new类或接口并在大括号里重写方法来实现。 2. 使用场景&…...
Nero Lens 智图 - 适用于 iOS 和 iPadOS 的专业图片处理 App
首先是手机端的无损放大 App:Nero Lens 智图,适用于 iOS 和 iPadOS,不仅可以放大,还有多种 AI 图片增强功能。 使用这款 App 可以通过 AI 模型智能放大可达 400%,还有老照片去划痕、上色,抠图移除背景、照…...
Nginx代理路径被吃
Nginx代理路径被吃的情况 日常工作中经常使用nginx反向代理一些资源,有时正常代理,发现代理不过去。 验证被吃调location情况 通过浏览器访问: https://zhao138969.com/LinuxPackage/Python/SelectDocker location /LinuxPackage { proxy…...
pytest-html报告修改与汉化
前言 Pytest框架可以使用两种测试报告,其中一种就是使用pytest-html插件生成的测试报告,但是报告中有一些信息没有什么用途或者显示的不太好看,还有一些我们想要在报告中展示的信息却没有,最近又有人问我pytest-html生成的报告&a…...
react-native从入门到实战系列教程一Swiper组件的使用及bug修复
轮播图,在app中随处可见,这么重要的功能我们怎么可能不学习下在react-native中的实现方式。 依然是第三方组件react-native-swiper 官网地址 https://www.npmjs.com/package/react-native-swiper 组件使用的组件及事件参考官方即可。 实现效果 官网…...
springboot开发的常用注解总结-配置组件类注解
Spring Boot 提供了许多注解,这些注解大大简化了 Spring 应用的配置和开发过程。以下是一些常见的 Spring Boot注解及其作用。 目录 配置组件类 (Configure Component )Configuration解释:Demo Code:更深度使用&#x…...
DataX 最新版本安装部署
1、下载 git clone gitgithub.com:alibaba/DataX.git 2、打包 mvn -U clean package assembly:assembly -Dmaven.test.skiptrue...
【架构】应用保护
这篇文章总结一下应用保护的手段。如今说到应用保护,更多的会想到阿里的sentinel,手段丰富,应用简单。sentinel的限流、降级、熔断,可以自己去试一下,sentinel主要通过配置实现功能,不难。sentinel的简介放…...
从核心到边界:六边形、洋葱与COLA架构的深度解析
文章目录 1 引言2 软件架构3 架构分类4 典型的应用架构4.1 分层架构4.2 CQRS4.3 六边形架构4.4 洋葱架构4.5 DDD 5 COLA架构设计5.1 分层设计5.2 扩展设计5.3 规范设计5.3.1 组件规范5.3.2 包规范5.3.3 命名规范 6 COLA架构总览7 小结 1 引言 软件的首要技术使命:管…...
04-Fastjson反序列化漏洞
免责声明 本文仅限于学习讨论与技术知识的分享,不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本文作者不为此承担任何责任,一旦造成后果请自行承担&…...
ABC365(A-D)未补
A - Leap Year(模拟) 题意:给定一个数字n,如果n不是4的倍数,输出365;如果n是4的倍数但不是100的倍数,输出366;如果n是100的倍数但不是400的倍数,输出365;如果…...
Python用png生成不同尺寸的图标
Kimi生成 from PIL import Imagedef generate_icon(source_image_path, output_image_path, size):with Image.open(source_image_path) as img:# 转换图片为RGBA模式,确保有透明通道if img.mode ! RGBA:img img.convert(RGBA)# 调整图片大小到指定尺寸img img.r…...
1688中国站获得工厂档案信息 API
公共参数 名称类型必须描述keyString是免费申请调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cacheString否[yes,no]默认y…...
定时任务框架 xxl-job
🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢…...
C/C++关键字大全
目录 一、const 二、static 三、#define 和 typedef 四、#define 和 inline 五、#define 和 const 六、new 和 malloc 七、const 和 constexpr 八、volatile 九、extern 十、前置 和后置 十一、atomic 十二、struct 和 class 一、const 1、const 关键字可用于定义…...
ROS2 Linux Mint 22 安装教程
前言: 本教程在Linux系统上使用。 一、linux安装 移动硬盘安装linux:[LinuxToGo教程]把ubuntu装进移动固态,随时随用以下是我建议安装linux mint版本的清单: 图吧工具箱:https://www.tbtool.cn/linux mint: https://…...
快速将网站从HTTP升级为HTTPS
在当今数字化的世界中,网络安全变的越来越重要,HTTPS(超文本传输安全协议)不仅能够提供加密的数据传输,还能增强用户信任度,提升搜索引擎排名,为网站带来多重益处。所以将网站从HTTP升级到HTTPS…...
游戏开发实战:如何用Bezier曲线打造流畅的3D角色动画路径(Unity/C#示例)
游戏开发实战:如何用Bezier曲线打造流畅的3D角色动画路径(Unity/C#示例) 在3D游戏开发中,角色移动轨迹的自然度直接影响玩家体验。传统直线移动或简单弧线往往显得生硬,而Bezier曲线凭借其平滑过渡和灵活控制的特性&am…...
告别Salesforce!这5个开源AI CRM项目,帮你用更低成本打造专属客户管理系统
开源AI CRM革命:5个低成本替代方案深度评测与技术选型指南 当Salesforce的年费账单突破六位数时,越来越多的技术决策者开始将目光转向开源生态。这不是简单的成本妥协,而是一场关于数据主权、技术栈控制和AI原生体验的范式转移。以下是经过三…...
AI辅助开发:打造你的智能编程技能教练——基于快马平台实践
最近在学编程时,发现一个痛点:遇到问题经常要反复查文档、搜论坛,效率很低。刚好体验了InsCode(快马)平台的AI辅助功能,用它做了个"智能编程教练"的小项目,效果意外地好。分享下具体实现思路和实际体验&…...
基于图像的深度学习与MVS三维重建全流程服务 支持远程部署定制 含pcl/c++/matlab...
基于图像的深度学习MVS三维重建全流程 可远程部署,可定制 点云pcl,c,matlab开发,基于图像三维重建,点云算法开发 只需要提供摄的图像,即可生成完整的三维模型(大小场景均可)上周去爬了个浙西的小众山&#…...
vue3-count-to避坑指南:数字增长动画的7个常见问题与解决方案
Vue3-Count-To深度避坑实战:数字动画7大疑难解析 数字动态增长效果在数据可视化、金融仪表盘和运营数据展示中扮演着关键角色。vue3-count-to作为Vue3生态中专精于此的轻量级库,虽然API简洁,但在真实业务场景中往往会遇到各种边界情况。本文将…...
突破透明动画性能瓶颈:VAP引擎实现移动端高效视觉体验
突破透明动画性能瓶颈:VAP引擎实现移动端高效视觉体验 【免费下载链接】vap VAP是企鹅电竞开发,用于播放特效动画的实现方案。具有高压缩率、硬件解码等优点。同时支持 iOS,Android,Web 平台。 项目地址: https://gitcode.com/gh_mirrors/va/vap …...
将 OnePlus 手机备份到云服务
丢失 OnePlus 设备上的珍贵照片、重要联系人、短信或应用数据可能会令人非常沮丧,无论是意外删除、设备损坏、被盗,甚至是恢复出厂设置。这时,云备份就派上了用场。它提供了一种简单可靠的数据保护方式,确保您可以随时随地在新 On…...
云原生实战:如何用GROUP模型提升容器工作负载预测准确率(附避坑指南)
云原生实战:如何用GROUP模型提升容器工作负载预测准确率(附避坑指南) 在云原生架构中,容器资源管理一直是DevOps团队面临的重大挑战。传统单容器预测方法往往忽视了微服务间复杂的协同关系,导致预测误差居高不下。本文…...
告别重复代码:BaseMapperPlus在SpringBoot项目中的5个高级用法
BaseMapperPlus实战:SpringBoot项目中提升开发效率的5个高阶技巧 在SpringBoot项目中使用MyBatis-Plus进行数据持久层开发时,BaseMapperPlus作为社区广泛采用的扩展接口,能显著减少模板代码。本文将分享五个实际业务场景中的高阶用法…...
毕业设计实战:基于SpringBoot+Vue+MySQL的智慧党建系统设计与实现指南
毕业设计实战:基于SpringBootVueMySQL的智慧党建系统设计与实现指南 在开发“基于SpringBootVueMySQL的智慧党建系统”毕业设计时,曾因活动报名记录表未通过党员ID与党建活动ID双外键关联踩过关键坑——初期仅单独设计报名记录表的报名编号字段ÿ…...
