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自带的反串行化, 实际上一直一起来都或多或少有些安全漏洞。我们能做的就是及时升级消除隐患。