首页 文章 精选 留言 我的

精选列表

搜索[message],共8701篇文章
优秀的个人博客,低调大师

《SpringMVC7实战》第6招 HTTP Message Conversion怎么把JSON变成Java

# Controller 返回对象后,谁选择 JSON、谁写 HTTP Body? ```text 旧方案:Controller 直接返回对象,靠“感觉”知道会变成 JSON 冲突:Content-Type 不支持、Accept 不匹配、日期格式错误时,400/406/415 混在一起 后果:前端拿不到稳定契约,排障靠猜,接口演进频繁返工 ``` 在 Petclinic REST 项目里,Controller 方法经常直接返回一个 Java 对象,例如 `Pet` 或 `Owner`。 我是老李,深圳南山科技园一家做宠物医院 SaaS 的架构师,团队正在把旧的 MVC 接口改造成 REST API。 我们遇到一个很实际的问题:Controller 返回对象以后,究竟是谁决定把它变成 JSON?又是谁真正把字节写进 HTTP Body? 如果 Content-Type 不支持会怎样? 如果 Accept 与能产出的格式不匹配,Spring 会选哪个状态码? 如果选错 HttpMessageConverter,线上会不会把 400 误报成 500,让前端和网关都误判? # 01、故事 ```text 周二 14:30→南山科技园 12 楼→给 Petclinic 加“按日期查宠物”接口→前端说 400 和 415 分不清 ``` 周二下午两点半,南山科技园 12 楼,会议室白板上画着 Petclinic 的接口链路。 任务是给宠物查询接口增加“最后更新时间”过滤条件,前端要求返回标准 JSON。 我让小李先写 Controller,返回 `List`,再配 Jackson 依赖。 半小时后,前端在群里催:“为什么我传 `Content-Type: text/plain` 返回 415,传坏 JSON 又返回 400?你们后端到底怎么区分?” 小李翻了几篇博客,只看到“加 Jackson 依赖就行”,没找到谁在选择 JSON、谁在写 Body。 > 白板画满箭头, > 对象返回却无门。 > 前端催问格式错, > 源码深处找根因。 # 02、问题 ```text 需求变化:从“返回页面”到“返回 JSON API” 冲突:Controller 返回对象,但没人显式声明格式 后果:Content-Type、Accept、日期格式任一不匹配,状态码不稳定 ``` 旧接口返回 JSP 视图,现在要改成 REST API,Controller 直接返回对象。 问题在于:Spring 默认不会因为“你返回了对象”就自动知道要写 JSON。 它必须经过 `HttpMessageConverter` 协商,而协商失败时,不同阶段会产生 400、406、415。 如果团队不理解这条链路,就会把“客户端错误”误判成“服务端错误”,甚至加一堆 try-catch 掩盖根因。 可验证的验收标准是:同一接口在 malformed JSON、不支持的 Content-Type、不匹配的 Accept 下,分别稳定返回 400、415、406。 > 对象不是字节, > 转换需要契约。 > 状态码若混乱, > 契约变成错觉。 # 03、原理 ```text 核心机制:HandlerMethodReturnValueHandler 选择返回值处理策略,RequestResponseBodyMethodProcessor 再按 Content-Type/Accept 选择 HttpMessageConverter ``` Spring MVC 在 Controller 方法执行完后,会遍历 `HandlerMethodReturnValueHandler`。 当方法或返回值上有 `@ResponseBody` 时,`RequestResponseBodyMethodProcessor` 会被选中。 它先看请求的 `Accept` 头,再看返回对象类型,然后遍历所有 `HttpMessageConverter`。 每个 Converter 有两个关键方法:`canWrite` 判断能不能写,`write` 真正写 Body。 Jackson 的 `MappingJackson2HttpMessageConverter` 通常排在前面,它能处理 `application/json`。 一个反直觉判断:不是 Controller 返回对象就一定有 JSON,而是“有 Converter 能写、且 Accept 允许”才有 JSON。 > 返回只是起点, > 协商才是关键。 > 没有 Converter, > 对象只是内存。 # 04、架构 ```text 输入:HTTP 请求头 Accept/Content-Type + Controller 返回对象 本模块:HandlerMethodReturnValueHandler → HttpMessageConverter 协商 输出:HTTP Response Body + Content-Type 响应头 边界:不处理视图渲染,不处理请求体解析失败后的业务补偿 ``` 这一层位于 MVC 的返回值处理阶段,在 Controller 执行之后、DispatcherServlet 写响应之前。 `RequestResponseBodyMethodProcessor` 同时处理 `@RequestBody` 和 `@ResponseBody`,所以请求解析和响应写出共享同一套 Converte r 列表。 收益是:Controller 代码保持简单,格式协商集中在框架层。 代价是:一旦 Converter 顺序或依赖缺失,问题会延迟到运行时才暴露。 适用条件是:接口以 JSON 或 XML 等消息体为主,而不是视图渲染。 > 边界划在协商, > 收益集中管理。 > 代价延迟暴露, > 适用消息优先。 # 05、第一版实战 ```text 环境→配置→代码→启动→验证→V1 ``` 环境:Spring Framework 7.0.9 Web MVC,需按当前官方文档核验;JDK 17;Maven 3.9;Jackson 2.18。 依赖: ```xml com.fasterxml.jackson.core jackson-databind 2.18.0 ``` 配置类: ```java @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { } ``` Controller: ```java @RestController @RequestMapping("/api/pets") public class PetController { @GetMapping("/{id}") public Pet getPet(@PathVariable Long id) { return new Pet(id, "Leo", LocalDate.of(2024, 1, 15)); } } ``` 启动应用: ```bash mvn spring-boot:run ``` 验证请求: ```bash curl -i -H "Accept: application/json" http://localhost:8080/api/pets/1 ``` 预期响应: ```text HTTP/1.1 200 Content-Type: application/json {"id":1,"name":"Leo","birthDate":"2024-01-15"} ``` 成功标准:返回 200,Content-Type 为 `application/json`,Body 是合法 JSON。 > 依赖加进工程, > 注解标在方法。 > 请求一来一往, > 对象变成字节。 # 06、排查 ```text 诊断树:状态码异常→是 400/406/415?→检查请求头→检查 Converter 列表→检查 Jackson 配置 ``` 问题一:前端传 `Content-Type: text/plain` 返回 415。 怀疑是请求体解析阶段没有匹配的 Converter。 检查请求头: ```bash curl -i -X POST -H "Content-Type: text/plain" -d '{"name":"Leo"}' http://localhost:8080/api/pets ``` 证据:返回 415 Unsupported Media Type。 根因:`@RequestBody` 解析时,`RequestResponseBodyMethodProcessor` 找不到能读 `text/plain` 的 Converter。 修复:前端统一使用 `application/json`,或后端增加 `StringHttpMessageConverter` 支持文本。 问题二:日期格式错误返回 400,但前端误以为是服务端 500。 怀疑是 Jackson 反序列化失败被包装成 400。 检查请求: ```bash curl -i -X POST -H "Content-Type: application/json" -d '{"name":"Leo","birthDate":"2024/01/15"}' http://localhost:8080/api/pets ``` 证据:返回 400,Body 里包含 `InvalidFormatException`。 根因:Jackson 默认日期格式是 ISO-8601,斜杠格式无法解析。 修复:在 DTO 字段上加 `@JsonFormat(pattern = "yyyy-MM-dd")`,或全局配置 ObjectMapper。 **错误尝试:** 小李一开始在 Controller 里 try-catch 所有异常并返回 500,结果前端把客户端错误当成服务端故障,网关还触发了告警。 > 状态码是信号, > 不是错误遮羞布。 > 根因藏在协商, > 修复要改契约。 # 07、优化 ```text V1:只靠默认 Jackson,日期格式和 Content-Type 支持有限 证据:400/415 不稳定,前端联调反复中断 V2:显式配置 Converter 和全局日期格式 ``` 改了什么:在 `WebConfig` 里显式配置 `MappingJackson2HttpMessageConverter`,并设置全局日期格式。 ```java @Override public void configureMessageConverters(List> converters) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); converters.add(new MappingJackson2HttpMessageConverter(mapper)); } ``` 为何改:默认 Converter 列表虽然包含 Jackson,但日期模块不一定注册,导致 `LocalDate` 序列化失败。 显式配置后,日期格式统一,前端不再需要猜测。 > 默认只是起点, > 显式才有契约。 > 日期统一格式, > 联调少些纠结。 # 08、演进 ```text V2:显式 Converter + 全局日期格式 能力:稳定区分 400/406/415,日期序列化可控 遗留:Accept 协商细节仍依赖框架默认顺序 ``` 用 V1 同一组请求验证: ```bash curl -i -H "Accept: application/json" http://localhost:8080/api/pets/1 ``` 返回 200,日期格式为 `2024-01-15`。 ```bash curl -i -H "Accept: application/xml" http://localhost:8080/api/pets/1 ``` 返回 406 Not Acceptable,因为没有 XML Converter。 收益:客户端错误状态码稳定,网关可以按 4xx 统一处理。 代价:显式配置后,新增格式需要手动加 Converter。 适用:以 JSON 为主的 REST API。 遗留:如果未来要支持 XML 或 Protobuf,需要重新评估 Converter 顺序和 Accept 权重。 > 能力来自显式, > 遗留交给演进。 > 状态码稳定后, > 网关不再误判。 # 09、洞见 ```text 从现象到判断:状态码不是随机出现,而是协商链路的必然结果 ``` ## 反直觉判断:400 和 415 不是同一个阶段 ```text 请求体解析失败→400 Content-Type 不支持→415 ``` 很多人以为“请求有问题就是 400”,但 Spring 明确区分:Body 语法错误是 400,媒体类型不支持是 415。 这个区分对前端和网关非常重要,因为重试策略不同。 ## 反直觉判断:Accept 不匹配时,Spring 优先返回 406,而不是降级 ```text Accept: application/xml 能产出: application/json 结果: 406 Not Acceptable ``` 框架不会“随便给个 JSON”,而是明确告诉客户端“我给不了你要的格式”。 这保护了接口契约,但也要求前端必须正确声明 Accept。 ## 判断:Converter 顺序决定“谁能写”,但 Accept 决定“谁被允许写” ```text Converter 列表顺序→canWrite 筛选 Accept 头→进一步过滤 ``` 即使 Jackson 排在第一位,如果 Accept 不包含 JSON,它也不会被选中。 协商是“能力”和“意愿”的交集。 > 状态码有逻辑, > 协商有顺序。 > 能力加意愿, > 才有最终字节。 # 10、系统落地 ```text 入口:前端请求 /api/pets 服务:PetController 返回 Pet 对象 本模块:RequestResponseBodyMethodProcessor + Jackson Converter 存储:内存对象,未接数据库 未实现:XML/Protobuf 格式、请求体校验、全局异常映射 ``` 原来项目只有 JSP 视图接口,本篇增加了 `@RestController` 和显式 Jackson 配置。 现在系统能稳定返回 JSON,并正确区分 400、406、415。 下一篇缺的是全局异常处理,把 Jackson 解析失败统一转成结构化错误响应。 官方文档可参考 Spring Framework 文档首页:https://spring.io/projects/spring-framework > 入口到服务, > 协商在中间。 > 存储未接入, > 异常待补全。 # 11、小结 ```text 三问三答:谁选 JSON?谁写 Body?选错会怎样? ``` 谁选 JSON?`RequestResponseBodyMethodProcessor` 根据 Accept 和 Converter 能力选择。 谁写 Body?被选中的 `HttpMessageConverter`,通常是 Jackson 的 `MappingJackson2HttpMessageConverter`。 选错会怎样?状态码混乱,400 可能被误报成 500,前端和网关都会误判。 当前状态:V2 已稳定运行,日期格式统一,状态码可预期。 > 三问有答案, > 协商是主线。 > 状态码稳定, > 契约才可见。 # 12、作业 ```text 理解→实战→排障→架构判断 ``` ## 理解题:为什么 Controller 返回对象不一定是 JSON? 因为必须经过 `HttpMessageConverter` 协商,只有存在能写该对象类型且 Accept 允许的 Converter,才会输出 JSON。 ## 实战题:给 Petclinic 增加一个返回 XML 的接口,并验证 Accept 协商。 添加 `jackson-dataformat-xml` 依赖,注册 `MappingJackson2XmlHttpMessageConverter`,用 `Accept: application/xm l` 请求,观察返回 200 和 XML Body。 ## 排障题:前端传 `Accept: text/html` 请求 JSON 接口,返回 406,如何定位? 检查 `Accept` 头是否与后端能产出的格式匹配,确认 Converter 列表里是否有 `text/html` 支持,必要时调整前端 Accept 或后端 Converter。 ## 架构判断题:把 Jackson 解析异常全部 catch 并返回 500,是否合理? 不合理。解析失败属于客户端错误,应返回 400,并保留异常细节供前端修正,而不是伪装成服务端故障。 > 理解协商链, > 实战加格式。 > 排障看状态, > 架构守契约。 # 13、升华 ```text 工程结论:REST API 的格式协商不是魔法,而是显式的 Converter 选择链路 ``` Controller 返回对象只是起点,真正决定 JSON 和 Body 的是 `HandlerMethodReturnValueHandler` 与 `HttpMessageConverter` 的 协作。 理解这条链路,才能稳定区分 400、406、415,才能让接口契约经得起演进。 下一篇将进入全局异常处理,把协商失败和业务异常统一成结构化错误响应。 > 对象返回无声, > 协商选择有痕。 > 状态码如契约, > 演进方得安稳。 **系列项目:** Petclinic REST API 宠物医院 SaaS 系统 **已完成:** 环境搭建、Petclinic 启动、DispatcherServlet 请求分发 **本篇:** @RequestBody/@ResponseBody、ResponseEntity、HttpMessageConverter 协商与 400/406/415 区分 **下一篇:** 全局异常处理与结构化错误响应

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册