SpringBoot2学习笔记
四、Web开发
4.5)数据响应与内容协商
4.5.1)响应JSON
4.5.1.1)jackson.jar+@ResponseBody
在POM.xml文件中,配置 spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在 spring-boot-starter-web-2.3.4.RELEASE.pom.xml文件中,配置spring-boot-starter-json
<!--web场景自动引入了json场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
在spring-boot-starter-json-2.3.4.RELEASE.pom.xml文件中,配置 jackson,给前端自动返回json数据;
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
<scope>compile</scope>
</dependency>
4.5.1.1.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);
}
4.5.1.1.2)返回值解析器示例
工程SpringBootDemo3中新建 ResponseTestController.java,代码如下:
@Controller
public class ResponseTestController {
//利用返回值处理器里面的消息转换器进行处理
@ResponseBody
@GetMapping(value = "/test/person")
public Person getPerson() {
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
}
测试:启动工程,浏览器访问:http://localhost:8080/test/person ,页面如下:
4.5.1.1.3)返回值解析器原理
-
返回值处理器判断是否支持这种类型返回值 supportsReturnType
-
返回值处理器调用 handleReturnValue 进行处理
-
RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
利用 MessageConverters 进行处理将数据写为json
-
内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
-
服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
-
SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
-
得到MappingJackson2HttpMessageConverter可以将对象写为json
-
利用MappingJackson2HttpMessageConverter将对象转为json再写出去
-
4.5.1.2)SpringMVC支持哪些返回值
ModelAndView Model View ResponseEntity ResponseBodyEmitter StreamingResponseBody HttpEntity HttpHeaders Callable DeferredResult ListenableFuture CompletionStage WebAsyncTask 有 @ModelAttribute 且为对象类型的 @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
4.5.1.3)HTTPMessageConverter原理
4.5.1.3.1)MessageConverter规范
HttpMessageConverter: 消息转换器,看是否支持将 Class类型的对象,转为MediaType类型的数据,例子:Person对象转为JSON或者 JSON转为Person【过程可逆】
4.5.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转换的)
4.5.1.4)内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。【通过遍历所有的MessageConverter,最终找到一个适合处理该媒体类型数据的MessageConverter进行处理】
4.5.1.4.1)引入xml依赖
修改POM.xml文件,代码如下:
<!-- 引入xml依赖-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
4.5.1.4.2)postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型
设置为 application/json ,如下图:
得到响应报文:
{
"userName": "zhangsan",
"age": 28,
"birth": "2022-06-06T03:45:16.408+00:00",
"pet": null
}
设置为 application/xml,如下图:
得到响应报文:
<Person>
<userName>zhangsan</userName>
<age>28</age>
<birth>2022-06-06T03:48:54.631+00:00</birth>
<pet/>
</Person>
4.5.1.4.3)开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能,修改配置文件 application.yaml,代码如下:
spring:
mvc:
contentnegotiation:
# 开启浏览器参数方式内容协商功能
favor-parameter: true
测试:启动工程,浏览器访问地址:http://localhost:8080/test/person?format=json,页面内容展示为json格式,浏览器访问地址:http://localhost:8080/test/person?format=xml,页面内容展示为xml格式,页面如下:
调用过程中源码如下:
确定客户端接收什么样的内容类型;
-
Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
最终进行内容协商返回给客户端json即可。
4.5.1.4.4)内容协商原理
-
判断当前响应头中是否已经有确定的媒体类型【MediaType】;
-
获取客户端(PostMan、浏览器)支持接收的内容类型;(获取客户端Accept请求头字段)【application/xml】
ontentNegotiationManager:内容协商管理器 默认使用基于请求头的策略
HeaderContentNegotiationStrategy:确定客户端可以接收的内容类型
-
遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
-
找到支持操作Person的converter,把converter支持的媒体类型统计出来。
-
客户端需要【application/xml】,服务端能力【10种、json、xml】
-
进行内容协商的最佳匹配媒体类型
-
用 支持将对象转为最佳匹配媒体类型 的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()));
}
4.5.1.5)自定义 MessageConverter
实现多协议数据兼容。json、xml、x-study
-
@ResponseBody:响应数据出去,调用 RequestResponseBodyMethodProcessor 处理;
-
Processor:处理方法返回值,通过 MessageConverter 处理;
-
所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写);
-
内容协商找到最终的 messageConverter;
工程中添加StudyMessageConverter.java,代码如下:
/**
* 自定义的Converter
*/
public class StudyMessageConverter 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都能写出哪些内容类型
* <p>
* application/x-study
*
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-study");
}
@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());
}
修改配置文件 WebConfig.java,代码如下:
// 拓展 MessageConverter[内容协商策略]
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StudyMessageConverter());
}
使用Postman进行测试,修改Headers中的Accept为:application/x-study,测试结果如下图:
运行过程中源码如下 :
如何在浏览器中以请求头参数自定义的方式进行内容协商配置形式访问?
修改配置文件 WebConfig.java,代码如下:
/**
* 自定义内容协商策略
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//支持的媒体类型:Map<String, MediaType> mediaTypes
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
//新增study媒体类型
mediaTypes.put("study",MediaType.parseMediaType("application/x-study"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}
};
}
测试:启动工程,浏览器访问地址:http://localhost:8080/test/person?format=study,页面内容如下: