个人展示网站,抖音代运营套餐价格表,网站建设h5,公众号软文推广多少钱一篇前言本篇文章是我这一个多月来帮助组内废弃fastjson框架的总结#xff0c;我们将大部分Java仓库从fastjson迁移至了Gson。这么做的主要的原因是公司受够了fastjson频繁的安全漏洞问题#xff0c;每一次出现漏洞都要推一次全公司的fastjson强制版本升级#xff0c;很令公司头… 前言本篇文章是我这一个多月来帮助组内废弃fastjson框架的总结我们将大部分Java仓库从fastjson迁移至了Gson。这么做的主要的原因是公司受够了fastjson频繁的安全漏洞问题每一次出现漏洞都要推一次全公司的fastjson强制版本升级很令公司头疼。文章的前半部分我会简单分析各种json解析框架的优劣并给出企业级项目迁移json框架的几种解决方案。在文章的后半部分我会结合这一个月的经验总结下Gson的使用问题以及fastjson迁移到Gson踩过的深坑。文章目录为何要放弃fastjsonfastjson替代方案三种json框架的特点性能对比最终选择方案替换依赖时的注意事项谨慎谨慎再谨慎做好开发团队和测试团队的沟通做好回归/接口测试考虑迁移前后的性能差异使用Gson替换fastjsonJson反序列化范型处理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打开时的安全性 fastjson
1.2.60发布增加了AutoType黑名单修复拒绝服务安全问题 fastjson
1.2.61发布增加AutoType安全黑名单 fastjson
1.2.62发布增加AutoType黑名单、增强日期反序列化和JSONPath fastjson
1.2.66发布Bug修复安全加固并且做安全加固补充了AutoType黑名单 fastjson
1.2.67发布Bug修复安全加固补充了AutoType黑名单 fastjson
1.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/json-comparison本文不详细讨论性能的差异毕竟这其中涉及了很多各个框架的实现思路和优化所以只给出结论1.序列化单对象性能Fastjson Jackson Gson其中Fastjson和Jackson性能差距很小Gson性能较差2.序列化大对象性能Jackson Fastjson Gson 序列化大Json对象时Jackson Gson FastjsonJackson序列化大数据时性能优势明显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\:\小李\}];// fastjson
JSONArray 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}]
// 小明
// 小李// Gson
JsonArray 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 {};// fastjson
JSONObject jsonObjectEmpty JSON.parseObject(jsonObjectEmptyCase);
System.out.println(jsonObjectEmpty);
System.out.println(jsonObjectEmpty.size());
// 输出
// {}
// 0// Gson
JsonObject jsonObjectGsonEmpty gson.fromJson(jsonObjectEmptyCase, JsonObject.class);
System.out.println(jsonObjectGsonEmpty);
System.out.println(jsonObjectGsonEmpty.size());
// 输出
// {}
// 0
没有异常开心。看看空数组呢毕竟[]感觉比{}更加容易出错。String jsonArrayEmptyCase [];// fastjson
JSONArray jsonArrayEmpty JSON.parseArray(jsonArrayEmptyCase);
System.out.println(jsonArrayEmpty);
System.out.println(jsonArrayEmpty.size());
// 输出
// []
// 0// Gson
JsonArray 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(马云);// fastjson
ListUser userListResultFastjson JSONArray.parseArray(JSON.toJSONString(userList), User.class);
ListUser userListResultFastjson2 JSON.parseObject(JSON.toJSONString(userList), new TypeReferenceListUser(){});
System.out.println(userListResultFastjson);
System.out.println(userListResultFastjson2 userListResultFastjson2);
// 输出
// userListResultFastjson[User [Hash 483422889, id1, userName马云], null]
// userListResultFastjson2[User [Hash 488970385, id1, userName马云], null]// Gson
ListUser userListResultTrue gson.fromJson(gson.toJson(userList), new TypeTokenListUser(){}.getType());
System.out.println(userListResultGson userListResultGson);
// 输出
// userListResultGson[User [Hash 1435804085, id1, userName马云], null]
可以看出Gson也能支持泛型。List/Map写入这一点fastjson和Gson有区别Gson不支持直接将List写入value而fastjson支持。所以Gson只能将List解析后写入value中详见如下代码// 实体类
User user new User();
user.setId(1L);
user.setUserName(马云);// fastjson
JSONObject jsonObject1 new JSONObject();
jsonObject1.put(user, user);
jsonObject1.put(userList, userList);
System.out.println(jsonObject1);
// 输出
// {userList:[{id:1,userName:马云},null],user:{id:1,userName:马云}}// Gson
JsonObject 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_UNDERSCORESGsonBuilder 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类型在两个框架里解析是不同的方式。fastjsonDate直接解析为UnixGson直接序列化为标准格式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 TypeAdapterDate {Overridepublic void write(JsonWriter out, Date value) throws IOException {if (value null) {out.nullValue();} else {out.value(value.getTime());}}Overridepublic 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 conversion
spring.mvc.converters.preferred-json-mappergson
Swagger异常这个问题和上面的SpringBoot异常类似是因为在SpringBoot中引入了Gson导致 swagger 无法解析 json。采用类似下文的解决方案添加Gson适配器http://yuyublog.top/2018/09/03/springboot%E5%BC%95%E5%85%A5swagger/GsonSwaggerConfig.javaConfiguration
public class GsonSwaggerConfig {//设置swagger支持gsonBeanpublic IGsonHttpMessageConverter IGsonHttpMessageConverter() {return new IGsonHttpMessageConverter();}
}
IGsonHttpMessageConverter.javapublic class IGsonHttpMessageConverter extends GsonHttpMessageConverter {public IGsonHttpMessageConverter() {//自定义Gson适配器super.setGson(new GsonBuilder().registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()).serializeNulls()//空值也参与序列化.create());}
}
SpringfoxJsonToGsonAdapter.javapublic class SpringfoxJsonToGsonAdapter implements JsonSerializerJson {Overridepublic JsonElement serialize(Json json, Type type, JsonSerializationContext jsonSerializationContext) {return new JsonParser().parse(json.value());}
}
Mapping JsonObject作为入参异常有时候我们会在入参使用类似public ResponseResultString submitAudit(RequestBody JsonObject jsonObject) {}
如果使用这种代码其实就是使用Gson来解析json字符串。但是这种写法的风险是很高的平常请大家尽量避免使用JsonObject直接接受参数。在Gson中JsonObject若是有数字字段会统一序列化为double也就是会把count 0这种序列化成count 0.0。为何会有这种情况简单的来说就是Gson在将json解析为Object类型时会默认将数字类型使用double转换。如果Json对应的是Object类型最终会解析为MapString, Object类型其中Object类型跟Json中具体的值有关比如双引号的值翻译为STRING。我们可以看下数值类型NUMBER全部转换为了Double类型所以就有了我们之前的问题整型数据被翻译为了Double类型比如30变为了30.0。可以看下Gson的ObjectTypeAdaptor类它继承了Gson的TypeAdaptor抽象类具体的源码分析和原理阐述大家可以看这篇拓展阅读https://www.jianshu.com/p/eafce9689e7d解决方案第一个方案把入参用实体类接收不要使用JsonObject第二个方案与上面的解决Date类型问题类似自己定义一个Adaptor来接受数字并且处理。这种想法我觉得可行但是难度较大可能会影响到别的类型的解析需要在设计适配器的时候格外注意。总结这篇文章主要是为了那些需要将项目迁移到Gson框架的同学们准备的。一般来说个人小项目是不需要费这么大精力去做迁移所以这篇文章可能目标人群比较狭窄。但文章中也提到了不少通用问题的解决思路比如怎么评估迁移框架的必要性。其中需要考虑到框架兼容性两者性能差异迁移耗费的工时等很多问题。希望文章对你有所帮助。参考《如何从Fastjson迁移到Gson》https://juejin.im/post/6844904089281626120《FastJson迁移至Jackson》此文作者自己封装了工具类来完成迁移https://mxcall.github.io/posts/%E5%B7%A5%E4%BD%9C/%E7%A8%8B%E5%BA%8F%E5%91%98/javaSE/FastJson%E8%BF%81%E7%A7%BB%E8%87%B3Jackson/《你真的会用Gson吗?Gson使用指南》https://www.jianshu.com/p/e740196225a4json性能对比https://github.com/zysrxx/json-comparison/tree/master/src/main/java/json/comparisonfastjson官方文档https://github.com/alibaba/fastjson/wiki易百教程https://www.yiibai.com/jackson往期精彩文章绝了几款主流的 JSON 库性能对比Java中常用的4个Json库哪个性能更牛逼这个 bug 让我更加理解 Spring 单例了