《学会 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…...
红日靶场实战复盘:我是如何用CS+蚁剑+IPC$从Web服务器一路打到域控的
红日靶场高阶渗透实战:从Webshell到域控的武器化链路构建 当安全工程师从外网拿到第一个Webshell时,真正的挑战才刚刚开始。红日靶场模拟的企业内网环境中,Web服务器往往只是跳板,真正的核心资产隐藏在层层网络隔离之后。本文将拆…...
Qwen-Image镜像开箱即用:无需pip install、conda install的纯推理工作流
Qwen-Image镜像开箱即用:无需pip install、conda install的纯推理工作流 1. 为什么选择这个定制镜像 如果你正在寻找一个能直接运行通义千问视觉语言模型(Qwen-VL)的环境,又不想花费大量时间配置各种依赖和驱动,这个RTX4090D专用的Qwen-Ima…...
3分钟搞定!Windows上最轻量的APK安装神器全攻略
3分钟搞定!Windows上最轻量的APK安装神器全攻略 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 还在为Windows系统无法直接安装安卓应用而烦恼吗࿱…...
百川2-13B-4bits开源大模型部署教程:RTX 4090 D开箱即用,无需conda环境配置
百川2-13B-4bits开源大模型部署教程:RTX 4090 D开箱即用,无需conda环境配置 1. 开篇:为什么选择百川2-13B-4bits? 如果你正在寻找一个能在消费级显卡上流畅运行、功能强大且完全开源的中文大语言模型,那么百川2-13B-…...
四自由度机械臂Matlab仿真全流程:从DH参数建模到轨迹规划实战
四自由度机械臂Matlab仿真全流程:从DH参数建模到轨迹规划实战 当你第一次尝试用Matlab控制机械臂时,可能会被各种专业术语和复杂的数学公式吓到。但别担心,这篇文章将带你从零开始,一步步完成四自由度机械臂的完整仿真流程。我们会…...
c盘爆红了怎么清理?c盘怎么清理垃圾而不误删文件?c盘瘦身最简单的方法?电脑C盘满了怎么清理_C盘空间不足清理实用技巧
如果您发现电脑运行变慢、系统提示“C盘空间不足”,或C盘变红,则很可能是C盘已接近满载,大量临时文件、缓存、更新残留及用户数据堆积在系统盘中。 以下是经过验证c盘爆红清理实用技巧: 关于C盘清理工具,给大家安排一款…...
ESP32硬件脉冲计数器库:PCNT外设深度封装与工业应用
1. 项目概述ESP32PulseCounter_Modified 是一个面向 Arduino 框架的轻量级硬件脉冲计数器封装库,专为 ESP32 系列 SoC 的 PCNT(Pulse Counter)外设模块深度定制。该库并非简单封装 ESP-IDF 原生 API,而是基于对 ESP32 脉冲计数硬件…...
Qwen3.5-9B镜像免配置教程:一行命令启动7860端口Web服务
Qwen3.5-9B镜像免配置教程:一行命令启动7860端口Web服务 1. 前言:为什么选择Qwen3.5-9B 如果你正在寻找一个功能强大又容易部署的多模态AI模型,Qwen3.5-9B绝对值得考虑。这个模型最大的特点就是"开箱即用"——不需要复杂的配置&a…...
Qwen3-32B-Chat跨境电商应用:多语言商品描述、平台规则解读、客服话术生成
Qwen3-32B-Chat跨境电商应用:多语言商品描述、平台规则解读、客服话术生成 1. 跨境电商AI助手解决方案 跨境电商行业面临着多语言沟通、平台规则复杂、客服效率低下等痛点。Qwen3-32B-Chat私有部署镜像为这些挑战提供了智能化解决方案,基于RTX4090D 24…...
基于GTE的智能广告投放:用户兴趣与广告文案的语义匹配
基于GTE的智能广告投放:用户兴趣与广告文案的语义匹配 1. 引言 你有没有遇到过这样的情况?刷手机时看到的广告完全不对胃口,不是已经买过的产品,就是根本不感兴趣的内容。这种糟糕的广告体验背后,其实是传统广告投放…...
