从 Jackson 的使用和源码看程序设计

背景

每隔一段时间,我会梳理一些平时经常用的类库源码。这次和大多数时候一样,我又选了一个简单的来看点源码。这次选的Jackson的ObjectMapper类源码。

为什么我要选简单的来看呢?因为提高技术知识只是问题的表面,根本是要提高自己认知问题、分析问题、解决问题的思想高度。研究的问题越复杂,越容易将精力放在解决问题的本身,而阻碍了思想高度的提升。

同样,这次我们还是套用思考框架来完成这个工作。这次使用学习技术的三步曲what、why、how来思考。

What

Jackson是Java生态圈中处理JSON和XML的类库,是spring-boot默认的JSON解析框架。这是因为spring mvc会自动注册MappingJackson2HttpMessageConverter,从而支持json 输出。

Jackson提供了三种数据处理方式。分别是数据绑定、树模型、流式API。其中, 数据绑定用于JSON转化,可以将JSON与POJO对象进行转化。 是最符合面向对象的一种使用方式。流式API是江湖传闻效率最高的一种使用方式。

Why

Jackson相对Gson、JSON-lib更为高效,使用灵活方便,所以应用广泛。

注意fastjson的效率一般情况下是要超过Jackson的。但是像spring-boot这样的开源平台,最好的方法还是使用更成熟的技术。因为 像这种JSON解析框架是很容易遭受攻击的对象。自己动手实践一下,可发现,Jackson的性能瓶颈主要在ObjectMapper实例化上。而这个类在实际项目使用时都是被封装好的,可以启动时实例化一次,所以在运行时,效率上谁高谁低还是有待商榷的。

How

和大多数人一样,我也是一看源码就头大。所以我的思路是先从简单的demo入手。代码我上传到了github上。地址:

https://github.com/xiexiaojing/yuna

值得一提的是:这虽然是一个经典学习场景的学习工具工程,但是完全可以 作为生产环境的基础代码来用。我们线上跑的代码架子和这个差不了多少。

数据绑定方式源码分析

我们直 接从转换类 ObjectMapper的源码开始看。

1,采用数组做缓存,一开始就分配好空间,提高效率。

<span style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"><span style="padding:0px;margin:0px;max-width:100%;box-sizing:border-box !important;overflow-wrap:break-word !important;">public</span> <span style="padding:0px;margin:0px;max-width:100%;box-sizing:border-box !important;overflow-wrap:break-word !important;">String</span> writeStringAsString(<span style="padding:0px;margin:0px;max-width:100%;box-sizing:border-box !important;overflow-wrap:break-word !important;">String</span> toWrite) throws Exception {</span>

<span style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> ObjectMapper objectMapper = <span style="padding:0px;margin:0px;max-width:100%;box-sizing:border-box !important;overflow-wrap:break-word !important;">new</span> ObjectMapper();</span>

<span style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <span style="padding:0px;margin:0px;max-width:100%;box-sizing:border-box !important;overflow-wrap:break-word !important;">return</span> objectMapper.writeValueAsString(toWrite);</span>

<span style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;">}</span>

写个测试类,传入null。这时候将看到结果

跟踪源码可看到真正产生这个结果的是WriterBasedJsonGenerator这个类。它就是打印了个null。

从源码看到打印时采用了Buffer缓存,从一开始就分配好空间。底层采用数据结构读取速度快,效率高。

2,字符转义处理,做好安全工作。

再给上面的程序,写个测试类,传入任意字符串。这时候将看到结果

字符串被原样打出来,只是两边多了两个引号,代表是字符串,不是数字。

看一下处理的源码,还是WriterBasedJsonGenerator这个类。

对于文本,首先判断文本是否太长,长的话就跳到另一个方法,这个方法用将字符串分段分而治之。看到 Escape这个单词,就要想到为了信息安全而将字符转义了。在公司或者github不时会收到jackson、fastjson漏洞,要求升级到XXX版本以上。对,所谓的升级补漏洞最重要的就是给需要 Escape的列表加了 些字符。

在文章What部分提到:「流式API是江湖传闻效率最高的一种使用方式」。我们先来验证一下这个传闻是不是真的。

首先构造一个Pojo:

@Data

public class Pojo {

private int id;

private String value;

}

使用数据绑定方式

public String writePojoAsString(Pojo toWrite) throws Exception {

return objectMapper.writeValueAsString(toWrite);

}

使用流式API

public String writeJsonGenerator(Pojo toWrite) throws Exception {

JsonFactory factory = new JsonFactory();

ByteArrayOutputStream os = new ByteArrayOutputStream(); //不能加上缓冲 有新增的方法

JsonGenerator jsonGenerator = factory.createGenerator(os, JsonEncoding.UTF8);

//对象开始

jsonGenerator.writeStartObject();

jsonGenerator.writeNumberField("id", toWrite.getId());

jsonGenerator.writeStringField("value", toWrite.getValue());

jsonGenerator.writeEndObject();

jsonGenerator.flush();

jsonGenerator.close();

return os.toString();

}

因为初始化时要先实例化ObjectMapper,有时间消耗。为了不让流式API转这个便宜。将两个要测试的方法写到一个类里,初始化时先实例化ObjectMapper。并且先运行流式API。

测试结果来看流式API要比数据绑定快近十倍。如果交换执行顺序会发现差距更为明显。

跟进ObjectMapper里就会发现,底层其中数据绑定调用的也是同样的API。只是它在进行数据转换的同时还进行了其他的工作。这些工作比如转义、分段等。就看这些是不是我们需要的。

总结

说自己掌握了一门语言至少要掌握基本的语法和数据结构、核心库、常用第三方框架、流行的开发框架和部署方法。而这些知识底层有很强的关联性,不断增加自己的知识的深度,更好的触类旁通。

推荐阅读

Java异常处理总结

学习Spring的思考框架

JAVA数据处理的常用技术

代码荣辱观-以运用风格为荣,以随意编码为耻

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章