StringHttpMessageConverter 详解:Spring MVC 中字符串读写的默认转换器

StringHttpMessageConverter 详解:Spring MVC 中字符串读写的默认转换器

一、它是什么

StringHttpMessageConverter 是 Spring MVC 内置的 HttpMessageConverter 实现,专门负责 String 类型与 HTTP 请求/响应体之间的转换。

它的职责非常单一:

  • 读:把请求体按字符集解码成 String。
  • 写:把 String 按字符集编码写入响应体。

它不处理 JSON,不处理对象,只处理字符串。但因为 String 在 Web 开发中极其常见,这个转换器的行为直接影响到很多接口的返回结果。

二、接口实现

java 复制代码
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

    private volatile List<Charset> availableCharsets;

    public StringHttpMessageConverter() {
        this(DEFAULT_CHARSET);
    }

    public StringHttpMessageConverter(Charset defaultCharset) {
        super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return String.class == clazz;
    }

    @Override
    protected String readInternal(Class<? extends String> clazz, 
                                  HttpInputMessage inputMessage) throws IOException {
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }

    @Override
    protected void writeInternal(String str, HttpOutputMessage outputMessage) 
            throws IOException {
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }
}

关键点:

成员 含义
DEFAULT_CHARSET 默认字符集是 ISO_8859_1,不是 UTF-8
supports 只支持 String.class
MediaType.TEXT_PLAIN 默认媒体类型是 text/plain
MediaType.ALL 同时接受所有媒体类型作为通配
readInternal 按字符集把请求体解码成字符串
writeInternal 按字符集把字符串编码写入响应体

三、它在转换器列表中的位置

Spring MVC 默认注册的转换器顺序大致是:

复制代码
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
...
MappingJackson2HttpMessageConverter
...

StringHttpMessageConverter 排在 Jackson 之前。

这意味着:当返回值是 String 时,StringHttpMessageConverter 会先被匹配,Jackson 没有机会处理。

这就是为什么:

java 复制代码
@RestController
public class TestController {

    @GetMapping("/text")
    public String text() {
        return "hello";
    }
}

响应体是:

复制代码
hello

而不是:

json 复制代码
"hello"

如果希望返回带双引号的 JSON 字符串,必须显式声明 produces = "application/json",或者把返回值包装成对象。

四、什么时候会被选中

1. 写响应时

触发条件:

  • 方法返回值类型是 String。
  • 客户端 Accept 头与 text/plain 或 */* 匹配。
  • 没有其他更高优先级的转换器处理 String。

典型场景:

java 复制代码
@GetMapping("/hello")
public String hello() {
    return "hello world";
}

2. 读请求时

触发条件:

  • 方法参数是 String,并且使用 @RequestBody。
  • 请求头 Content-Type 是 text/plain 或兼容类型。
java 复制代码
@PostMapping("/echo")
public String echo(@RequestBody String body) {
    return body;
}

如果客户端发送 Content-Type: application/json,StringHttpMessageConverter 也能匹配,因为构造方法里注册了 MediaType.ALL。但实际项目里不推荐用 String 接 JSON,容易绕过 JSON 校验。

五、字符集问题

这是 StringHttpMessageConverter 最容易踩坑的地方。

1. 默认字符集是 ISO_8859_1

java 复制代码
public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

如果响应头没有明确指定 charset,转换器会用 ISO_8859_1 编码字符串。中文字符在这个字符集下无法表示,会被替换成 ?。

2. 什么时候会乱码

java 复制代码
@GetMapping("/chinese")
public String chinese() {
    return "你好";
}

如果响应头是:

复制代码
Content-Type: text/plain

没有 charset,浏览器按默认编码解析,可能显示乱码。

3. 解决方式

方式一:在请求映射上声明 produces

java 复制代码
@GetMapping(value = "/chinese", produces = "text/plain;charset=UTF-8")
public String chinese() {
    return "你好";
}

方式二:配置 StringHttpMessageConverter 的默认字符集

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof StringHttpMessageConverter stringConverter) {
                stringConverter.setDefaultCharset(StandardCharsets.UTF_8);
            }
        }
    }
}

方式三:Spring Boot 中配置编码

yaml 复制代码
server:
  servlet:
    encoding:
      charset: UTF-8
      force: true

force: true 会强制请求和响应使用 UTF-8,但要注意它作用于整个请求链路,不只影响 StringHttpMessageConverter。

方式四:替换默认转换器

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }
}

add(0, ...) 把 UTF-8 版本的转换器放在最前面,覆盖默认的 ISO_8859_1 版本。

六、和 Jackson 转换器的对比

对比 StringHttpMessageConverter MappingJackson2HttpMessageConverter
处理类型 String 任意对象
媒体类型 text/plain、*/* application/json
优先级 高 低
返回 String 时 纯文本输出 不会轮到它
字符集默认 ISO_8859_1 UTF-8
典型用途 纯文本接口、简单返回值 JSON 接口

一句话记忆:String 先被字符串转换器截胡,Jackson 只处理非 String 对象。

七、完整示例

1. 纯文本返回

java 复制代码
@RestController
public class TextController {

    @GetMapping(value = "/text", produces = "text/plain;charset=UTF-8")
    public String text() {
        return "这是一段纯文本";
    }
}

响应:

复制代码
Content-Type: text/plain;charset=UTF-8

这是一段纯文本

2. 返回 JSON 字符串

如果确实希望返回带引号的 JSON 字符串:

java 复制代码
@GetMapping(value = "/json-string", produces = "application/json")
public String jsonString() {
    return "hello";
}

因为显式声明 produces = "application/json",StringHttpMessageConverter 的 canWrite 不匹配,Jackson 接手,输出:

json 复制代码
"hello"

3. 读取纯文本请求体

java 复制代码
@PostMapping(value = "/echo", consumes = "text/plain")
public String echo(@RequestBody String body) {
    return "收到:" + body;
}

客户端:

复制代码
POST /echo
Content-Type: text/plain;charset=UTF-8

你好

响应:

复制代码
收到:你好

八、常见问题

1. 返回 String 没有双引号

这是正常行为,不是 bug。StringHttpMessageConverter 把字符串当纯文本输出。需要 JSON 字符串时显式声明 produces = "application/json"。

2. 中文乱码

默认 ISO_8859_1 导致。解决方式是设置 produces 带 charset=UTF-8,或替换转换器的默认字符集。

3. 415 Unsupported Media Type

@RequestBody String 时,请求的 Content-Type 与转换器不匹配。检查客户端是否发送了 text/plain 或兼容类型。

4. 406 Not Acceptable

客户端 Accept 头不接受 text/plain。检查 Accept 头,或后端声明合适的 produces。

5. 自定义转换器被覆盖

如果自定义了 StringHttpMessageConverter 但没有放在列表前面,默认的 ISO_8859_1 版本可能仍然生效。使用 add(0, ...) 或 extendMessageConverters 修改现有实例。

九、记忆方法

1. 一句话定位

复制代码
String 走字符串转换器,
纯文本输出不加引号,
默认 ISO_8859_1,
中文必须显式 UTF-8。

2. 三个关键数字

复制代码
1 个类型:String
1 个默认媒体类型:text/plain
1 个默认字符集:ISO_8859_1

3. 优先级口诀

复制代码
String 在前,Jackson 在后,
返回字符串,纯文本先走。

4. 乱码解决口诀

复制代码
produces 加 charset,
或者替换默认编码,
Spring Boot 配 force,
三选一就能解决。

5. 和 Jackson 的分界

复制代码
String → StringHttpMessageConverter
对象   → MappingJackson2HttpMessageConverter
String + produces=json → Jackson 接手

十、总结

维度 说明
作用 String 与 HTTP 请求/响应体之间的转换
支持类型 仅 String
默认媒体类型 text/plain,同时接受 */*
默认字符集 ISO_8859_1
优先级 高于 Jackson
返回 String 行为 纯文本,不加双引号
中文乱码原因 默认字符集不是 UTF-8
解决方式 produces 声明 charset、替换默认字符集、Spring Boot 强制编码
典型场景 纯文本接口、简单字符串返回值

StringHttpMessageConverter 是 Spring MVC 中最容易被忽视但影响很直接的转换器。它排在 Jackson 前面,所以返回 String 时永远是纯文本;它默认用 ISO_8859_1,所以中文容易乱码。记住"String 优先、纯文本、ISO_8859_1"这三个关键词,再记住"要 JSON 加 produces,要中文加 charset",这类问题就能快速定位和解决。

相关推荐
摇滚侠1 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
java·笔记·python
小蒜学长1 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
Deng桑1 小时前
SpringAI写第一个Agent-把工单循环跑起来
java·agent·工具调用·spring ai
步行cgn3 小时前
@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
java·spring·mvc
jason成都4 小时前
RTKLIB 的 Java 移植版来了:rtklib-java v3.3.0 功能全景
java·gnss·rtklib·rtklib_java
LazyHunter94 小时前
JDK 28:Java 下一步方向解析与新特性展望
java·jdk
m0_639906944 小时前
Java 重写 Override 与重载 Overload 区别、访问修饰符、static 关键字
java·开发语言
GIS数据转换器4 小时前
基于CIM的城市智慧供热监管服务平台
java·大数据·开发语言·人工智能·后端·物联网·struts
m0_639906945 小时前
SpringBoot自动配置原理
java·spring boot·后端