34

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

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ%3D%3D&%3Bmid=2247485117&%3Bidx=1&%3Bsn=4d48425502bff29ee3d08f728789249f&%3Bchksm=fafdec13cd8a6505dd2f1b5974242423a66e1e4aed31bbaa1ce5beb829ed0c9035e0bffac4dc&%3Btoken=688774514&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

背景

每隔一段时间,我会梳理一些平时经常用的类库源码。这次和大多数时候一样,我又选了一个简单的来看点源码。这次选的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。这时候将看到结果

J3qiIzR.jpg!web

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

zaqI7vN.png!web

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

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

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

fIVFVfn.jpg!web

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

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

qiI7jmE.jpg!web

对于文本,首先判断文本是否太长,长的话就跳到另一个方法,这个方法用将字符串分段分而治之。看到 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。

6Jvu2m7.jpg!web

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

nInyI33.jpg!web

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

aQBRNvu.jpg!web

总结

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

推荐阅读

Java异常处理总结

学习Spring的思考框架

JAVA数据处理的常用技术

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK