程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Spring Boot

    • Spring Boot - 自动配置
    • Spring Boot - 自定义starter
    • Spring Boot - 配置文件
    • Spring Boot - 自定义SpringApplication
    • Spring Boot - 生命周期与事件
    • Spring Boot - 事件驱动
    • Spring Boot - Bean 加载方式
    • Spring Boot - 容器资源感知与获取
    • Spring Boot - 定时任务
    • Spring Boot - 异步任务
    • Spring Boot - 内置日志
    • Spring Boot - 函数式 Web
    • Spring Boot - 响应式远程调用
    • Spring Boot - 接口文档
    • Spring Boot - 单元测试
    • Spring Boot - 内容协商
      • 1. 内容协商
      • 2. 原理
      • 3. 自定义 MessageConverter
    • Spring Boot - 参数校验
    • Spring Boot - HTTP客户端工具
    • Spring Boot - 控制器请求映射
    • Spring Boot - 请求参数接收
    • Spring Boot - 通用响应类
    • Spring Boot - 全局异常处理
    • Spring Boot - 整合Druid
    • Spring Boot - 整合Thymeleaf
    • Spring Boot - 国际化实现
    • Spring Boot - 自定义注解
  • Spring高级
  • Spring Boot
scholar
2023-11-01
目录

Spring Boot - 内容协商

视频:https://www.bilibili.com/video/BV1Es4y1q7Bf?p=34,p34 - p38。

或者看原理:https://www.bilibili.com/video/BV19K4y1L7MT?p=39,p39 - p42。

# 1. 内容协商

根据不同客户端接受能力的不同,返回不同类型的数据。

简单理解就是浏览器发生请求的时候,告诉后端需要的返回格式是什么,是 XML 或者 JSON,然后后端处理完请求后,返回前先判断自己可以返回什么格式,然后再和浏览器告诉的格式进行对比,找出权重高的格式进行返回。

浏览器通过在请求头的 accept 来告诉后端,如:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
1

q 代表权重,如果后端支持权重高的则处理成该格式返回。*/* 代表接收任意格式。

后端返回的格式在响应头的 Content-Type 里:

Content-Type: text/html;charset=UTF-8
1

Spring Boot 内置支持内容协商,只需要浏览器传的 Accept 指定需要什么格式,那么 Spring Boot 就返回这个,当然格式不是随便写的。

基于请求头内容协商:(默认开启)

客户端向服务端发送请求,携带 HTTP 标准的 Accept 请求头。

Accept: application/json`、text/xml、text/yaml
1

服务端根据客户端 请求头期望的数据类型 进行 动态返回。

基于请求参数内容协商:(需要开启)

发送请求 GET /projects/spring-boot?format=json

根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】

发送请求 GET /projects/spring-boot?format=xml,优先返回 XML 类型数据

在 application.yml 开启

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true # 开启基于请求参数的内容协商功能。默认参数名:format。默认此功能不开启
      parameter-name: format1 # 指定内容协商时使用的参数名。默认是 format
1
2
3
4
5

开启后内容协商管理器,就会多了一个 ParameterContentNegotiationStrategy(由 Spring 容器注入)。

Spring Boot 内置 JSON 解析器,但是没有 XML 解析器,因此如果需要返回 XML 格式的,则需要引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
1
2
3
4

# 2. 原理

内容协商原理:

  1. 判断当前响应头中是否已经有确定的媒体类型 MediaType
  2. 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端 Accept 请求头字段 application/xml)
    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
  3. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
  4. 找到支持操作 Person 的 converter,把 converter 支持的媒体类型统计出来
  5. 客户端需要 application/xml,服务端有 10 种 MediaType
  6. 进行内容协商的最佳匹配媒体类型
  7. 用支持将对象转为最佳匹配媒体类型的 converter。调用它进行转化

Spring Boot 内容协商采用策略模式来选择使用哪个策略解析返回格式。

Spring Boot 默认只有 基于请求头内容协商 的解析器,但是当开启 基于请求参数内容协商 后,容器会多一个解析器,这样 Spring Boot 优先使用这个解析器去解析返回格式。

包括还有其他解析器,但是默认只用一个,需要其他的就开启 Spring Boot 提供的解析器,这就是策略模式:提供多个策略,让用户选择一个,如果用户不选,则有默认策略去执行。

# 3. 自定义 MessageConverter

我们可以自己定义协议:application/x-youngkbt。

当浏览器请求头携带 Accept: application/x-youngkbt 时,则返回 xx; xx 数据。

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

  2. Processor 处理方法返回值。通过 MessageConverter处理

  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

  4. 内容协商找到最终的 messageConverter

自定义 Converter,需要实现 HttpMessageConverter

public class MyMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }

    /**
     * 服务器要统计所有 MessageConverter 都能写出哪些内容类型
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-youngkbt");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // 自定义协议数据的写出
        String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();

        // 写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

注册

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyMessageConverter());
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

用 Postman 发送请求(请求头 Accept:application/x-youngkbt),将返回自定义协议数据的写出。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
Spring Boot - 单元测试
Spring Boot - 参数校验

← Spring Boot - 单元测试 Spring Boot - 参数校验→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式