关于java:废弃fastjson大型项目迁移Gson保姆级攻略

40次阅读

共计 10742 个字符,预计需要花费 27 分钟才能阅读完成。

前言

大家好,又双叒叕见面了,我是天天放大家鸽子的蛮三刀。

在被大家取关之前,我立下一个“远大的现实”,肯定要在这周更新文章。当初看来,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 关上时的安全性 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/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\":\" 小李 \"}]";

// 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
List<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]

// Gson
List<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("马云");

// 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_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 conversion
spring.mvc.converters.preferred-json-mapper=gson

Swagger 异样

这个问题和下面的 SpringBoot 异样相似,是因为在 SpringBoot 中引入了 Gson,导致 swagger 无奈解析 json。

采纳相似下文的解决方案(增加 Gson 适配器):

http://yuyublog.top/2018/09/0…

  1. GsonSwaggerConfig.java
@Configuration
public class GsonSwaggerConfig {
    // 设置 swagger 反对 gson
    @Bean
    public IGsonHttpMessageConverter IGsonHttpMessageConverter() {return new IGsonHttpMessageConverter();
    }
}
  1. IGsonHttpMessageConverter.java
public class IGsonHttpMessageConverter extends GsonHttpMessageConverter {public IGsonHttpMessageConverter() {
        // 自定义 Gson 适配器
        super.setGson(new GsonBuilder()
                .registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter())
                .serializeNulls()// 空值也参加序列化
                .create());
    }
}
  1. 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 为主)
  • 技术面试
  • 算法题解 / 数据结构 / 设计模式
  • 我的生存趣事

集体公众号:后端技术漫谈

如果文章对你有帮忙,无妨点赞,珍藏起来~

正文完
 0