前言
大家好,又双叒叕见面了,我是天天放大家鸽子的蛮三刀。
在被大家取关之前,我立下一个“远大的现实”,肯定要在这周更新文章。当初看来,flag有用了。。。
本篇文章是我这一个多月来帮忙组内废除fastjson框架的总结,咱们将大部分Java仓库从fastjson迁徙至了Gson。
这么做的次要的起因是公司受够了fastjson频繁的安全漏洞问题,每一次呈现破绽都要推一次全公司的fastjson强制版本升级,很令公司头疼。
文章的前半部分,我会简略剖析各种json解析框架的优劣,并给出企业级我的项目迁徙json框架的几种解决方案。
在文章的后半局部,我会联合这一个月的教训,总结下Gson的应用问题,以及fastjson迁徙到Gson踩过的深坑。
文章目录:
- 为何要放弃fastjson?
fastjson代替计划
- 三种json框架的特点
- 性能比照
- 最终抉择计划
替换依赖时的注意事项
- 审慎,审慎,再审慎
- 做好开发团队和测试团队的沟通
- 做好回归/接口测试
- 思考迁徙前后的性能差别
应用Gson替换fastjson
- Json反序列化
- 范型解决
- List/Map写入
- 驼峰与下划线转换
迁徙常见问题踩坑
- Date序列化形式不同
- SpringBoot异样
- Swagger异样
- @Mapping JsonObject作为入参异样
留神:是否应用fastjson是近年来一个争议性很大的话题,本文无心探讨框架选型的对错,只关注迁徙这件事中遇到的问题进行反思和思考。大家如果有想发表的认识,能够在评论区 理 性 探讨。
本文浏览大略须要:5分钟码字不易,欢送关注我的集体公众号:后端技术漫谈
为何要放弃fastjson?
究其原因,是fastjson破绽频发,导致了公司外部须要频繁的督促各业务线降级fastjson版本,来避免平安问题。
fastjson在2020年频繁裸露安全漏洞,此破绽能够绕过autoType开关来实现反序列化近程代码执行并获取服务器拜访权限。
从2019年7月份公布的v1.2.59始终到2020年6月份公布的 v1.2.71 ,每个版本的降级中都有对于AutoType的降级,波及13个正式版本。
fastjson中与AutoType相干的版本历史:
1.2.59公布,加强AutoType关上时的安全性 fastjson1.2.60公布,减少了AutoType黑名单,修复拒绝服务平安问题 fastjson1.2.61公布,减少AutoType平安黑名单 fastjson1.2.62公布,减少AutoType黑名单、加强日期反序列化和JSONPath fastjson1.2.66公布,Bug修复平安加固,并且做平安加固,补充了AutoType黑名单 fastjson1.2.67公布,Bug修复平安加固,补充了AutoType黑名单 fastjson1.2.68公布,反对GEOJSON,补充了AutoType黑名单1.2.69公布,修复新发现高危AutoType开关绕过安全漏洞,补充了AutoType黑名单1.2.70公布,晋升兼容性,补充了AutoType黑名单1.2.71公布,补充平安黑名单,无新增利用,预防性补充
相比之下,其余的json框架,如Gson和Jackson,破绽数量少很多,高危破绽也比拟少,这是公司想要替换框架的次要起因。
fastjson代替计划
本文次要探讨Gson替换fastjson框架的实战问题,所以在这里不开展具体探讨各种json框架的优劣,只给出论断。
通过评估,次要有Jackson和Gson两种json框架放入思考范畴内,与fastjson进行比照。
三种json框架的特点
FastJson
速度快fastjson绝对其余JSON库的特点是快,从2011年fastjson公布1.1.x版本之后,其性能从未被其余Java实现的JSON库超过。
应用宽泛
fastjson在阿里巴巴大规模应用,在数万台服务器上部署,fastjson在业界被宽泛承受。在2012年被开源中国评比为最受欢迎的国产开源软件之一。
测试齐备
fastjson有十分多的testcase,在1.2.11版本中,testcase超过3321个。每次公布都会进行回归测试,保证质量稳固。
应用简略
fastjson的API非常简洁。
Jackson
容易应用 - jackson API提供了一个高层次外观,以简化罕用的用例。
无需创立映射 - API提供了默认的映射大部分对象序列化。
性能高 - 疾速,低内存占用,适宜大型对象图表或零碎。
洁净的JSON - jackson创立一个洁净和紧凑的JSON后果,这是让人很容易浏览。
不依赖 - 库不须要任何其余的库,除了JDK。
Gson
提供一种机制,使得将Java对象转换为JSON或相同如应用toString()以及结构器(工厂办法)一样简略。容许事后存在的不可变的对象转换为JSON或与之相同。
容许自定义对象的表现形式
反对任意简单的对象
输入轻量易读的JSON
性能比照
共事撰写的性能比照源码:
https://github.com/zysrxx/jso...
本文不具体探讨性能的差别,毕竟这其中波及了很多各个框架的实现思路和优化,所以只给出论断:
1.序列化单对象性能Fastjson > Jackson > Gson,其中Fastjson和Jackson性能差距很小,Gson性能较差2.序列化大对象性能Jackson> Fastjson > Gson ,序列化大Json对象时Jackson> Gson > Fastjson,Jackson序列化大数据时性能劣势显著
3.反序列化单对象性能 Fastjson > Jackson > Gson , 性能差距较小
4.反序列化大对象性能 Fastjson > Jackson > Gson , 性能差距较很小
最终抉择计划
- Jackson实用于高性能场景,Gson实用于高安全性场景
对于新我的项目仓库,不再应用fastjson。对于存量零碎,思考到Json更换老本,由以下几种计划可选:
- 我的项目未应用autoType性能,倡议间接切换为非fastjson,如果切换老本较大,能够思考持续应用fastjson,敞开safemode。
- 业务应用了autoType性能,倡议推动废除fastjson。
替换依赖注意事项
企业我的项目或者说大型项目的特点:
- 代码结构复杂,团队多人保护。
- 承当重要线上业务,一旦呈现重大bug会导致重大事故。
- 如果是老我的项目,可能短少文档,不能随便批改,牵一发而动全身。
- 我的项目有很多开发分支,一直在迭代上线。
所以对于大型项目,想要做到将底层的fastjson迁徙到gson是一件简单且苦楚的事件,其实对于其余依赖的替换,也都一样。
我总结了如下几个在替换我的项目依赖过程中要特地器重的问题。
审慎,审慎,再审慎
再怎么审慎都不为过,如果你要更改的我的项目是十分重要的业务,那么一旦犯下谬误,代价是十分大的。并且,对于业务方和产品团队来说,没有新的性能上线,然而零碎却炸了,是一件“无法忍受”的事件。只管你可能感觉很冤屈,因为只有你或者你的团队晓得,尽管业务看上去没变动,然而代码底层曾经产生了天翻地覆的变动。
所以,审慎点!
做好开发团队和测试团队的沟通
在依赖替换的过程中,须要做好我的项目的布局,比方分模块替换,严格细分排期。
把后期布局做好,开发和测试能力井井有条的进行工作。
开发之间,须要提前沟通好开发注意事项,比方依赖版本问题,避免由多个开发同时批改代码,最初发现应用的版本不同,接口用法都不同这种很难堪,并且要花额定工夫解决的事件。
而对于测试,更要当时沟通好。一般来说,测试不会太在意这种对于业务没有变动的技术我的项目,因为既不是优化速度,也不是新性能。但其实迁徙波及到了底层,很容易就呈现BUG。要让测试团队理解更换我的项目依赖,是须要大量的测试工夫投入的,老本不亚于新性能,让他们尽量器重起来。
做好回归/接口测试
下面说到测试团队须要投入大量工时,这些工时次要都用在我的项目性能的整体回归上,也就是回归测试。
当然,不只是业务回归测试,如果有条件的话,要做接口回归测试。
如果公司有接口治理平台,那么能够极大进步这种我的项目测试的效率。
打个比方,在一个模块批改实现后,在测试环境(或者沙箱环境),部署一个线上版本,部署一个批改后的版本,间接将接口返回数据进行比照。一般来说是Json比照,网上也有很多的Json比照工具:
https://www.sojson.com/
思考迁徙前后的性能差别
正如下面形容的Gson和Fastjson性能比照,替换框架须要留神框架之间的性能差别,尤其是对于流量业务,也就是高并发我的项目,响应工夫如果产生很大的变动会引起上下游的留神,导致一些额定的结果。
应用Gson替换Fastjson
这里总结了两种json框架罕用的办法,贴出具体的代码示例,帮忙大家疾速的上手Gson,无缝切换!
Json反序列化
String jsonCase = "[{\"id\":10001,\"date\":1609316794600,\"name\":\"小明\"},{\"id\":10002,\"date\":1609316794600,\"name\":\"小李\"}]";// fastjsonJSONArray jsonArray = JSON.parseArray(jsonCase);System.out.println(jsonArray);System.out.println(jsonArray.getJSONObject(0).getString("name"));System.out.println(jsonArray.getJSONObject(1).getString("name"));// 输入:// [{"date":1609316794600,"name":"小明","id":10001},{"date":1609316794600,"name":"小李","id":10002}]// 小明// 小李// GsonJsonArray jsonArrayGson = gson.fromJson(jsonCase, JsonArray.class);System.out.println(jsonArrayGson);System.out.println(jsonArrayGson.get(0).getAsJsonObject().get("name").getAsString());System.out.println(jsonArrayGson.get(1).getAsJsonObject().get("name").getAsString());// 输入:// [{"id":10001,"date":1609316794600,"name":"小明"},{"id":10002,"date":1609316794600,"name":"小李"}]// 小明// 小李
看得出,两者区别次要在get各种类型上,Gson调用办法有所扭转,然而变动不大。
那么,来看下空对象反序列化会不会出现异常:
String jsonObjectEmptyCase = "{}";// fastjsonJSONObject jsonObjectEmpty = JSON.parseObject(jsonObjectEmptyCase);System.out.println(jsonObjectEmpty);System.out.println(jsonObjectEmpty.size());// 输入:// {}// 0// GsonJsonObject jsonObjectGsonEmpty = gson.fromJson(jsonObjectEmptyCase, JsonObject.class);System.out.println(jsonObjectGsonEmpty);System.out.println(jsonObjectGsonEmpty.size());// 输入:// {}// 0
没有异样,开心。
看看空数组呢,毕竟[]感觉比{}更加容易出错。
String jsonArrayEmptyCase = "[]";// fastjsonJSONArray jsonArrayEmpty = JSON.parseArray(jsonArrayEmptyCase);System.out.println(jsonArrayEmpty);System.out.println(jsonArrayEmpty.size());// 输入:// []// 0// GsonJsonArray jsonArrayGsonEmpty = gson.fromJson(jsonArrayEmptyCase, JsonArray.class);System.out.println(jsonArrayGsonEmpty);System.out.println(jsonArrayGsonEmpty.size());// 输入:// []// 0
两个框架也都没有问题,完满解析。
范型解决
解析泛型是一个十分罕用的性能,咱们我的项目中大部分fastjson代码就是在解析json和Java Bean。
// 实体类User user = new User();user.setId(1L);user.setUserName("马云");// fastjsonList<User> userListResultFastjson = JSONArray.parseArray(JSON.toJSONString(userList), User.class);List<User> userListResultFastjson2 = JSON.parseObject(JSON.toJSONString(userList), new TypeReference<List<User>>(){});System.out.println(userListResultFastjson);System.out.println("userListResultFastjson2" + userListResultFastjson2);// 输入:// userListResultFastjson[User [Hash = 483422889, id=1, userName=马云], null]// userListResultFastjson2[User [Hash = 488970385, id=1, userName=马云], null]// GsonList<User> userListResultTrue = gson.fromJson(gson.toJson(userList), new TypeToken<List<User>>(){}.getType());System.out.println("userListResultGson" + userListResultGson);// 输入:// userListResultGson[User [Hash = 1435804085, id=1, userName=马云], null]
能够看出,Gson也能反对泛型。
List/Map写入
这一点fastjson和Gson有区别,Gson不反对间接将List写入value,而fastjson反对。
所以Gson只能将List解析后,写入value中,详见如下代码:
// 实体类User user = new User();user.setId(1L);user.setUserName("马云");// fastjsonJSONObject jsonObject1 = new JSONObject();jsonObject1.put("user", user);jsonObject1.put("userList", userList);System.out.println(jsonObject1);// 输入:// {"userList":[{"id":1,"userName":"马云"},null],"user":{"id":1,"userName":"马云"}}// GsonJsonObject jsonObject = new JsonObject();jsonObject.add("user", gson.toJsonTree(user));System.out.println(jsonObject);// 输入:// {"user":{"id":1,"userName":"马云"},"userList":[{"id":1,"userName":"马云"},null]}
如此一来,Gson看起来就没有fastjson不便,因为放入List是以gson.toJsonTree(user)
的模式放入的。这样就不能先入对象,在前面批改该对象了。(有些同学比拟习惯先放入对象,再批改对象,这样的代码就得改变)
驼峰与下划线转换
驼峰转换下划线依附的是批改Gson的序列化模式,批改为LOWER_CASE_WITH_UNDERSCORES
GsonBuilder gsonBuilder = new GsonBuilder();gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);Gson gsonUnderScore = gsonBuilder.create();System.out.println(gsonUnderScore.toJson(user));// 输入:// {"id":1,"user_name":"马云"}
常见问题排雷
上面整顿了咱们在公司我的项目迁徙Gson过程中,踩过的坑,这些坑当初写起来感觉没什么技术含量。然而这才是我写这篇文章的初衷,帮忙大家把这些很难发现的坑避开。
这些问题有的是在测试进行回归测试的时候发现的,有的是在自测的时候发现的,有的是在上线后发现的,比方Swagger挂了这种不会去测到的问题。
Date序列化形式不同
不晓得大家想过一个问题没有,如果你的我的项目里有缓存零碎,应用fastjson写入的缓存,在你切换Gson后,须要用Gson解析进去。所以就肯定要保障两个框架解析逻辑是雷同的,然而,显然这个欲望是美妙的。
在测试过程中,发现了Date类型,在两个框架里解析是不同的形式。
- fastjson:Date间接解析为Unix
- Gson:间接序列化为规范格局Date
导致了Gson在反序列化这个json的时候,间接报错,无奈转换为Date。
解决方案:
新建一个专门用于解析Date类型的类:
import com.google.gson.TypeAdapter;import com.google.gson.stream.JsonReader;import com.google.gson.stream.JsonWriter;import java.io.IOException;import java.util.Date;public class MyDateTypeAdapter extends TypeAdapter<Date> { @Override public void write(JsonWriter out, Date value) throws IOException { if (value == null) { out.nullValue(); } else { out.value(value.getTime()); } } @Override public Date read(JsonReader in) throws IOException { if (in != null) { return new Date(in.nextLong()); } else { return null; } }}
接着,在创立Gson时,把他放入作为Date的专用解决类:
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class,new MyDateTypeAdapter()).create();
这样就能够让Gson将Date解决为Unix。
当然,这只是为了兼容老的缓存,如果你感觉你的仓库没有这方面的顾虑,能够疏忽这个问题。
SpringBoot异样
切换到Gson后,应用SpringBoot搭建的Web我的项目的接口间接申请不了了。报错相似:
org.springframework.http.converter.HttpMessageNotWritableException
因为SpringBoot默认的Mapper是Jackson解析,咱们切换为了Gson作为返回对象后,Jackson解析不了了。
解决方案:
application.properties外面增加:
#Preferred JSON mapper to use for HTTP message conversionspring.mvc.converters.preferred-json-mapper=gson
Swagger异样
这个问题和下面的SpringBoot异样相似,是因为在SpringBoot中引入了Gson,导致 swagger 无奈解析 json。
采纳相似下文的解决方案(增加Gson适配器):
http://yuyublog.top/2018/09/0...
- GsonSwaggerConfig.java
@Configurationpublic class GsonSwaggerConfig { //设置swagger反对gson @Bean public IGsonHttpMessageConverter IGsonHttpMessageConverter() { return new IGsonHttpMessageConverter(); }}
- IGsonHttpMessageConverter.java
public class IGsonHttpMessageConverter extends GsonHttpMessageConverter { public IGsonHttpMessageConverter() { //自定义Gson适配器 super.setGson(new GsonBuilder() .registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()) .serializeNulls()//空值也参加序列化 .create()); }}
- SpringfoxJsonToGsonAdapter.java
public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> { @Override public JsonElement serialize(Json json, Type type, JsonSerializationContext jsonSerializationContext) { return new JsonParser().parse(json.value()); }}
@Mapping JsonObject作为入参异样
有时候,咱们会在入参应用相似:
public ResponseResult<String> submitAudit(@RequestBody JsonObject jsonObject) {}
如果应用这种代码,其实就是应用Gson来解析json字符串。然而这种写法的危险是很高的,平时请大家尽量避免应用JsonObject间接承受参数。
在Gson中,JsonObject若是有数字字段,会对立序列化为double,也就是会把count = 0
这种序列化成count = 0.0
。
为何会有这种状况?简略的来说就是Gson在将json解析为Object类型时,会默认将数字类型应用double转换。
如果Json对应的是Object类型,最终会解析为Map<String, Object>类型;其中Object类型跟Json中具体的值无关,比方双引号的""值翻译为STRING。咱们能够看下数值类型(NUMBER)全副转换为了Double类型,所以就有了咱们之前的问题,整型数据被翻译为了Double类型,比方30变为了30.0。
能够看下Gson的ObjectTypeAdaptor类,它继承了Gson的TypeAdaptor抽象类:
具体的源码剖析和原理论述,大家能够看这篇拓展浏览:
https://www.jianshu.com/p/eaf...
解决方案:
第一个计划:把入参用实体类接管,不要应用JsonObject
第二个计划:与下面的解决Date类型问题相似,本人定义一个Adaptor,来承受数字,并且解决。这种想法我感觉可行然而难度较大,可能会影响到别的类型的解析,须要在设计适配器的时候分外留神。
总结
这篇文章次要是为了那些须要将我的项目迁徙到Gson框架的同学们筹备的。
一般来说,集体小我的项目,是不须要费这么大精力去做迁徙,所以这篇文章可能指标人群比拟狭隘。
但文章中也提到了不少通用问题的解决思路,比方怎么评估迁徙框架的必要性。其中须要思考到框架兼容性,两者性能差别,迁徙消耗的工时等很多问题。
心愿文章对你有所帮忙。
参考
《如何从Fastjson迁徙到Gson》
https://juejin.im/post/684490...
《FastJson迁徙至Jackson》此文作者本人封装了工具类来实现迁徙
https://mxcall.github.io/post...
《你真的会用Gson吗?Gson使用指南》
https://www.jianshu.com/p/e74...
json性能比照
https://github.com/zysrxx/jso...
fastjson官网文档
https://github.com/alibaba/fa...
易百教程
https://www.yiibai.com/jackson
关注我
我是一名奋斗在一线的互联网后端开发工程师。
次要关注后端开发,数据安全,边缘计算等方向,欢送交换。
各大平台都能找到我
- 微信公众号:后端技术漫谈
- Github:@qqxx6661
- CSDN:@蛮三刀把刀
- 知乎:@后端技术漫谈
- 掘金:@蛮三刀把刀
- 腾讯云+社区:@后端技术漫谈
- 博客园:@后端技术漫谈
- BiliBili:@蛮三刀把刀
原创文章次要内容
- 后端开发实战(Java为主)
- 技术面试
- 算法题解/数据结构/设计模式
- 我的生存趣事
集体公众号:后端技术漫谈
如果文章对你有帮忙,无妨点赞,珍藏起来~