[think in spring]spring mvc为什么默认使用jackson而不选择fastjson

2020-11-11 15:19:20 浏览数 (1)

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

0 人点赞