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 来告诉后端,如:
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
里:
Content-Type: text/html;charset=UTF-8
Spring Boot 内置支持内容协商,只需要浏览器传的 Accept
指定需要什么格式,那么 Spring Boot 就返回这个,当然格式不是随便写的。
基于请求头内容协商:(默认开启)
客户端向服务端发送请求,携带 HTTP 标准的 Accept 请求头。
Accept: application/json`、text/xml、text/yaml
服务端根据客户端 请求头期望的数据类型 进行 动态返回。
基于请求参数内容协商:(需要开启)
发送请求 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
2
3
4
5
开启后内容协商管理器,就会多了一个 ParameterContentNegotiationStrategy
(由 Spring 容器注入)。
Spring Boot 内置 JSON 解析器,但是没有 XML 解析器,因此如果需要返回 XML 格式的,则需要引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2
3
4
# 原理
内容协商原理:
- 判断当前响应头中是否已经有确定的媒体类型
MediaType
- 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端 Accept 请求头字段 application/xml)
contentNegotiationManager
内容协商管理器 默认使用基于请求头的策略HeaderContentNegotiationStrategy
确定客户端可以接收的内容类型
- 遍历循环所有当前系统的
MessageConverter
,看谁支持操作这个对象(Person) - 找到支持操作 Person 的 converter,把 converter 支持的媒体类型统计出来
- 客户端需要 application/xml,服务端有 10 种 MediaType
- 进行内容协商的最佳匹配媒体类型
- 用支持将对象转为最佳匹配媒体类型的 converter。调用它进行转化
Spring Boot 内容协商采用策略模式来选择使用哪个策略解析返回格式。
Spring Boot 默认只有 基于请求头内容协商 的解析器,但是当开启 基于请求参数内容协商 后,容器会多一个解析器,这样 Spring Boot 优先使用这个解析器去解析返回格式。
包括还有其他解析器,但是默认只用一个,需要其他的就开启 Spring Boot 提供的解析器,这就是策略模式:提供多个策略,让用户选择一个,如果用户不选,则有默认策略去执行。
# 自定义 MessageConverter
我们可以自己定义协议:application/x-youngkbt
。
当浏览器请求头携带 Accept: application/x-youngkbt
时,则返回 xx; xx
数据。
@ResponseBody
响应数据出去 调用RequestResponseBodyMethodProcessor
处理Processor 处理方法返回值。通过
MessageConverter
处理所有
MessageConverter
合起来可以支持各种媒体类型数据的操作(读、写)内容协商找到最终的
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());
}
}
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());
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
用 Postman 发送请求(请求头 Accept:application/x-youngkbt
),将返回自定义协议数据的写出。