HttpMessageConverter 是 Spring MVC(以及 Spring WebFlux)中用于在 HTTP 请求/响应与 Java 对象之间进行转换的核心接口。它解决了 HTTP 协议传输的是字节流,而 Java 业务代码处理的是对象这一矛盾,实现了数据的序列化(Object -> HTTP Body)和反序列化(HTTP Body -> Object)。
以下是关于 messageConverters 的核心机制、默认实现、匹配规则及自定义配置的详细解析。
1. 核心职责与接口定义
HttpMessageConverter 接口定义了五个关键方法,决定了转换器是否能处理特定类型的数据以及如何读写数据:
public interface HttpMessageConverter<T> {
// 1. 判断是否支持将 HTTP 请求体读取为指定类型的 Java 对象
boolean canRead(Class<?> clazz, MediaType mediaType);
// 2. 判断是否支持将指定类型的 Java 对象写入 HTTP 响应体
boolean canWrite(Class<?> clazz, MediaType mediaType);
// 3. 获取该转换器支持的媒体类型列表(如 application/json, text/plain)
List<MediaType> getSupportedMediaTypes();
// 4. 从输入消息中读取并转换为对象(反序列化)
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 5. 将对象写入输出消息(序列化)
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
主要应用场景:
@RequestBody:Spring 使用 canRead 和 read 方法将请求体 JSON/XML 转换为 Controller 方法的参数对象。
@ResponseBody / ResponseEntity:Spring 使用 canWrite 和 write 方法将 Controller 返回的对象序列化为 JSON/XML 写入响应体。
2. 默认注册的转换器
在 Spring Boot 或启用 <mvc:annotation-driven/> 的环境中,RequestMappingHandlerAdapter 会自动注册一组默认的 HttpMessageConverter。常见的默认实现包括:
| 转换器类名 | 支持格式 | 依赖库 | 说明 |
| ByteArrayHttpMessageConverter | application/octet-stream | 无 | 处理 byte[]数组 |
| StringHttpMessageConverter | text/plain, */ * | 无 | 处理 String 类型,默认 UTF-8 |
| ResourceHttpMessageConverter | */* | 无 | 处理 Resource 类型(如文件下载) |
| SourceHttpMessageConverter | application/xml | JDK XML | 处理 javax.xml.transform.Source |
| AllEncompassingFormHttpMessageConverter | application/x-www-form-urlencoded | 无 | 处理表单数据 |
| MappingJackson2HttpMessageConverter | application/json | Jackson | 最常用,处理 JSON 转换 |
| Jaxb2RootElementHttpMessageConverter | application/xml | JAXB | 处理 XML 转换(若 classpath 存在 JAXB) |
注意:MappingJackson2HttpMessageConverter 是前后端分离开发中最核心的转换器,只要项目中引入了 jackson-databind 依赖,Spring Boot 就会自动配置它。
3. 匹配与执行流程
当请求到达 Controller 时,RequestMappingHandlerAdapter 会遍历已注册的 messageConverters 列表,遵循以下逻辑:
读取阶段(Request -> Object):
检查请求头的 Content-Type。
遍历转换器,调用 canRead(targetClass, mediaType)。
第一个返回 true 的转换器将被选中,执行 read() 方法。
若无匹配转换器,抛出 HttpMediaTypeNotSupportedException (415)。
写入阶段(Object -> Response):
检查请求头的 Accept(内容协商)或 @RequestMapping 中的 produces 属性。
遍历转换器,调用 canWrite(returnValue.getClass(), mediaType)。
第一个返回 true 的转换器将被选中,执行 write() 方法。
若无匹配转换器,抛出 HttpMediaTypeNotAcceptableException (406)。
关键点:转换器的注册顺序非常重要。Spring 默认按顺序查找,一旦找到匹配的即停止。因此,自定义转换器通常需要通过配置调整顺序,或者确保其 canRead/canWrite 逻辑足够精确,避免拦截默认行为。
4. 自定义 HttpMessageConverter
若需支持非标准格式(如自定义二进制协议、特定加密格式等),可实现 HttpMessageConverter 接口。
示例:自定义转换器
public class CustomTextMessageConverter implements HttpMessageConverter<String> {
private static final MediaType CUSTOM_MEDIA_TYPE = new MediaType("application", "custom-text");
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return String.class.isAssignableFrom(clazz) && CUSTOM_MEDIA_TYPE.includes(mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return String.class.isAssignableFrom(clazz) && CUSTOM_MEDIA_TYPE.includes(mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(CUSTOM_MEDIA_TYPE);
}
@Override
public String read(Class<? extends String> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// 自定义读取逻辑,例如解密
InputStream body = inputMessage.getBody();
return new String(body.readAllBytes(), StandardCharsets.UTF_8).toUpperCase();
}
@Override
public void write(String str, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 自定义写入逻辑,例如加密
OutputStream body = outputMessage.getBody();
body.write(("Custom: " + str).getBytes(StandardCharsets.UTF_8));
}
}
注册自定义转换器
在 Spring Boot 中,推荐通过实现 WebMvcConfigurer 接口来注册,这样可以保留默认转换器,仅添加新的转换器或调整顺序。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 方式1:添加到列表末尾(优先级最低)
converters.add(new CustomTextMessageConverter());
// 方式2:添加到列表头部(优先级最高,可能覆盖默认行为,需谨慎)
// converters.add(0, new CustomTextMessageConverter());
}
// 注意:如果使用 extendMessageConverters,可以访问并修改默认列表
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 例如:移除默认的 StringHttpMessageConverter,替换为自定义的
// converters.removeIf(c -> c instanceof StringHttpMessageConverter);
// converters.add(new CustomStringConverter());
}
}
5. 常见问题与排查
返回字符串带双引号:
现象:Controller 返回 String,前端收到 "hello" 而不是 hello。
原因:MappingJackson2HttpMessageConverter 优先级高于 StringHttpMessageConverter,它将 String 当作 JSON 字符串处理,因此加上了引号。
解决:调整转换器顺序,确保 StringHttpMessageConverter 在 Jackson 之前;或在 Controller 上设置 produces = "text/plain"。
415 Unsupported Media Type:
原因:请求头 Content-Type 与任何转换器的 canRead 不匹配。例如,前端发送 application/json,但后端缺少 Jackson 依赖或转换器未正确注册。
406 Not Acceptable:
原因:响应头 Accept 要求的内容格式,后端没有任何转换器支持 canWrite。例如,前端要求 application/xml,但后端只配置了 JSON 转换器。
RestTemplate 中的 NPE:
若在非 Spring Boot 环境手动创建 RestTemplate,需确保调用了 restTemplate.setMessageConverters(...) 或使用了带默认构造器的变体,否则可能因转换器列表为空导致空指针异常。
总结
messageConverters 是 Spring MVC 实现 RESTful 风格和数据绑定的基石。理解其匹配机制(canRead/canWrite)和执行顺序,对于解决数据格式错误、自定义协议支持以及性能优化至关重要。在大多数常规开发中,默认的 Jackson 转换器已能满足需求;仅在处理特殊数据格式时,才需要自定义实现。

579

被折叠的 条评论
为什么被折叠?



