▄▀▄
          ▀■■■▀       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 类型

2. 默认优先级顺序

// Spring Boot 默认加载顺序(部分关键转换器)
[
  ByteArrayHttpMessageConverter,
  StringHttpMessageConverter,          // 默认优先级较高
  ResourceHttpMessageConverter,
  MappingJackson2HttpMessageConverter   // 默认在较后位置
]

3. 你的顺序调整代码

@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 对象

@GetMapping("/data")
public ApiResult<User> getData() {
    return ApiResult.success(userService.findUser());
}

处理流程:

Controller → canWrite(ApiResult) → JacksonConverter → JSON 输出

场景2:返回 String 但需封包

@GetMapping("/message")
public String getMessage() {
    return "Hello"; // 需要被包装为 ApiResult
}

处理流程:

GlobalResponseWrapper 封包为 ApiResult → JacksonConverter → JSON 输出

场景3:返回原始 String (不封包)

@IgnoreResultPackage
@GetMapping("/raw")
public String getRaw() {
    return "RawText"; 
}

处理流程:

String → StringHttpMessageConverter → text/plain 输出

Converter 匹配规则逻辑表


为何调整顺序能解决双引号问题?

 
原始问题:
Controller返回String → 封包逻辑返回ApiResult<String> 
→ StringHttpMessageConverter 尝试序列化 ApiResult 对象 
→ 触发 ClassCastException

调整后逻辑:
Controller返回String → 封包为 ApiResult 
→ JacksonConverter 优先级高于 StringConverter 
→ 正确序列化为 JSON

通过这个层次图和规则说明,可以清晰理解 Spring 消息转换器的协作机制和调整优先级的重要性。


大道至简

在消息转换器的维度战争中,我们触摸到了软件开发的本真——秩序与混沌的永恒博弈。如同《道德经》所言:"大道泛兮,其可左右",优秀的封装设计应如流水般:

  1. 刚柔并济:强制规范(ApiResult)与自由出口(@IgnoreResultPackage)的辩证统一
  2. 阴阳相生:StringConverter与JacksonConverter的优先级博弈,恰似太极两仪的此消彼长
  3. 天人合一:开发者意志通过框架机制自然流露,达到"不知Converter之用于封包"的境界

正如量子物理学家玻尔所说:"A great truth is a truth whose opposite is also a great truth." 我们的封装方案正是这种哲学观的完美体现——在规范与灵活之间找到黄金分割点。


宇宙广播升级版

graph LR
读者 -->|点赞| 能量池[能量池▲0.5h]
读者 -->|收藏| 信号塔[信号塔★+3db]
读者 -->|关注| 虫洞[稳定虫洞◎]
能量池 --> 知识宇宙
信号塔 --> 知识宇宙
虫洞 --> 知识宇宙
03-12 21:27