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命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...
ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
