数据响应与内容协商
1.响应JSON
1.1 jackson.jar+@ResponseBody
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//web场景自动引入了json场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
给前端自动返回json数据。
1. 返回值处理器
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} else {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
RequestResponseBodyMethodProcessor
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 使用消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
2. 返回值处理器原理
- 返回值处理器判断是否支持这种类型返回值 supportsReturnType
- 返回值处理器调用 handleReturnValue 进行处理
- RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的
- 利用 MessageConverters 进行处理 将数据写为json
- 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
- SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
- 得到MappingJackson2HttpMessageConverter可以将对象写为json
- 利用MappingJackson2HttpMessageConverter将对象转为json再写出去
- 利用 MessageConverters 进行处理 将数据写为json
1.2 HTTPMessageConverter原理
1. MessageConverter规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
2. 默认的MessageConverter
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
9 - 支持注解方式xml处理的
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
2. 内容协商
根据客户端接收能力的不同,SpringMVC自动返回不同类型的数据。
2.1 引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2.2 内容协商原理
-
判断当前响应头中是否已有确定的媒体类型(MediaType)
-
获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
- HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
-
遍历循环所有当前系统的 MessageConverter,看谁支持操作当前这个要发送的对象
-
找到支持操作对象的converter,把converter支持的媒体类型统计出来
-
进行内容协商的最佳匹配媒体类型
-
用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化
小结:
- @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
- Processor 处理方法返回值。通过 MessageConverter 处理
- 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
- 内容协商找到最终的 messageConverter;
2.3 自定义Converter
场景一:通过浏览器请求头确定自定义返回数据的格式(Accept字段)
/**
* @program: SpringDemo
* @description: 自定义的消息转换器
* @create: 2021-01-08 09:14
**/
public class MyMessageConverter implements HttpMessageConverter<User> {
/***
* @Description: 服务器需要统计各个Converter能读哪些内容的数据
* @Param: [aClass, mediaType]
* @return: boolean
* @Date: 2021/1/8
*/
@Override
public boolean canRead(Class aClass, MediaType mediaType) {
return false;
}
/***
* @Description: 服务器需要统计各个Converter能写哪些内容的数据
* @Param: [aClass, mediaType]
* @return: boolean
* @Date: 2021/1/8
*/
@Override
public boolean canWrite(Class aClass, MediaType mediaType) {
return aClass.isAssignableFrom(User.class);
}
/***
* @Description: 表明本转换器支持的类型
* @Param: [aClass, mediaType]
* @return: boolean
* @Date: 2021/1/8
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-mypro");
}
@Override
public User read(Class<? extends User> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(User user, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
//自定义格式数据的写出
String data = user.getName()+";"+user.getName();
OutputStream body = httpOutputMessage.getBody();
body.write(data.getBytes());
}
}
@Configuration(proxyBeanMethods = true)
public class MyConfig {
//在配置类中添加自定义的Converter
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyMessageConverter());
}
};
}
}
场景二:通过浏览器请求的url确定自定义返回数据的格式(format参数)
如:localhost:8080/test/user?format=json
(此中方式需要在配置文件中手动开启)
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
HashMap<String, MediaType> map = new HashMap<>();
map.put("json",MediaType.APPLICATION_JSON);
map.put("xml",MediaType.APPLICATION_XML);
map.put("pro",MediaType.parseMediaType("application/x-mypro"));
//添加媒体协商
//指定支持解析哪些参数对应的媒体类型
ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(map);
//添加请求头协商
HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy));
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyMessageConverter());
}
};
}