数据响应与内容协商
响应页面指的是我们如何发送一个请求,跳转到指定页面。将会在后面的视图解析中说明。
响应页面常见于开发单体应用。
响应数据常见于开发前后端分离的应用。后端代码主要用来接收请求。前端页面给我们发送过来请求,给前端响应json数据。或者给前端响应xml、图片、音视频数据。
1.响应JSON
1.1.Jackson.jar+@ResponseBody
假设给前端自动返回json数据,需要引入相关的依赖
<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>
控制层代码如下
@Controller
public class ResponseTestController {
@ResponseBody
@GetMapping("/test/person")
public Person getPerson(){
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
}
引入了依赖后,给方法上标注@ResponseBody,就可以给前端自动返回JSON数据。
原理:
有讨论ReturnValueHandler。现在直接看看重点:
1、返回值解析器
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
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.返回值解析器原理
- 1、返回值处理器判断是否支持这种类型返回值supportsReturnType
- 2、返回值处理器调用handleReturnValue进行处理
- 3、RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的。
-
- 3.1.利用MessageConverters进行处理将数据写为json
-
-
- 3.1.1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
-
-
-
- 3.1.2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
-
-
-
- 3.1.3、 SpringMVC会挨个遍历所有容器底层的HttpMessageConverter,看谁能处理?(也就是把对象转换成为json数据)
-
-
-
-
- 得到MappingJackson2HttpMessageConverter可以将对象写为json
-
-
-
-
-
- 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
- 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
-
-
1.2.SpringMVC到底支持哪些返回值
ModelAndView //包含数据和页面
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable //异步
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;处理器//即在方法上或者类上是否标注@ResponseBody
1.3.HTTPMessageConverter原理
1.3.1.MessageConverter规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:CanWrite将Person对象转为JSON。canRead或者 JSON转为Person
1.3.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
7 - true //支持将任意对象转为指定的,不管是什么都支持
8 - true
9 - 支持注解方式xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
2.内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
2.1.引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
重新编译运行
查看请求头
内容协商Accept中,浏览器具备什么类型数据的接收能力,可以看到xml数据是优先被接收的。
2.2.postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
2.3.开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json即可。
2.4.内容协商原理
1、判断当前响应头中是否已经有确定的媒体类型。MediaType
2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
HeaderContentNegotiationStrategy确定客户端可以接收的内容类型
3、遍历循环所有当前系统的MessageConverter,看谁支持操作这个对象(Person)
4、找到支持操作Person的converter, 把converter支 持的媒体类型统计出来。
5、客户端需要[application/xml]。服务端能力[10种、json、 xm|]
6、进行内容协商的最佳匹配媒体类型
7、用支持将对象转为最佳匹配媒体类型的converter。调用它进行转化。
导入了jackson处理xml的包,xml的converter就会自动进来
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
5.自定义 MessageConverter
实现多协议数据兼容。json、 xml、 x-guigu
0、@ResponseBody 响应数据出去调用RequestResponseBodyMethodProcessor处理
1、Processor处理方法返回值。通过MessageConverter处理
2、所有MessageConverter合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的messageConverter;
SpringMVC的什么功能。一个入口给容器中添加一个WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
}
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
}
/**
* 自定义的Converter
*/
public class GuiguMessageConverter 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都能写出哪些内容类型
*
* application/x-guigu
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-guigu");
}
@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());
}
}
import java.util.Date;
@Controller
public class ResponseTestController {
/**
* 1、浏览器发请求直接返回 xml [application/xml] jacksonXmlConverter
* 2、如果是ajax请求 返回 json [application/json] jacksonJsonConverter
* 3、如果硅谷app发请求,返回自定义协议数据 [appliaction/x-guigu] xxxxConverter
* 属性值1;属性值2;
*
* 步骤:
* 1、添加自定义的MessageConverter进系统底层
* 2、系统底层就会统计出所有MessageConverter能操作哪些类型
* 3、客户端内容协商 [guigu--->guigu]
*
* 作业:如何以参数的方式进行内容协商
* @return
*/
@ResponseBody //利用返回值处理器里面的消息转换器进行处理
@GetMapping(value = "/test/person")
public Person getPerson(){
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
}
用Postman发送/test/person(请求头Accept:application/x-guigu),将返回自定义协议数据的写出。
有可能我们添加的自定义的功能会覆盖默认很多功能,导致-些默认的功能失效。
大家考虑,. 上述功能除了我们完全自定义外? SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢? [提示: 参照SpringBoot官方文档web开发内容协商章节]