问题描述
有个自己的项目,以前使用JSON作为全局返回内容的,但是后面因为项目需求,需要同时使用XML来返回数据,但是在引入XML过后,通过浏览器访问以前所有返回JSON的接口都返回成XML了。
网上搜到了几篇文章,但是都没有解决问题,于是决定自己分析一波:
代码分析
- 经过以前的代码分析,我们知道,在SpringBoot取调用Controller之前,会分别调用两个接口的实现类,来分别对接口的参数和返回值做处理
HandlerMethodArgumentResolver
和HandlerMethodReturnValueHandler
。那很明显,SpringBoot去对返回值做解析的时候就是通过后者来实现的。 - 通过断点一路跟进,我们走到了:
RequestResponseBodyMethodProcessor#handleReturnValue
方法。我们注意到这个方法里面调用了一个writeWithMessageConverters
。熟悉SpringBoot的同学应该知道,SpringBoot消息的转化是通过HttpMessageConverter
来实现的,所以很明显,我们定位到了目标位置。
1 |
|
根据上面的调试,
getProducibleMediaTypes
这个方法返回了5条数据(因为我同时配置了Json和XML的序列化器)1
2
3
4
5application/json
application/*+json
application/xml;charset=UTF-8
text/xml;charset=UTF-8
application/*+xml;charset=UTF-8但是因为我是浏览器直接访问的,Accept的是:
1
2
3
4
5
6
7text/html
application/xhtml+xml
image/webp
image/apng
application/xml;q=0.9
application/signed-exchange;v=b3;q=0.9
*/*;q=0.8返回的
compatibleMediaTypes
为:1
2
3
4
5
6
7
8application/xhtml+xml
application/xml;charset=UTF-8;q=0.9
application/xml;q=0.9
application/json;q=0.8
application/*+json;q=0.8
application/xml;charset=UTF-8;q=0.8
text/xml;charset=UTF-8;q=0.8
application/*+xml;charset=UTF-8;q=0.8所以很明显了,会使用XML来序列化。
那为什么不引入的时候就会返回JSON了呢?至少我们浏览器的Accept肯定也是和上面的一样的。因为即使去掉XML依赖过后,
compatibleMediaTypes
仍然会返回JSON这个MedaType。那为什么会返回JSON呢?这就得看determineCompatibleMediaTypes
的源码了。
1 |
|
- 我们着重看
isCompatibleWith
这个方法上的注释。
1 |
|
- 注释上说了其推断的逻辑,比如:
text/*
可以匹配text/plain
,所以最后我们能发现application/json
实际上匹配了*/*;q=0.8
。所以可以通过JSON来转换。
以上文章配置无用原因
以上两篇文章中的解决方案,其实和这块无关,这个配置的其实是Spring的内容协商机制([文档地址](Content Negotiation using Spring MVC))
内容协商(Content Negotiation)是HTTP协议中的一个特性,它允许客户端和服务器在传输数据时协商使用的表示方式。例如,客户端可以在请求头
Accept
中指定希望接收的数据格式,如格式。在Spring MVC中,可以通过
configureContentNegotiation
方法来自定义内容协商的行为。如下:1
2
3
4
5
6
7
8
9@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON)
.favorPathExtension(true);
}
}其中设置了默认的内容类型为
application/json
,并且启用了路径扩展名策略。这意味着,如果请求没有明确指定Accept
头,或者服务器无法提供Accept
头中指定的媒体类型,那么服务器会返回application/json
格式的数据。另外,如果请求的URL路径包含文件扩展名,如.json
或.xml
,那么服务器会优先考虑这个扩展名来决定返回数据的格式。所以其实以上文章的解决方案,和我们实际的场景有关系,但是并不大,并不能解决我们现在的这个问题。
总结
- 因为SpringBoot决定使用那个HttpMessageConverter来转换的时候,优先级很大程度上依赖于Accept中的MediaType。所以需要确保Accept中的内容正确。