九,自定义转换器详细操作(附+详细源码解析)
文章目录
- 九,自定义转换器详细操作(附+详细源码解析)
- 1. 基本介绍
- 2. 准备工作
- 3. 自定义转换器操作
- 4. 自定义转换器的注意事项和细节
- 5. 总结:
- 6. 最后:
Spring Boot 定义对象参数支持自动封装
- 在开发中,Spring Boot 在响应客户端请求时,也支持自定义对象参数
- 完成自动类型转换与格式化
- 支持级联封装
1. 基本介绍
- Spring Boot 在响应客户端请求时,将提交的数据封装成对象时,使用了内置的转换器。
- Spring Boot也支持自定义转换器,这个内置的转换器在debug的时候,可以看到,后面给大家演示,提供了 124个内置转换器,看下源码 GenericConverter ——> ConvertiblePair
2. 准备工作
在 pom.xml 文件当中配置相关的 jar
依赖。如下图所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rainbowsea</groupId>
<artifactId>springboot_parameters</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 导入SpringBoot 父工程-规定写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<!-- 导入web项目场景启动器:会自动导入和web开发相关的jar包所有依赖【库/jar】-->
<!-- 后面还会在说明spring-boot-starter-web 到底引入哪些相关依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
对应需要测试的 Bean 对象/POJO对象,两个 Car 和 Monster ,这里我们使用上 Lombok 插件,关于 Lombok的详细内容,大家可以移步至:✏️✏️✏️ 六,Spring Boot 容器中 Lombok 插件的详细使用,简化配置,提高开发效率-CSDN博客
package com.rainbowsea.springboot.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Car {
private String name;
private Double price;
}
package com.rainbowsea.springboot.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
public class Monster {
private Integer id;
private String name;
private Integer age;
private Boolean isMarried;
private Date birth;
private Car car;
}
创建对应的controller 控制器,对应的请求路径的处理。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
//@RestController // @Controller + @ResponseBody
@Controller
public class ParameterController {
// 处理添加 monster 的方法
@PostMapping("/savemonster")
@ResponseBody
public String saveMonster(Monster monster) {
System.out.println("monster-" + monster);
return "success";
}
}
对应前端 ,浏览器提交数据的 表单 html 页面的编写内容。
自定义转换器关联 Car 对象,使用
,
号间隔。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加妖怪</title>
</head>
<body>
<h1>添加妖怪-坐骑[测试封装POJO:]</h1>
<form action="/savemonster" method="post">
编号: <input name="id" value="100"><br/>
姓名: <input name="name" value="牛魔王"><br/>
年龄: <input name="age" value="500"><br/>
婚否: <input name="isMarried" value="true"><br/>
生日: <input name="birth" value="2000/11/11"><br/>
<!--使用自定义转换器关联Car,字符串整体提交,使用,号间隔-->
坐骑: <input name="car" value="避水金晶兽,666.6">
<!-- 坐骑名称: <input name="car.name" value="法拉利"><br/>
坐骑价格: <input name="car.price" value="999"><br/>-->
<input type="submit" value="保存">
</form>
</body>
</html>
编写 Spring Boot 的应用程序的启动场景
package com.rainbowsea.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
3. 自定义转换器操作
编写 自定义转换器:将 前端的“String ”类型的数据转换为 后端“Car” 类型的数据 。
package com.rainbowsea.springboot.config;
import com.rainbowsea.springboot.bean.Car;
import com.rainbowsea.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 标志配置类
public class WebConfig {
@Bean // 注如到 ioc容器当中
public 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>() { // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型
@Override
public Car convert(String source) { // source 就是传入的字符串,避水金晶兽
// 这里就加入你的自定义的转换业务处理
//if(StringUtils) 或者 ObjectUtils 工具类都行。
if(!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] split = source.split(",");
car.setName(split[0]);
car.setPrice(Double.parseDouble(split[1])); // 将String类型的数据转换为 Double 类型的数据
return car;
}
return null;
}
});
}
}
}
}
运行测试:
上面是使用了 匿名的内部类 ,我们也可以不使用匿名内部类,分开来写也是可以的。
package com.rainbowsea.springboot.config;
import com.rainbowsea.springboot.bean.Car;
import com.rainbowsea.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 标志配置类
public class WebConfig {
@Bean // 注如到 ioc容器当中
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 老师解读
* 1. 在addFormatters方法中,增加一个自定义的转换器
* 2. 增加自定义转换器 String->car
* 3. 增加的自定义转换器会注册到converters容器中
* 4. converters 底层结构时 ConcurrentHashMap 内置了124个转换器
* 5. 一会老师会使用 debug 来看到这些转换器
*/
Converter<String,Car> converter = new Converter<String, Car>() { // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型
@Override
public Car convert(String source) { // source 就是传入的字符串,避水金晶兽
// 这里就加入你的自定义的转换业务处理
//if(StringUtils)
if(!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] split = source.split(",");
car.setName(split[0]);
car.setPrice(Double.parseDouble(split[1]));
return car;
}
return null;
}
};
// 添加自定义的转换器
registry.addConverter(converter);
}
}
}
}
运行测试:
注意:自定义转换器可以添加多个,默认 Spring Boot 内置的转换器是 124 个
这里,我们再添加一个转换器:将 前端的"Spring ’ 类型的数据,转换成 Monster 类型的数据,这里主要演示的是,可以添加多个自定义转换器,所以,自定义转换器内部的业务,我就不处理编写的,直接返回 null了。
package com.rainbowsea.springboot.config; import com.rainbowsea.springboot.bean.Car; import com.rainbowsea.springboot.bean.Monster; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration // 标志配置类 public class WebConfig { @Bean // 注如到 ioc容器当中 public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurer() { @Override public void addFormatters(FormatterRegistry registry) { /** * 老师解读 * 1. 在addFormatters方法中,增加一个自定义的转换器 * 2. 增加自定义转换器 String->car * 3. 增加的自定义转换器会注册到converters容器中 * 4. converters 底层结构时 ConcurrentHashMap 内置了124个转换器 * 5. 一会老师会使用 debug 来看到这些转换器 */ Converter<String,Car> converter = new Converter<String, Car>() { // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型 @Override public Car convert(String source) { // source 就是传入的字符串,避水金晶兽 // 这里就加入你的自定义的转换业务处理 //if(StringUtils) if(!ObjectUtils.isEmpty(source)) { Car car = new Car(); String[] split = source.split(","); car.setName(split[0]); car.setPrice(Double.parseDouble(split[1])); return car; } return null; } }; // 第2个自定义转换器 // 还可以增加更多的转换器 Converter<String,Monster> converter2 = new Converter<String, Monster>() { // // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型 @Override public Monster convert(String source) { // source 就是传入的字符串,避水金晶兽 // 这里就加入你的自定义的转换业务处理 //if(StringUtils) if(!ObjectUtils.isEmpty(source)) { Monster monster = new Monster(); String[] split = source.split(","); return monster; } return null; } }; // 添加自定义的转换器 registry.addConverter(converter); registry.addConverter(converter2); } } } }
这里我们进行一个 Debug 进行追踪源码:看看是不是真的添加上了2个我们自己编写的转换器,记住Spring Boot 默认是 124个,这里我们添加了 2个就是 126个了
4. 自定义转换器的注意事项和细节
从上面的我们的Debug分析可以知道的 Spring Boot 是使用 Map集合存储我们的转换器的,而对应 Map 当中的 key 就是我们转换的内容信息 。
=
而 Map 集合当中 key 是唯一的不可以重复的,所以,当我们自定义了多个转换内容类型是重复(一样)的 转换器的时候,会覆盖掉,我们前面转换内容信息一样的 转换器。
如下:我们再定义一个“将 前端的“String ”类型的数据转换为 后端“Car” 类型的数据 的转换器”。
package com.rainbowsea.springboot.config;
import com.rainbowsea.springboot.bean.Car;
import com.rainbowsea.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 标志配置类
public class WebConfig {
@Bean // 注如到 ioc容器当中
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 老师解读
* 1. 在addFormatters方法中,增加一个自定义的转换器
* 2. 增加自定义转换器 String->car
* 3. 增加的自定义转换器会注册到converters容器中
* 4. converters 底层结构时 ConcurrentHashMap 内置了124个转换器
* 5. 一会老师会使用 debug 来看到这些转换器
*/
Converter<String,Car> converter = new Converter<String, Car>() { // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型
@Override
public Car convert(String source) { // source 就是传入的字符串,避水金晶兽
// 这里就加入你的自定义的转换业务处理
//if(StringUtils)
if(!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] split = source.split(",");
car.setName(split[0]);
car.setPrice(Double.parseDouble(split[1]));
return car;
}
return null;
}
};
// 添加转换器converter3 重复了
Converter<String,Car> converter3 = new Converter<String, Car>() { // 第一个参数是要转换的类型,第二个参数是想要转换成什么类型
@Override
public Car convert(String source) { // source 就是传入的字符串,避水金晶兽
// 这里就加入你的自定义的转换业务处理
//if(StringUtils)
if(!ObjectUtils.isEmpty(source)) {
System.out.println("source-" + source);
}
return null;
}
};
// 第2个自定义转换器
// 还可以增加更多的转换器
Converter<String,Monster> converter2 = new Converter<String, Monster>() { //
// 第一个参数是要转换的类型,第二个参数是想要转换成什么类型
@Override
public Monster convert(String source) { // source 就是传入的字符串,避水金晶兽
// 这里就加入你的自定义的转换业务处理
//if(StringUtils)
if(!ObjectUtils.isEmpty(source)) {
Monster monster = new Monster();
String[] split = source.split(",");
return monster;
}
return null;
}
};
// 添加自定义的转换器
registry.addConverter(converter);
registry.addConverter(converter2);
registry.addConverter(converter3);
}
};
}
}
从上述结果上来看,我们可以十分清楚的明白了。
因为:因为Spring Boot是用 Map存储我们的转换器的,而Map其中的 key 存储的是我们转换器的内容信息,Spring Boot以我们转换的内容信息,作为 key 唯一,不可重复。所以一旦我们出现了,转换内容信息是一样的转换器,那么前面的转换器会被最后一个重复的转换器给替换掉。
5. 总结:
- Spring Boot 内置有 124 个转换器。我们可以自定义多个转换器。
- Spring Boot是用 Map 存储我们的转换器的,而Map其中的 key 存储的是我们转换器的内容信息,Spring Boot以我们转换的内容信息,作为 key 唯一,不可重复。所以一旦我们出现了,转换内容信息是一样的转换器,那么前面的转换器会被最后一个重复的转换器给替换掉。
- 实际开发中 Spring Boot 内置的 124个转换器,以及Spring Boot的自动封装对象的机制,足够我们实际开发中使用了。
6. 最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”