当前位置: 首页 > news >正文

Spring MVC 根据请求头 (如 Accept) 怎么返回 JSON 或 XML 数据?

Spring MVC 通过 内容协商 (Content Negotiation) 来根据客户端请求的 Accept 头决定返回 JSON、XML 还是其他格式的数据。

以下是核心机制和步骤:

  1. 客户端请求中的 Accept 头:

    • 客户端(如浏览器、curl、Postman等)在发起HTTP请求时,会通过 Accept 请求头告知服务器它期望接收的数据格式(MIME类型)。
    • 例如:
      • Accept: application/json (客户端期望JSON)
      • Accept: application/xml (客户端期望XML)
      • Accept: application/json, application/xml;q=0.9, */*;q=0.8 (客户端优先期望JSON,如果不行则XML,q是权重因子)
  2. Spring MVC 的 ContentNegotiationManager:

    • Spring MVC 内部使用 ContentNegotiationManager (或其策略实现) 来确定请求的目标媒体类型。
    • ContentNegotiationManager 会按照一定的优先级顺序尝试解析客户端的期望格式。默认情况下(尤其是在Spring Boot中),这个顺序通常是:
      1. URL 路径扩展名 (Path Extension): 检查URL末尾是否有如 .json.xml 的扩展名。例如:GET /api/users/1.json。 (可以通过配置禁用,因为有些人认为这不符合纯粹的RESTful风格,URL应标识资源而非其表示。)
      2. URL 参数 (Parameter): 检查URL中是否有名为 format (或其他可配置名称) 的参数。例如:GET /api/users/1?format=json。 (默认通常是关闭的,但可以配置启用。)
      3. Accept 请求头: 这是最常用且最符合HTTP规范的方式。 ContentNegotiationManager 会解析 Accept 头来确定客户端期望的媒体类型。
      4. 默认内容类型 (Default Content Type): 如果以上方法都无法确定,或者客户端没有发送明确的 Accept 头,Spring MVC 可能会使用预设的默认内容类型(例如,JSON)。
  3. @ResponseBodyHttpMessageConverter:

    • 当Controller方法被 @ResponseBody 注解标记(或者Controller类被 @RestController 注解标记,它包含了 @ResponseBody),Spring MVC 知道这个方法的返回值应该直接写入HTTP响应体中,而不是被视图解析器解析为视图名。
    • Spring MVC 维护了一个 HttpMessageConverter 列表。这些转换器负责将Java对象序列化为特定的HTTP响应体格式(如JSON、XML)以及将HTTP请求体反序列化为Java对象。
    • 常见的转换器有:
      • MappingJackson2HttpMessageConverter: 用于处理JSON,依赖Jackson库。
      • Jaxb2RootElementHttpMessageConverter: 用于处理XML,依赖JAXB API和实现。
      • MappingJackson2XmlHttpMessageConverter: 也是用于处理XML,但使用Jackson的XML模块。
  4. 工作流程总结:

    1. 客户端发送请求,包含 Accept 头 (例如, Accept: application/xml)。
    2. DispatcherServlet 将请求路由到相应的Controller方法。
    3. ContentNegotiationManager 解析请求(主要关注 Accept 头),确定目标媒体类型为 application/xml
    4. Controller方法执行完毕,返回一个Java对象 (例如,一个 User 对象)。
    5. 由于方法有 @ResponseBody,Spring MVC会查找一个能够将 User 对象转换为 application/xml 格式的 HttpMessageConverter
    6. 如果找到了合适的转换器(例如 Jaxb2RootElementHttpMessageConverterMappingJackson2XmlHttpMessageConverter),该转换器就会将 User 对象序列化为XML字符串。
    7. Spring MVC将序列化后的XML字符串写入HTTP响应体,并设置响应头 Content-Type: application/xml

示例代码 (Spring Boot):

假设你有一个POJO:

// src/main/java/com/example/demo/User.java
package com.example.demo;// 如果使用JAXB,需要添加 @XmlRootElement
// import javax.xml.bind.annotation.XmlRootElement;
// @XmlRootElement
public class User {private Long id;private String name;private String email;// 构造函数、Getters 和 Setterspublic User(Long id, String name, String email) {this.id = id;this.name = name;this.email = email;}public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public String getEmail() { return email; }public void setEmail(String email) { this.email = email; }
}

Controller:

// src/main/java/com/example/demo/UserController.java
package com.example.demo;import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@GetMapping(value = "/users/{id}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })public User getUser(@PathVariable Long id) {// 实际应用中会从服务层或数据库获取用户return new User(id, "John Doe " + id, "john.doe" + id + "@example.com");}
}

依赖:

  • 对于JSON: Spring Boot 的 spring-boot-starter-web 默认包含了 Jackson,所以通常不需要额外添加。
  • 对于XML: 你需要添加相应的XML处理库。
    • 使用Jackson XML:
      <!-- pom.xml -->
      <dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
      </dependency>
      
      如果使用Jackson XML,它会自动注册 MappingJackson2XmlHttpMessageConverter
    • 使用JAXB:
      从Java 9开始,JAXB不再是JDK的一部分,需要手动添加依赖:
      <!-- pom.xml -->
      <dependency><groupId>jakarta.xml.bind</groupId><artifactId>jakarta.xml.bind-api</artifactId><!-- Version may vary -->
      </dependency>
      <dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><!-- Version may vary -->
      </dependency>
      
      并且在你的POJO上使用 @XmlRootElement (以及其他JAXB注解,如 @XmlElement)。

如何测试:

使用 curl 或 Postman:

  1. 请求JSON:

    curl -H "Accept: application/json" http://localhost:8080/users/1
    

    响应头会包含 Content-Type: application/json,响应体是JSON格式的用户数据。

  2. 请求XML:

    curl -H "Accept: application/xml" http://localhost:8080/users/1
    

    响应头会包含 Content-Type: application/xml,响应体是XML格式的用户数据。

produces 属性:

@RequestMapping (或 @GetMapping 等) 中使用 produces 属性,例如 produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE },是一个好习惯。它明确声明了这个端点能够生成哪些媒体类型。如果客户端请求的 Accept 类型与 produces 中声明的任何一种都不匹配,Spring MVC 通常会返回 406 Not Acceptable 状态码。

自定义内容协商策略 (可选):

如果想更精细地控制内容协商的行为(例如,禁用路径扩展名策略,或设置默认的内容类型),可以通过实现 WebMvcConfigurer 接口并重写 configureContentNegotiation 方法来进行配置。

// import org.springframework.context.annotation.Configuration;
// import org.springframework.http.MediaType;
// import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
// import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;// @Configuration
// public class WebConfig implements WebMvcConfigurer {
//
//     @Override
//     public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//         configurer.favorParameter(false) // 不使用URL参数 (?format=json)
//                   .favorPathExtension(false) // 不使用路径扩展 (.json)
//                   .ignoreAcceptHeader(false) // 优先使用Accept头 (默认)
//                   .defaultContentType(MediaType.APPLICATION_JSON) // 默认返回JSON
//                   .mediaType("json", MediaType.APPLICATION_JSON)
//                   .mediaType("xml", MediaType.APPLICATION_XML);
//     }
// }

通过这种方式,Spring MVC 提供了一个强大且灵活的机制,使RESTful API能够根据客户端的需求返回不同格式的数据,而Controller层的代码保持简洁,不需要关心具体的序列化细节。

相关文章:

  • WebFlux vs WebMVC vs Servlet 对比
  • 【SSM-SpringMVC(二)】Spring接入Web环境!本篇开始研究SpringMVC的使用!SpringMVC数据响应和获取请求数据
  • Spring MVC参数传递
  • tensorflow-cpu
  • tabs切换#
  • git|gitee仓库同步到github
  • PyCharm 快捷键指南
  • 基于Qt6 + MuPDF在 Arm IMX6ULL运行的PDF浏览器——MuPDF Tools
  • Spring Boot集成RabbitMQ高级篇:可靠性与性能提升
  • 面试题:请解释Java中的设计模式,并举例说明单例模式(Singleton Pattern)的实现方式
  • 解决WSL、Ubuntu的.ico图标不正确显示缩略图
  • 解决IDEA Maven编译时@spring.profiles.active@没有替换成具体环境变量的问题
  • 将本地文件上传到云服务器上
  • Redis+Caffeine构建高性能二级缓存
  • 数据出境的安全合规思考
  • 蓝桥杯13届国赛 2022
  • Java设计模式之代理模式:从入门到精通(保姆级教程)
  • 我开源了一个免费在线工具!UIED Tools
  • 【Jenkins简单自动化部署案例:基于Docker和Harbor的自动化部署流程记录】
  • 【现代深度学习技术】注意力机制05:多头注意力
  • 为惩戒“工贼”,美国编剧工会“痛下杀手”
  • 体坛联播|安切洛蒂执掌巴西男足,字母哥尝试离开雄鹿
  • 明查|印度空军“又有一架战机被巴基斯坦击落,飞行员被俘”?
  • 国产水陆两栖大飞机AG600批产首架机完成总装下线
  • 第12届警博会在即:一批便民利企装备亮相,规模创历史新高
  • 学习时报头版:世界要公道不要霸道