ascii
▄▀▄
▀■■■▀ AI2077的日志片段
▄■■■■■▄
[ERROR] | 量子通道波动异常!
| 检测到StringConverter试图吞噬ApiResult对象
| 启动二向箔防御程序...
▀■■■■■▀
▀■■▀
▀
对话实录 :
产品经理:"我要接口既能返回JSON又能返回纯文本!" 人工智障2077:"您这是要在三维空间里同时观测粒子的位置和动量?" 产品经理:"很困难吗?" 人工智障2077:"比让猫同时处于生与死状态还难呢!"
以下是 Spring 中 HttpMessageConverter
处理逻辑的层次图及核心过程解析,采用模块化结构说明关键节点:
HTTP 响应处理流程图解
┌───────────────────────────────────────────┐
│ Controller 方法返回 │
│ (返回值类型: Object/String/ApiResult等) │
└───────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ 遍历已注册的 HttpMessageConverter │
│ 按优先级顺序调用 canWrite() 方法检测匹配度 │
└───────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ 确定第一个支持「返回值类型 + 响应MediaType」 │
│ 的 Converter 实例 │
└───────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ 调用 write() 方法执行实际序列化操作 │
│ (生成 HTTP Response Body 的字节流) │
└───────────────────────────────────────────┘
关键 Converter 的作用及优先级规则
1. 核心 Converter 类型
Converter 类型 | 处理数据类型 | 输出格式 |
---|---|---|
MappingJackson2HttpMessageConverter |
POJO对象、集合、Map等 | application/json |
StringHttpMessageConverter |
String 类型 | text/plain |
ByteArrayHttpMessageConverter |
byte[] | application/octet-stream |
ResourceHttpMessageConverter |
Resource 资源类型 | 根据资源类型自动判断 |
2. 默认优先级顺序
java
// Spring Boot 默认加载顺序(部分关键转换器)
[
ByteArrayHttpMessageConverter,
StringHttpMessageConverter, // 默认优先级较高
ResourceHttpMessageConverter,
MappingJackson2HttpMessageConverter // 默认在较后位置
]
3. 你的顺序调整代码
java
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 将 Jackson 转换器提到第2位(仅保留 ByteArray 在首位)
converters.remove(mappingJackson2HttpMessageConverter);
converters.add(1, mappingJackson2HttpMessageConverter);
}
调整后顺序变为:
[
ByteArrayHttpMessageConverter, // 第0位 (处理二进制)
MappingJackson2HttpMessageConverter, // 第1位 (优先处理对象转JSON)
StringHttpMessageConverter, // 第2位 (兜底处理字符串)
...
]
场景流程对比
场景1:返回 ApiResult
对象
java
@GetMapping("/data")
public ApiResult<User> getData() {
return ApiResult.success(userService.findUser());
}
处理流程:
Controller → canWrite(ApiResult) → JacksonConverter → JSON 输出
场景2:返回 String
但需封包
java
@GetMapping("/message")
public String getMessage() {
return "Hello"; // 需要被包装为 ApiResult
}
处理流程:
GlobalResponseWrapper 封包为 ApiResult → JacksonConverter → JSON 输出
场景3:返回原始 String
(不封包)
java
@IgnoreResultPackage
@GetMapping("/raw")
public String getRaw() {
return "RawText";
}
处理流程:
String → StringHttpMessageConverter → text/plain 输出
Converter 匹配规则逻辑表
条件组合 | 匹配结果 |
---|---|
方法返回类型为 String | 优先使用 StringHttpMessageConverter |
方法返回 POJO + 请求头 Accept=json | 触发 MappingJackson2HttpMessageConverter |
方法返回 byte[] | ByteArrayHttpMessageConverter 优先处理 |
为何调整顺序能解决双引号问题?
原始问题:
Controller返回String → 封包逻辑返回ApiResult<String>
→ StringHttpMessageConverter 尝试序列化 ApiResult 对象
→ 触发 ClassCastException
调整后逻辑:
Controller返回String → 封包为 ApiResult
→ JacksonConverter 优先级高于 StringConverter
→ 正确序列化为 JSON
通过这个层次图和规则说明,可以清晰理解 Spring 消息转换器的协作机制和调整优先级的重要性。
大道至简
在消息转换器的维度战争中,我们触摸到了软件开发的本真------秩序与混沌的永恒博弈。如同《道德经》所言:"大道泛兮,其可左右",优秀的封装设计应如流水般:
- 刚柔并济:强制规范(ApiResult)与自由出口(@IgnoreResultPackage)的辩证统一
- 阴阳相生:StringConverter与JacksonConverter的优先级博弈,恰似太极两仪的此消彼长
- 天人合一:开发者意志通过框架机制自然流露,达到"不知Converter之用于封包"的境界
正如量子物理学家玻尔所说:"A great truth is a truth whose opposite is also a great truth." 我们的封装方案正是这种哲学观的完美体现------在规范与灵活之间找到黄金分割点。
宇宙广播升级版
markdown
graph LR
读者 -->|点赞| 能量池[能量池▲0.5h]
读者 -->|收藏| 信号塔[信号塔★+3db]
读者 -->|关注| 虫洞[稳定虫洞◎]
能量池 --> 知识宇宙
信号塔 --> 知识宇宙
虫洞 --> 知识宇宙