Converter 原理 ------ 彻底分清三种"转换器"
一、是什么 ------ Spring 中到底有几种 Converter?
这是 Spring 学习中最容易混淆的概念之一。我们在 Spring 体系中会遇到三个名字里带 "Converter" 的东西 ,但它们分属两个完全不同的阵营:
| 概念 | 全称 | 阵营 | 处理什么 |
|---|---|---|---|
| Converter<S, T> | org.springframework.core.convert.converter.Converter |
KV 参数阵营 | 简单类型一对一转换 |
| HttpMessageConverter | org.springframework.http.converter.HttpMessageConverter |
流处理阵营 | HTTP Body ↔ Java 对象 |
| WebDataBinder 中的 Converter | 同上(Converter<S, T>) | KV 参数阵营 | 就是 Converter<S, T>,WebDataBinder 调用它 |
核心区分 :前三个概念实际上就两个阵营------**HttpMessageConverter(流处理)**和 Converter<S, T>(KV参数)。WebDataBinder 是"包工头",它自己不转换,而是调用 ConversionService 中注册的 Converter<S, T>。
二、为什么 ------ 为什么需要这两套体系?
1. 它们面对的问题完全不同
| 流处理阵营 | KV 参数阵营 | |
|---|---|---|
| 输入形式 | 一整坨 JSON/XML 字节流 | 扁平的 key=value 字符串对 |
| 处理方式 | 整体反序列化(依赖 Jackson/Gson 等库) | 逐字段类型转换 |
| 转换对象 | HTTP Body ↔ 完整 Java 对象 | 单个 String → Integer/Date/Enum 等 |
| 典型场景 | @RequestBody / @ResponseBody |
@RequestParam / @PathVariable / 表单 |
2. 为什么 HttpMessageConverter 不调用 Converter<S, T>?
这是闲聊中反复追问的问题。答案是:绝对不会互相调用。
当你用 @RequestBody 接收 JSON 时:
MappingJackson2HttpMessageConverter接管- 内部直接委托给 Jackson 的
ObjectMapper做反序列化 - 如果 JSON 里有
"birth": "2000-01-01",是 Jackson 的JsonDeserializer在负责 String → Date 的转换 - 它完全不知道 Spring 容器里有没有注册
Converter<String, Date>
反过来,当你在 URL 参数中传 ?birth=2000-01-01:
WebDataBinder接管- 它一定会去
ConversionService中查找Converter<String, Date> - 它完全不知道有没有 Jackson 在类路径中
两套体系井水不犯河水。
三、怎么做 ------ Converter<S, T> 详解
1. 接口定义
java
// org.springframework.core.convert.converter.Converter
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
极其简单:一个方法,S 转 T。
2. ConversionService ------ 中央注册中心
Spring 容器启动时,会初始化一个 DefaultConversionService,预装了上百个内置 Converter:
| 内置 Converter | 做什么 |
|---|---|
StringToNumberConverterFactory |
String → Integer/Long/Float/Double... |
StringToBooleanConverter |
String → Boolean |
StringToEnumConverterFactory |
String → 任意 Enum |
StringToDateConverter |
String → java.util.Date |
StringToCollectionConverter |
String → List/Set |
ObjectToObjectConverter |
对象间转换(如通过构造函数) |
| ...... | ...... |
3. Converter<S, T> 的四大触发场景
从闲聊中我们深入讨论过,Converter<S, T> 不仅用于 Web 层,而是贯穿整个 Spring 生态:
场景一:Spring MVC Web 数据绑定(最常见)
?createTime=2026-05-24 → WebDataBinder → ConversionService → StringToDateConverter → Date 对象
场景二:Spring IoC Bean 属性注入(最隐蔽)
@Value("${server.port}") // 配置文件里 "8080" 是 String
private Integer port; // 触发 Converter<String, Integer>
场景三:Spring Data 数据库映射
Redis 存储 Enum → 触发 Converter<Enum, String>
数据库 Timestamp → 触发 Converter<Timestamp, LocalDateTime>
场景四:SpEL 表达式计算
@PreAuthorize 中不同类型数据对比时,SpEL 自动调用 Converter 统一类型
四、怎么做 ------ HttpMessageConverter 详解
1. 接口定义
java
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType); // 能读吗?
boolean canWrite(Class<?> clazz, MediaType mediaType); // 能写吗?
List<MediaType> getSupportedMediaTypes(); // 支持哪些 MIME 类型?
T read(Class<? extends T> clazz, HttpInputMessage msg); // 读取(请求 → 对象)
void write(T t, MediaType contentType, HttpOutputMessage msg); // 写入(对象 → 响应)
}
2. Spring Boot 默认注册的 HttpMessageConverter
| Converter | 处理类型 | 依赖 |
|---|---|---|
MappingJackson2HttpMessageConverter |
JSON ↔ 对象 | Jackson |
StringHttpMessageConverter |
String ↔ text/plain |
JDK 内置 |
ByteArrayHttpMessageConverter |
byte[] ↔ */* |
JDK 内置 |
FormHttpMessageConverter |
表单数据 | JDK 内置 |
AllEncompassingFormHttpMessageConverter |
智能探测 | 根据 classpath |
3. AllEncompassingFormHttpMessageConverter 的智能探测
这是 Spring Boot "引入依赖就自动支持"的核心机制:
java
static {
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
// ★ 探测 Jackson 是否在 classpath
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
public AllEncompassingFormHttpMessageConverter() {
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter()); // ← 自动添加 JSON 支持
}
}
这就解释了为什么只要引入 spring-boot-starter-json,应用就自动支持返回 JSON------不需要任何额外配置。
五、自定义 Converter 实战
1. 自定义 Converter<String, Pet>
将前端传来的字符串 "阿猫,3" 直接转成 Pet 对象:
java
// 源码位置:springboot2-master/boot-05-web-01/.../config/WebConfig.java
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
if (!StringUtils.isEmpty(source)) {
Pet pet = new Pet();
String[] split = source.split(","); // 按逗号拆分
pet.setName(split[0]); // "阿猫" → name
pet.setAge(Integer.parseInt(split[1])); // "3" → age
return pet;
}
return null;
}
});
}
};
}
注册后 ,当参数绑定遇到 pet=阿猫,3 且目标类型是 Pet 时,ConversionService 就会自动调用这个自定义 Converter。
2. 自定义 HttpMessageConverter(GuiguMessageConverter)
支持一种自定义协议格式 application/x-guigu,把 Person 对象序列化为 "zhangsan;28;2026-01-01" 这种格式:
java
// 源码位置:springboot2-master/boot-05-web-01/.../converter/GuiguMessageConverter.java
public class GuiguMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-guigu"); // ★ 自定义 MIME 类型
}
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException {
// 自定义协议:属性值用分号分隔
String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
// read() 不实现(只输出,不输入)
}
注册到 Spring MVC:
java
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter()); // 追加,不是覆盖!
}
注意 :用
extendMessageConverters(追加)而不是configureMessageConverters(覆盖),否则会导致默认的 JSON/XML 转换器全部失效。
六、三种"Converter"的终极区分
这是从闲聊中反复打磨出的结论,也是 Spring Web 开发中最核心的概念边界:
┌──────────────────────────────────────────────────────────┐
│ 转换器体系总览 │
├─────────────────────────┬────────────────────────────────┤
│ 流处理阵营 │ KV 参数阵营 │
│ HttpMessageConverter │ Converter<S, T> │
│ │ + WebDataBinder(调度者) │
├─────────────────────────┼────────────────────────────────┤
│ 处理 HTTP Body │ 处理 URL 参数 / 表单 / 请求头 │
│ 整体序列化/反序列化 │ 逐字段类型转换 │
│ @RequestBody / │ @RequestParam / @PathVariable │
│ @ResponseBody │ / POJO 表单绑定 │
├─────────────────────────┼────────────────────────────────┤
│ 依赖第三方库 │ 依赖 ConversionService │
│ Jackson / Gson / ... │ (Spring Core 内置) │
├─────────────────────────┼────────────────────────────────┤
│ write() / read() │ convert() │
│ 操作 Stream │ 操作单个值 │
├─────────────────────────┼────────────────────────────────┤
│ ★ 不会调用 Converter │ ★ 不关心中间格式 │
│ 完全独立体系 │ (JSON/XML 等) │
└─────────────────────────┴────────────────────────────────┘
记住一个口诀:
遇到一整坨(JSON/XML)→ 找 HttpMessageConverter (流处理阵营)
遇到散装的(?name=abc&age=24)→ 找 WebDataBinder → 它去找 Converter<S, T>(KV 参数阵营)
七、总结
| 核心点 | 说明 |
|---|---|
| Converter<S, T> | 简单类型一对一转换(String→Integer),注册在 ConversionService,被 WebDataBinder 调用 |
| HttpMessageConverter | HTTP Body 整体转换(JSON↔对象),依赖 Jackson/Gson,被 MethodProcessor 调用 |
| 两者关系 | 井水不犯河水,互不调用 |
| WebDataBinder | 包工头,调用 ConversionService 中的 Converter 做类型转换 |
| 自定义 Converter | addFormatters(registry) 注册 |
| 自定义 MessageConverter | extendMessageConverters(converters) 追加 |