阅读本文大概需要 3 分钟。
最近在使用Spring时遇到一个关于JSON解析的问题,@Response的接口如果返回值为一个Interfacce那么结果将变为空对象,也就是{},记录一下,防止再次踩坑。
前两天,业务部门反映,官网有新闻数据接口返回数据为空,导致官网无法访问。于是我着手开始查找原因。
1.
当然是首先怀疑是不是代码出错导致JSON返回了空对象,于是我直接debug了一下controller的代码,直接call到返回值那一行,发现返回值到响应时还是正常的,可以确定代码是没有问题的,排除。
2.
排除了业务代码问题后,我的注意力放在了项目中的几个拦截器上,会不会是拦截器导致的数据被刷掉了?
进行逐一排查,但是奇怪的是拦截器并没有做什么修改接口响应的操作,仅仅是记录日志和一些无关紧要的操作
3.
到这一步,问题的原因已经超出了搜索引擎和个人经验能解决的范畴了,于是我开始翻代码提交记录,试图找出影响接口响应值的修改。
由于ResponseBody注解和JSON解析框架有着密切的关系,所以着重排查有关JSON的依赖引用,经过我的排查,发现jackson依赖在最近的提交中被删除。
问题的原因浮出水面,Jackson的引用被删除,导致Spring默认的HttpMessageConverter由Jackson变为了默认的Gson。
Gson解析的 ”BUG“ (姑且称为BUG,后面会解析)导致对象解析失败,所以响应变为了{}
问题原因找到后,添加上Jackson依赖,测试,响应正常
虽然问题解决,但是我还是想要尝试去探究问题的原因
因为知道了是由于HttpMessageConverter的JSON解析器导致,所以我直接跟踪代码定位到解析器执行部分。
1.
AbstractMessageConverterMethodProcessor 类
核心代码:
这里循环了所有的HttpMessageConverter也就是消息处理器,每个HttpMessageConverter都会有一个canWrite方法,来确认是否执行。
当所有条件都满足时,会进入 HttpMessageConverter的write方法,也就是我用红框圈起来的代码。
2.
继续跟踪会进入AbstractGenericHttpMessageConverter类的write方法,这个类是消息处理器的基类,我们能看到这个方法处理了StreamingHttpOutputMessage类型,随之调用了子类的writeInternal方法。
3.
继续跟踪代码进入具体的Gson解析器实现类GsonHttpMessageConverter的writeInternal方法,代码如下
OK,到这一步,已经完全定位到了导致响应为{}的原因所在,再来看 继续分析
4.
这里调用了Gson的toJson方法,并且传入了源对象,对象Type类型,以及一个输出流,这里需要注意的是传入的Type类型是返回值的类型也就是一个接口,这样做有什么后果呢?继续进入toJson方法
首先,这个方法的核心是根据传入的type类型构建了一个Adapter对象
5.
就是它!胜利在眼前,我们进入~
这个方法看起来有点复杂,没关系,大家只关注我圈起来的核心部分,也就是真正的构造部分,这一句会创建一个TypeAdapter对象,现在查看其代码
这里很简单,就是获取一下全部的字段然后创建一个Adapter对象,但是来再看getBoundFields方法
我们看到这里会判断type如果是一个接口便不会往下执行了,也就是说这个Adapter的字段列表将是空,空对象生成出来的Json是{}也就是必然结果了~
分析完毕,一开始我以为是Gson的BUG,后来慢慢分析发现这是Spring中GsonHttpMessageConverter 实现类的 BUG....
附上我提交的issues链接:
https://github.com/spring-projects/spring-framework/issues/24234
本Demo源码链接:
https://github.com/iwangjie/GsonTest
如果你喜欢这篇文章,再看,转发+关注。
生活很美好,明天见(。・ω・。)ノ♡
欢迎关注