1. 阿里的fastjson
阿里的一些开源项目例如dubbo, druid, fastjson等在国内的影响力是蛮大的。今天谈下温少的fastjson, 它的流行源于它的快, 参考作者的谈fastjson内幕, 给出的测评是碾压jackson, 那时的jackson应该是1.x。https://www.iteye.com/blog/wenshao-1142031
笔者把fastjson整合到spring mvc 蛮多年, 当初还需要自己编写实现了泛型的
FastJsonHttpMessageConverter implements GenericHttpMessageConverter。总体用法上觉得配置暴力些但使用还算简单, 全局的JSON属性, 基本都是静态方法调用, 传入一些Filter可过滤一些类的字段, 引用死循环简单设下属性也可避免。
而这一年来, fastjson被阿里云自身暴出不少漏洞,反串行化执行远程代码(网上有一些示范的攻击代码, 有兴趣同学自行搜索), 拒绝服务, 修复得算快, 但影响肯定是有的, 应该也不少公司在用了,但估计没能及时升级。笔者重新回来审视下json开发库的选择。
搜了些文章, 有些jdk 1.8之后String.substring实现的变化, fastjson的速度和jaskcon2是差不多的, 而fastjson内部用了ASM优化在大json串解析会消耗更多内存等等。回头想想fastjson过程中也是碰到些问题, 一些特殊的json字段例如包含了/等, 默认开启了ASM, 即使在@JSONField设置了别名, 还是无法把json字符串转为对象, 必须禁止ASM, 例如
//Without ASM by default
private static FastJsonConfig fastJsonConfig = new FastJsonConfig();
static {
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
fastJsonConfig.getParserConfig().setAsmEnable(false);
}
public static FastJsonConfig getFastJsonConfig() {
return fastJsonConfig;
}
解析时传入FastJsonConfig即可。
Fastjson也有一些非标准的实现, 例如节点带入java class type, $引用等。
2. spring mvc默认选择的jackson
现在回头看下Jackson, 参考下MappingJackson2HttpMessageConverter用法, 基本都是重用一个ObjectMapper是线程安全的, 构造ObjectMapper有点消耗, 所以网上有些性能评测每次调用就构造一个ObjectMapper是不大公平的。基本json的设置都是绑定到ObjectMapper, 注册Filter, 模块等等, 扩展性较强, 每次写基本是构造新的ObjectWriter, 有一些可设置在ObjectWriter。对象转为json串忽略字段,别名等基本比较依赖对象类使用注解@JsonProperty, @JsonIgnore, @JsonView等。
用的时候有时感觉不是太爽, 一个pojo类, 不同时候可能返回不同的json字段, 这样就需要在pojo加入很多jackson的注解JsonProperty, JsonView等, 侵入性有些强; 如果第三方的pojo无法加注解的, 虽然有ObjectMapper.addMixIn等方法绕过; 引用死循环需要手工指定
@JsonManagedReference和@JsonBackReference虽然合理但啰嗦些;总体API使用没fastjson舒服。 很多时候可能直接拼接为Map再转为json感觉还简单些。
那spring mvc为什么还是选择了jackson作为默认的json库呢?
主要的原因应该是jackson功能全面, 相对稳定, 可定制化一些。
(1) jackson包含了stream api, 有点类似 XML的SAX解析, 流读取可以省很多内存。假设一个json文件很大, 只是需要统计里面的数据或部分数据, 用流api是十分高效的, 这应该是fastjson没有的。
套用网上的例子:
public void testParser() throws Exception {
String testStr = "{\"message\":\"Hello World!\",\"names\":[\"周杰伦\",\"王力宏\"]}";
JsonParser p = factory.createParser(testStr);
JsonToken t = p.nextToken();
List<String> names = new ArrayList<String>();
if ( t != JsonToken.START_OBJECT){
System.out.println("Json格式不正确!");
return;
}
while (t != JsonToken.END_OBJECT){
if (t == JsonToken.FIELD_NAME && "message".equals(p.getCurrentName())){
t = p.nextToken();
String message = p.getText();
System.out.printf("My message to you is %s!\n", message);
}
if (t == JsonToken.FIELD_NAME && "names".equals(p.getCurrentName())){
t = p.nextToken();
while (t != JsonToken.END_ARRAY){
if (t == JsonToken.VALUE_STRING){
String name = p.getValueAsString();
names.add(name);
}
t = p.nextToken();
}
}
t = p.nextToken();
}
System.out.println(names.toString());
p.close();
}
(2) 整个json类似XML DOM解析, 见代码
public static JsonNode parseNode(String text) { try { return objectMapper.readTree(text); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Failed to parseNode, " + e.toString(), e); } }
JsonNode封装用起来就比fastjson啰嗦些了, fastjson解析为JSONObject和JSONArray好用一些。笔者简单的把ObjectMapper解析为Map, 之后封装类似fastjson JSONObject和JSONArray, 可以参考
https://github.com/zealzeng/zen-framework/tree/master/zen-common/src/main/java/org/zenframework/util/json
下的JacksonUtils工具类parseObject, parseArray.
(3) Data binding转为对象就是 ObjectMapper处理的事情。
其实Jackson的CVE也不少,也是有一些反串行化,数据绑定有不少安全漏洞,可执行远程代码, 也是修修补补。spring mvc, spring boot, spring security里面json默认都是jackson处理, 如果不想多配置, jackson也将就着, 综合看它应该相对全面些稳些。Fastjson等配置MessageConverter在spring 5.x方式又有点点变化啰嗦些。
要把字符串转换为对象, 无论是XML, JSON, spring mvc ctrl参数自动生成, spring自身的SPEL, 甚至是java自带的反串行化, 实际上一直一起来都或多或少有些安全漏洞。我们能做的就是及时升级消除隐患。
本文分享自 Hyperledger实践 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!