⬅️ 上一篇: springboot系列九: 接收参数相关注解
🎉 欢迎来到 springboot系列十: 自定义转换器,处理JSON,内容协商 🎉
在本篇文章中,我们将探讨如何在 Spring Boot 中实现自定义转换器、处理 JSON 数据以及进行内容协商。通过掌握这些技术,您可以更灵活地处理不同格式的数据,提高应用程序的兼容性和用户体验。
🔧 本篇需要用到的项目: springbootweb项目
自定义转换器
基本介绍
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
@Bean
public WebMvcConfigurer webMvcConfigurer() {
//整个是WebMvcConfigurer接口的匿名内部类
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 解读
* 1.在addFormatters方法中, 增加一个自定义转换器
* 2.增加自定义转换器 String -> Car
* 3.增加的自定义转换器会注册到 converters 容器中
* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]
* 5.一会我们debug查看这些转换器
*/
registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类
@Override
public 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
@Bean
public WebMvcConfigurer webMvcConfigurer() {
//整个是WebMvcConfigurer接口的匿名内部类
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 解读
* 1.在addFormatters方法中, 增加一个自定义转换器
* 2.增加自定义转换器 String -> Car
* 3.增加的自定义转换器会注册到 converters 容器中
* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]
* 5.一会我们debug查看这些转换器
*/
/*registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类
@Override
public 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接口的匿名内部类
@Override
public 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
@Bean
public WebMvcConfigurer webMvcConfigurer() {
//整个是WebMvcConfigurer接口的匿名内部类
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 解读
* 1.在addFormatters方法中, 增加一个自定义转换器
* 2.增加自定义转换器 String -> Car
* 3.增加的自定义转换器会注册到 converters 容器中
* 4.converters 底层结构是 ConcurrentHashMap, 内置有124个转换器[不同版本个数不一样~]
* 5.一会我们debug查看这些转换器
*/
/*registry.addConverter(new Converter<String, Car>() {//传入Converter接口的匿名内部类
@Override
public 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接口的匿名内部类
@Override
public 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>() {
@Override
public 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接口的匿名内部类
@Override
public 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>() {
@Override
public Monster convert(String source) {
return null;
}
};
//第3个自定义的转换器
Converter<String, Car> zzwConverter3 = new Converter<String, Car>() {
@Override
public 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")
@ResponseBody
public 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 及内容协商的过程中,您有哪些新的发现或疑问?欢迎在评论区留言,让我们一起讨论吧!😊