Skip to content
0

Spring Boot - 内容协商

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

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

内容协商

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

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

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

java
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

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

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

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

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

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

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

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

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

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

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

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

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

在 application.yml 开启

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

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

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

xml
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

原理

内容协商原理:

  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 提供的解析器,这就是策略模式:提供多个策略,让用户选择一个,如果用户不选,则有默认策略去执行。

自定义 MessageConverter

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

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

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

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

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

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

自定义 Converter,需要实现 HttpMessageConverter

java
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());
    }
}

注册

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

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyMessageConverter());
            }
        }
    }
}

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

最近更新