Spring Boot 从“会用”到“精通”:Converter 原理

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) 追加
相关推荐
Clf丶忆笙1 小时前
搭建支持多语言开发的Quarkus环境:Java、Kotlin与Scala全栈指南
java·开发语言·云原生·kotlin·scala·quarkus
java1234_小锋1 小时前
LangChain4j 开发Java Agent智能体- 对话与提示词工程(Prompt)
java·开发语言·prompt·langchain4j
zzz_23681 小时前
【Redis】Redis 数据结构与 Spring Boot 集成
数据结构·spring boot·redis
v***59831 小时前
SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由
java·spring cloud·gateway
MrMonkeyHou1 小时前
Java微服务架构中的双剑合璧:Nacos与Gateway深度解析
java·微服务·架构·gateway
普通网友1 小时前
【python】pyspark.errors.exceptions.base.PySparkRuntimeError [JAVA_GATEWAY_EXITED] Java gateway proce
java·python·gateway
许彰午10 小时前
14_Java泛型完全指南
java·windows·python
智慧物业老杨10 小时前
司法绿色通道下的物业纠纷数智化解决方案——基于“三优先“机制的全流程技术落地实践
java·django
2601_9611940210 小时前
2026初级会计实务公式总结大全|计算题公式手册PDF
java·spring·eclipse·pdf·tomcat·hibernate