1、响应JSON
返回值处理:将Controller中的方法在执行完成后,返回值处理器要将返回的类型转为前端需要的类型,如果方法上加了@ResponseBody,返回值类型就变成标注了@ResponseBody的JSON类型。
@ResponseBody
@GetMapping("/test/person")
public Person getPerson(){
Person person = new Person();
person.setAge(11);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
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>
底层具体的依赖如下,框架已经帮忙引入, 自己只需要导入spring-boot-starter-web即可。
返回值处理器:
之前的流程在请求参数解析中,返回值在invokeAndHandle中
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
this.disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//执行完目标方法之后,进行返回值处理
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception var6) {
if (logger.isTraceEnabled()) {
logger.trace(this.formatErrorForReturnValue(returnValue), var6);
}
throw var6;
}
}
this.returnValueHandlers.handleReturnValue();中进行返回值处理。
returnValueHandlers是所以返回值处理器的集合,下图是所有的返回值处理器。
handleReturnValue方法的内部实现:
HandlerMethodReturnValueHandlerComposite类
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);
}
}
首先选择好对应的返回值处理器,再调用handler.handleReturnValue进入到下面方法:
//以处理@ResponseBody返回值类型的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);
}
返回值解析器原理
- selectHandler中,返回值处理器判断是否支持这种类型返回值 supportsReturnType
- 返回值处理器调用 handleReturnValue 进行处理
- RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
- 利用MessageConverters将数据写为JSON
-
内容协商(浏览器默认会以请求头的方式告诉服务器他能接受的内容类型)
-
服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
-
SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
-
得到MappingJackson2HttpMessageConverter可以将对象写为json(返回值处理,将标注@ResponseBody的自定义类型,如Person转为JSON)
-
利用MappingJackson2HttpMessageConverter将对象转为json再写出去(内容协商完成发给前端要的json格式)。
-
-
- 利用MessageConverters将数据写为JSON
1.2、SpringMVC支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
1.3、HTTPMessageConverter原理
1.3.1 MessageConverter规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
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、内容协商(writeWithMessageConverters的具体实现)
根据客户端接受能力,返回不同媒体类型的数据。
总体流程:
1、根据返回值类型,找到能够处理此类型的返回值处理器。
2、返回值处理器中有converters,找到能够处理返回值类型的converters。
3、查看客户端接受哪些媒体类型,查看上一步的converters可以将返回值转化为哪些媒体类型。
4、双重循环,得到最终确定的媒体类型。
2.1 引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
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()));
}
导入了jackson处理xml的包,xml的converter就会自动进来
2.2 postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
2.3 开启浏览器参数方式内容协商功能
一般内容协商是写在请求头的Accept里的,为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
mvc:
contentnegotiation:
favor-parameter: true
localhost:8080/test/person?format=xml
localhost:8080/test/person?format=json
内容协商管理器就变成了基于参数策略和基于请求头策略,优先遍历第一个策略。
确定客户端接受什么样的内容:
1、Parameter策略优先确定要返回数据类型(获取请求参数中的format的值)
2、 解析出了媒体类型,直接返回。如果是*/*模糊匹配,将继续匹配类型。最终确定了客户端可以接受的类型,进行内容协商返回给客户端即可。
2.4 内容协商原理
1、判断当前响应头是否已经有确定的媒体类型。MediaType
2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】,contentNegotiationManager内容协商管理器,默认使用基于请求头的策略(HeaderContentNegotiationStrategy)最终知道客户端支持哪些类型。
List acceptableTypes;
try {
acceptableTypes = this.getAcceptableMediaTypes(request);
}
=======
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
以上图PostMan为例,此时的acceptableTypes为application/xml
3、遍历循环所有当前系统的MessageConverter,看哪些MessageConverter支持操作Controller方法的返回类型,如Person类型。(返回值处理器查找过程没写)
4、找到支持操作Person类型的converter,把converter支持转化的媒体类型统计出来
List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);
5、客户端需要【application/xml】,服务端能处理的类型producibleTypes :如下
6、进行内容协商的最佳匹配媒体类型,使用双层循环,找到服务器能实现客户端指定内容类型的类型。因为客户端可能允许接受多种类型,所有会选择最佳匹配媒体类型。
List<MediaType> mediaTypesToUse = new ArrayList();
Iterator var15 = acceptableTypes.iterator();//客户端允许接受的类型迭代器
MediaType mediaType;
while(var15.hasNext()) {
mediaType = (MediaType)var15.next();
//服务器可以返回的类型迭代器
Iterator var17 = producibleTypes.iterator();
while(var17.hasNext()) {
MediaType producibleType = (MediaType)var17.next();
if (mediaType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));
}
}
}
7、用支持 将对象转为 最佳匹配媒体类型 的converter。调用它将他转化。
if (selectedMediaType != null) {
//在上一步的双层遍历结果中,选择一个最终确定的类型
selectedMediaType = selectedMediaType.removeQualityValue();
Iterator var23 = this.messageConverters.iterator();
//遍历所有messageConverters,看谁可以转化此类型
while(var23.hasNext()) {
converter = (HttpMessageConverter)var23.next();
genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
if (genericConverter != null) {
if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {
break label183;
}
} else if (converter.canWrite(valueType, selectedMediaType)) {
break label183;
}
}
}
总结:先看客户端可以接受哪些类型,再看服务器端哪些MessageConverter可以处理Controller中方法的返回值类型(如上述方法的Person)。根据上一步的MessageConverter结果,看他们可以转化为哪些类型。再使用双层循环,循环遍历客户端接受的类型和服务器端能处理的类型,得到最终服务器能处理的客户端接受的哪些类型。最后,找到能处理最佳匹配类型(上一步的类型集合中根据权重排序选出最佳)的MessageConverter,进行转化。
如果转化不了 会返回406错误
3、自定义MessageConverter
导入了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()));
}
实现多协议数据兼容。json、xml、x-fang
1、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
2、Processor 处理方法返回值。通过 MessageConverter 处理
3、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4、内容协商找到最终的 messageConverter;
3.1 根据请求头内容协商,自定义MessageConverter
配置SpringMVC的功能,一个入口:在容器中放入一个WebMvcConfigurer,一般写在配置类中。
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyMessageConverter());
}
}
}
自定义的MessageConverter:
public class MyMessageConverter implements HttpMessageConverter<Person> {
/***
* 是否支持把mediaType读成Person,例如标注了@RequestBody的参数,是否能将传过来的json或者xml内容转为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-fang
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-fang");
}
@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());
}
}
返回值处理器的MessageConverts中多了一个自定义的Converter :
处理结果:
3.2 根据请求参数format的自定义内容协商
默认的请求参数内容协商管理器只有两种类型:xml和json
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("fang", MediaType.parseMediaType("application/x-fang"));
ParameterContentNegotiationStrategy parameterstrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//指定支持哪些参数对应的媒体类型,替换Spring默认的策略。
HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterstrategy,headStrategy));
}
如果不增加服务器的请求头策略,如上图,利用请求头的内容协商策略会失效,服务器会能返回什么类型就返回什么类型。所以都要生效就必须写下面代码:
HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterstrategy,headStrategy));