springboot系列十: 自定义转换器,处理JSON,内容协商
文章目录
- 自定义转换器
- 基本介绍
- 应用实例
- 查看源码
- 注意事项和细节
- 处理JSON
- 需求说明
- 应用实例
- 内容协商
- 基本介绍
- 应用实例
- debug源码
- 优先返回xml
- 注意事项和细节

⬅️ 上一篇: springboot系列九: 接收参数相关注解
🎉 欢迎来到 springboot系列十: 自定义转换器,处理JSON,内容协商 🎉
在本篇文章中,我们将探讨如何在 Spring Boot 中实现自定义转换器、处理 JSON 数据以及进行内容协商。通过掌握这些技术,您可以更灵活地处理不同格式的数据,提高应用程序的兼容性和用户体验。
🔧 本篇需要用到的项目:
自定义转换器
基本介绍
1.SpringBoot在响应客户端请求时, 将提交的数据封装成对象时, 使用了内置转换器
2.SpringBoot也支持自定义转换器, 这个内置转换器在debug的时候, 可以看到, 提供了124个内置转换器, 看下源码. GenericConverter类-ConvertiblePair(内部类)

]
应用实例
需求说明: 演示自定义转换器使用
1.修改save.html
<!--使用自定义转换器关联car, 字符串整体提交, 使用,号间隔-->
坐骑: <input name="name" value="碧水金睛兽,666.6"><br/
2.创建src/main/java/com/zzw/springboot/config/WebConfig.java, 增加自定义转换器
springboot系列四: sprintboot容器功能
/*** @Configuration(proxyBeanMethods = false)* 1.表示 WebConfig 是一个配置类* 2.proxyBeanMethods = false 表示使用Lite模式*/
@Configuration(proxyBeanMethods = false)
public class WebConfig {//注入bean WebMvcConfiger@Beanpublic WebMvcConfigurer webMvcConfigurer() {//整个是WebMvcConfigurer接口的匿名内部类return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {/*** 解读* 1.在addFormatters方法中, 增加一个自定义转换器* 2.增加自定义转换器 String -> Car* 3.增加的自定义转换器会注册到 converters 容器中* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]* 5.一会我们debug查看这些转换器*/registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}});}};}
}
3.测试
monster = Monster(id=100, name=张三, age=30, maritalStatus=false, birthday=Sat Jan 01 00:00:00 CST 1994, car=Car(vehicleName=碧水金睛兽, vehiclePrice=666.6))
查看源码
1.debug, 可以看到我们新增的Converter


快捷键查看有多少元素



注意事项和细节
1.注册转换器换种写法, 方便理解
/*** @Configuration(proxyBeanMethods = false)* 1.表示 WebConfig 是一个配置类* 2.proxyBeanMethods = false 表示使用Lite模式*/
@Configuration(proxyBeanMethods = false)
public class WebConfig {//注入bean WebMvcConfiger@Beanpublic WebMvcConfigurer webMvcConfigurer() {//整个是WebMvcConfigurer接口的匿名内部类return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {/*** 解读* 1.在addFormatters方法中, 增加一个自定义转换器* 2.增加自定义转换器 String -> Car* 3.增加的自定义转换器会注册到 converters 容器中* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]* 5.一会我们debug查看这些转换器*//*registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}});*///换种写法注册自定义转换器, 方便理解//1.先创建一个自定义的转换器Converter<String, Car> zzwConverter = new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}};//添加转换器到converters容器registry.addConverter(zzwConverter);//还可以增加更多的转换器....}};}
}
2.假如我们不添加自定义转换器, 会报typeMismatch错误, 报400错误. 而400错误是客户端的错误, 请求包含语法错误.
JavaWeb系列八: WEB 开发通信协议(HTTP协议)

3.创建两个自定义转换器
/*** @Configuration(proxyBeanMethods = false)* 1.表示 WebConfig 是一个配置类* 2.proxyBeanMethods = false 表示使用Lite模式*/
@Configuration(proxyBeanMethods = false)
public class WebConfig {//注入bean WebMvcConfiger@Beanpublic WebMvcConfigurer webMvcConfigurer() {//整个是WebMvcConfigurer接口的匿名内部类return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {/*** 解读* 1.在addFormatters方法中, 增加一个自定义转换器* 2.增加自定义转换器 String -> Car* 3.增加的自定义转换器会注册到 converters 容器中* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]* 5.一会我们debug查看这些转换器*//*registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}});*///换种写法注册自定义转换器, 方便理解//1.先创建一个自定义的转换器Converter<String, Car> zzwConverter = new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}};//还可以增加更多的转换器....//第2个自定义的转换器Converter<String, Monster> zzwConverter2 = new Converter<String, Monster>() {@Overridepublic Monster convert(String source) {return null;}};//添加转换器到converters容器registry.addConverter(zzwConverter);registry.addConverter(zzwConverter2);}};}
}
debug, 看一看 converters容器 是不是变成了 126 个.


4.创建三个自定义转换器, 由于key是[源类型->目标类型], 所以会覆盖掉一个.
//1.先创建一个自定义的转换器
Converter<String, Car> zzwConverter = new Converter<String, Car>() {//传入Converter接口的匿名内部类@Overridepublic Car convert(String source) {//source就是 传入的字符串 碧水金睛兽,666.6//这里加入自定义的转换业务代码if (!ObjectUtils.isEmpty(source)) {String[] split = source.split(",");Car car = new Car();car.setVehicleName(split[0]);car.setVehiclePrice(Double.parseDouble(split[1]));return car;}return null;}
};//还可以增加更多的转换器....
//第2个自定义的转换器
Converter<String, Monster> zzwConverter2 = new Converter<String, Monster>() {@Overridepublic Monster convert(String source) {return null;}
};
//第3个自定义的转换器
Converter<String, Car> zzwConverter3 = new Converter<String, Car>() {@Overridepublic Car convert(String source) {return null;}
};//添加转换器到converters容器
registry.addConverter(zzwConverter);
registry.addConverter(zzwConverter2);
registry.addConverter(zzwConverter3);
1)测试, 是否覆盖.


2)查看 converters 容器. 因为第三个转换器和第一个转换器 key 是相同的, 所以覆盖掉了.

处理JSON
需求说明
演示返回JSON格式的数据
应用实例
1.SpringBoot 支持返回 JSON 格式数据, 在启用WEB开发场景时, 已经引入了相关依赖.
springboot系列二: sprintboot依赖管理


2.新建src/main/java/com/zzw/springboot/controller/ResponseController.java
@Controller
public class ResponseController {//编写方法, 返回monster数据 要求以json格式返回@GetMapping(value = "/getMonster")@ResponseBodypublic Monster getMonster() {//说明//开发中 monster对象是从db获取,这里我们模拟一个mosnter对象Monster monster = new Monster();monster.setId(100);monster.setName("张三");monster.setAge(18);monster.setBirthday(new Date());monster.setMaritalStatus(false);Car car = new Car();car.setVehicleName("奔驰");car.setVehiclePrice(100000.0);monster.setCar(car);return monster;}
}
3.Postman测试.

4.Debug一下 monster对象如何以Json格式返回.
浏览器/Postman 请求, 第一个断点

第二个断点, 找到 AbstractJackson2HttpMessageConverter.class


用工厂模式创建了个 generator.

generator是 UTF8JsonGenerator

object 就是 monster对象

这条语句一旦执行完毕, 浏览器就拿到数据.

内容协商
基本介绍
1.根据客户端接收能力不同, SpringBoot返回不同媒体类型的数据.
JavaWeb系列八: WEB 开发通信协议(HTTP协议)
2.比如:
客户端Http请求, 携带 Accept aaplication/xml 请求头, 要求服务端返回xml数据;
客户端Http请求, 携带 Accept aaplication/json 请求头, 则要求服务端返回json数据
3.效果如下
如果Accept, 我设置的是 application/json, 那么返回的数据就是 json 格式.

如果Accept, 我设置的是 application/xml, 那么返回的数据就是 xml 格式.

应用实例
需求说明: 使用Postman发送Http请求, 根据请求头不同, 返回对应的json数据, 或者xml数据, 如图

注意: Accept: */* 默认返回 json 格式

在底层,generator生成的是xml格式的, 但是在进行转换的时候, 需要有一个jar包的依赖.
<!--引入处理xml的依赖-->
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.12.4</version>
</dependency>
debug源码
Postman切换不同的Accept类型, 来Debug源码, 看看对应的JsonGenerator类型
1,返回json类型

靠contentType进行内容协商


2.返回xml类型



优先返回xml
加入xml依赖以后, 使用浏览器请求,为什么会返回xml数据, 而不是json?
分析
(1)浏览器请求后, 后端接收到的contentType值是 application/xhtml+xml, 为什么?
(2)因为请求头信息, 如下
1.Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
2.application/xhtml+xml 的权重比较高0.9, 后面的类型, 包括 */* 的权重是 0.8



注意事项和细节
1.Postman可以通过修改Accept的值, 来访会不同的数据格式.
2.对于浏览器, 我们无法修改其Accept的值, 怎么办? 解决方案: 开启基于请求参数的内容协商功能.
1)修改application.yml, 开启基于请求参数的内容协商功能.
spring:mvc:contentnegotiation:favor-parameter: true #开启基于请求参数的内容协商功能

2)浏览器测试

3)注意, 参数format是规定好的, 在开启请求参数的内容协商功能后, SpringBoot底层ParameterContentNegotiationStrategy会通过format来接收参数, 然后返回对应的媒体类型/数据格式, 当然format=xx这个xx 媒体类型/数据格式 是SpringBoot可以处理的才行, 不能乱写.

4)修改parameterName
spring:mvc:contentnegotiation:favor-parameter: true #开启基于请求参数的内容协商功能parameter-name: helloFormat #指定一个内容协商的参数名
5)测试

🔜 下一篇预告: [即将更新,敬请期待]
📚 目录导航 📚
- springboot系列一: springboot初步入门
- springboot系列二: sprintboot依赖管理
- springboot系列三: sprintboot自动配置
- springboot系列四: sprintboot容器功能
- springboot系列五: springboot底层机制实现 上
- springboot系列六: springboot底层机制实现 下
- springboot系列七: Lombok注解,Spring Initializr,yaml语法
- springboot系列八: springboot静态资源访问,Rest风格请求处理
- springboot系列九: 接收参数相关注解
- springboot系列十: 自定义转换器,处理JSON,内容协商
💬 读者互动 💬
在学习 Spring Boot 自定义转换器、处理 JSON 及内容协商的过程中,您有哪些新的发现或疑问?欢迎在评论区留言,让我们一起讨论吧!😊
相关文章:
springboot系列十: 自定义转换器,处理JSON,内容协商
文章目录 自定义转换器基本介绍应用实例查看源码注意事项和细节 处理JSON需求说明应用实例 内容协商基本介绍应用实例debug源码优先返回xml注意事项和细节 ⬅️ 上一篇: springboot系列九: 接收参数相关注解 🎉 欢迎来到 springboot系列十: 自定义转换器,…...
C++(new与delete操作符)
C中的new与delete new 与 delete定位new表达式 new 与 delete 在C中需要动态申请内存空间时需要使用 new 与 delete 这两个操作符 #include <iostream> using namespace std; int main() {int* p1 new int;//开辟一块int类型大小的空间给p1int* p2 new int(1);//开辟…...
STM32智能工业自动化监控系统教程
目录 引言环境准备智能工业自动化监控系统基础代码实现:实现智能工业自动化监控系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景:工业自动化与管理问题解决方案与优化收尾与总结 1. 引言 智能…...
WPF设置欢迎屏幕,程序启动过度动画
当主窗体加载时间过长,这时候基本都会想添加一个等待操作来响应用户点击,提高用户体验。下面我记录两个方法,一点拙见,仅供参考。 方法1:在App类中使用SplashScreen类。 protected override void OnStartup(StartupEventArgs e)…...
Flink实时开发添加水印的案例分析
在Flink中,处理时间序列数据时,通常需要考虑事件时间和水印(watermarks)的处理。以下是修改前后的代码对比分析: 修改前的代码: val systemDS unitDS.map(dp > {dp.setDeviceCode(DeviceCodeEnum.fro…...
收银系统源码-线上商城diy装修
线下线上一体化收银系统越来越受门店重视,尤其是连锁多门店,想通过线下线上相互带动,相互引流,提升门店营业额。商城商城如何装修呢? 1.收银系统开发语言 核心开发语言: PHP、HTML5、Dart后台接口: PHP7.3后合管理网…...
Linux中nohup(no hang up)不挂起,用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。
nohup的英文全称是 no hang up,即“不挂起”。这个命令在Linux或Unix系统中非常有用,主要用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。默认情况下(非重定向时),nohup会将输出写入一…...
【.NET全栈】ASP.NET开发Web应用——站点导航技术
文章目录 前言一、站点地图1、定义站点地图文件2、使用SiteMapPath控件3、SiteMap类4、URL地址映射 二、TreeView控件1、使用TreeView控件2、以编程的方式添加节点3、使用TreeView控件导航4、绑定到XML文件5、按需加载节点6、带复选框的TreeView控件 三、Menu控件1、使用Menu控…...
docker 容器内部UI映射host
方法有很多, 目前我总计一个我自己尝试成功的方法,通过xpra。 Xpra可以看作是screen或tmux的图形版本,支持远程X11应用程序的显示和交互。 在远程服务器上,安装Xpra: sudo apt-get install xpra启动Xpra服务器会话&…...
数仓面试题——DWS层新增维度字段需求
前言 在数据仓库开发中,数据仓库的设计和维护一直是一个备受关注的话题。随着业务需求的不断变化,数据仓库的结构也需要随之调整。 面试过程中,多次被提问:当DWS构建好后,突然来了一个新的需求,需要添加某个…...
Qt实现MDI应用程序
本文记录Qt实现MDI应用程序的相关操作实现 目录 1.MDM模式下窗口的显示两种模式 1.1TabbedView 页签化显示 1.2 SubWindowView 子窗体显示 堆叠cascadeSubWindows 平铺tileSubWindows 2.MDM模式实现记录 2.1. 窗体继承自QMainWindow 2.2.增加组件MdiArea 2.3.定义统一…...
逆向案例二十六——webpack自执行函数是完整的,但我们只需要加载器,某职业技术学校登陆密码逆向
网址:统一身份认证平台 找到登陆包,搜索找到加密位置。 找到加密位置,打上断点 分析,E就是加密结果 进入n.i函数,就是t.i,看一下这个函数,传一个值,然后不变的返回,所以没什么意义 …...
容器安全最佳实践和工具
容器安全最佳实践和工具 什么是容器安全 容器安全是指保护容器化应用程序和基础设施免受潜在威胁和攻击的措施和策略。容器化技术(如Docker、Kubernetes)使得应用程序能够在隔离的环境中运行,这既提供了灵活性,也引入了新的安全…...
牛客周赛 Round 51
目录 A.小红的同余 B.小红的三倍数 C.小红充电 D.小红的gcd E.小红走矩阵 F.小红的数组 这次周赛题目比较简单,算法题也基本上是板子题,出得很好(~ ̄▽ ̄)~ A.小红的同余 思路:签到题&am…...
【Linux】详解加锁实现线程互斥
一、多线程不加线程互斥可能会引发的问题 下面是一个抢标逻辑。抢票为什么会抢到负数:假设当票数为1时,此时四个进程的判断条件tickets都大于0,都会进入抢票操作,第一个进程抢完票以后tickets0并写回内存,第二个进程再…...
Java学习高级四
JDK8开始,接口新增了三种形式的方法 接口的多继承 内部类 成员内部类 静态内部类 局部内部类 匿名内部类 import javax.swing.*; import java.awt.event.ActionEvent;public class Test {public static void main(String[] args) {// 扩展 内部类在开发中的真实使用…...
mmc-utils 的 MMC 测试工具
MMC 工具介绍 有一个名为 mmc-utils 的 MMC 测试工具,由 Ulf Hansson 维护,您可以在以下公共 git 存储库中找到它: mmc/mmc-utils.git - Unnamed repository; edit this file description to name the repository. 功能 mmc-utils 工具可以…...
使用Python Turtle绘制圣诞树和装饰
简介(❤ ω ❤) 在这篇文章中,我们将探索如何使用Python的Turtle模块来绘制一个充满节日气氛的圣诞树,以及一些可爱的装饰品。Turtle是一个受Logo语言启发的图形库,非常适合初学者学习编程和创建图形。 码农不是吗喽(大学生版&…...
非常好的新版网盘系统,是一款PHP网盘与外链分享程序,支持文件预览
这是一款PHP网盘与外链分享程序,支持所有格式文件的上传, 可以生成文件外链、图片外链、音乐视频外链,生成外链同时自动生成相应的UBB代码和HTML代码, 还可支持文本、图片、音乐、视频在线预览,这不仅仅是一个网盘&a…...
针对【module_or_function】的单元测试,全面覆盖可能的【edge_cases】
针对【module_or_function】的单元测试,全面覆盖可能的【edge_cases】 编写单元测试是为了验证代码模块或函数的正确性和鲁棒性。对于module_or_function,首先需要确定这个模块或函数的具体功能和预期输入范围。一个好的单元测试应该包括以下几个步骤&a…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
