- 起因
- 疑问
- 实战分析
3.1 结论
3.2 源码分析
3.3 结论
1. 起因
相关代码:
@GetMapping("/header/t1")
public Order test1() {
return new Order("1","2",new Date());
}
在谷歌浏览器调用,如图1-1所示出现xml的响应报文:
但是在postman中调用,结果如图1-2所示:
出现以上问题的原因推测是在浏览器调用GET请求时,请求header中accept字段的问题。
2. 疑问
但是为什么在别的项目操作,也是使用浏览器进行操作,返回报文却是JSON格式的?
3. 实战分析
3.1 结论
项目中引用了依赖。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.0</version>
<scope>compile</scope>
</dependency>
导致Spring MVC的默认会加载xml格式的消息转换器。
而浏览器请求的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.9
格式。
由于浏览器要求服务端返回的xml格式的优先级高,且SpringBoot存在xml格式的消息转换器,于是浏览器最终返回的xml报文。
3.2 源码分析
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
...
List<MediaType> mediaTypesToUse;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
mediaTypesToUse = Collections.singletonList(contentType);
}
else {
//获取请求对象
HttpServletRequest request = inputMessage.getServletRequest();
//获取请求中的accept字段。
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
//获取SpringBoot支持的消息转换器的MediaType类型
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
//若requestedMediaTypes存在*,则返回producibleMediaTypes,
//若producibleMediaTypes存在*,则返回requestedMediaTypes
mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
//对mediaTypesToUse进行排序。
//规则:accept的q越大,则优先级越高,若q相同,则精确匹配优先级高。
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
}
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
//获取到优先级最高的MediaType。
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
}
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + converter + "]");
}
}
return;
}
}
}
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
3.3 结论
根据请求header的accept的类型,可要求服务器返回指定格式的消息,若请求的accept字段为null
或者*/*
,那么则按SpringBoot支持的消息转换器的优先级最高的输出消息(默认JSON格式)。