5. JsonFactory工厂而已,还蛮有料,这是我没想到的
少年易学老难成,一寸光阴不可轻。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。
前言
各位好,我是YourBatman。前面用四篇文章介绍完了Jackson底层流式API的读(JsonParser)、写(JsonGenerator)操作,我们清楚的知道,这哥俩都是abstract抽象类,使用时并没有显示的去new它们的(子类)实例,均通过一个工厂来搞定,这便就是本文的主角JsonFactory
。
通过名称就知道,这是工厂设计模式。Jackson它并不建议你直接new读/写实例,因为那过于麻烦。为了对使用者屏蔽这些复杂的构造细节,于是就有了JsonFactory
实例工厂的出现。
可能有的人会说,一个对象工厂有什么好了解的,很简单嘛。非也非也,一件事情本身的复杂度并不会凭空消失,而是从一个地方转移到另外一个地方,这另外一个地方指的就是JsonFactory。因此按照本系列的定位,了解它你绕不过去。
版本约定
- Jackson版本:
2.11.0
- Spring Framework版本:
5.2.6.RELEASE
- Spring Boot版本:
2.3.0.RELEASE
正文
JsonFactory是Jackson的(最)主要工厂类,用于 配置和构建JsonGenerator
和JsonParser
,这个工厂实例是线程安全的,因此可以重复使用。
作为一个实例工厂,它最重要的职责当然是创建实例对象。本工厂职责并不单一,它负责读、写两种实例的创建工作。
创建JsonGenerator实例
JsonGenerator它负责向目的地写数据,因此强调的是目的地在哪?如何写?
如截图所示,一共有六个重载方法用于构建JsonGenerator实例,多个重载方法目的是对使用者友好,我们可以认为最终效果是一样的。比如,底层实现是:
JsonFactory:
@Override
public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException {
IOContext ctxt = _createContext(out, false);
ctxt.setEncoding(enc);
// 如果编码是UTF-8
if (enc == JsonEncoding.UTF8) {
return _createUTF8Generator(_decorate(out, ctxt), ctxt);
}
// 使用指定的编码把OutputStream包装为一个writer
Writer w = _createWriter(out, enc, ctxt);
return _createGenerator(_decorate(w, ctxt), ctxt);
}
这就解释了,为何在详解JsonGenerator的这篇文章中,我一直以UTF8JsonGenerator
作为实例进行讲解,因为例子中指定的编码就是UTF-8嘛。当然,即使你自己不显示的指定编码集,默认情况下Jackson也是使用UTF-8:
JsonFactory:
@Override
public JsonGenerator createGenerator(OutputStream out) throws IOException {
return createGenerator(out, JsonEncoding.UTF8);
}
示例:
@Test
public void test1() throws IOException {
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator jsonGenerator1 = jsonFactory.createGenerator(System.out);
JsonGenerator jsonGenerator2 = jsonFactory.createGenerator(System.out, JsonEncoding.UTF8);
System.out.println(jsonGenerator1);
System.out.println(jsonGenerator2);
}
运行程序,输出:
com.fasterxml.jackson.core.json.UTF8JsonGenerator@cb51256
com.fasterxml.jackson.core.json.UTF8JsonGenerator@59906517
创建JsonParser实例
JsonParser它负责从一个JSON字符串中提取出值,因此它强调的是数据从哪来?如何解析?
如截图所示,一共11个重载方法(其实最后一个不属于重载)用于构建JsonParser实例,它的底层实现是根据不同的数据媒介,使用了不同的处理方式,最终生成UTF8StreamJsonParser/ReaderBasedJsonParser
。
你会发现这几个重载方法均无需我们指定编码集,那它是如何确定使用何种编码去解码形如byte[]数组这种数据来源的呢?这得益于其内部的编码自动发现机制实现,也就是ByteSourceJsonBootstrapper#detectEncoding()
这个方法。
示例:
@Test
public void test2() throws IOException {
JsonFactory jsonFactory = new JsonFactory();
JsonParser jsonParser1 = jsonFactory.createParser("{}");
// JsonParser jsonParser2 = jsonFactory.createParser(new FileReader("..."));
JsonParser jsonParser3 = jsonFactory.createNonBlockingByteArrayParser();
System.out.println(jsonParser1);
// System.out.println(jsonParser2);
System.out.println(jsonParser3);
}
运行程序,输出:
com.fasterxml.jackson.core.json.ReaderBasedJsonParser@5f3a4b84
com.fasterxml.jackson.core.json.async.NonBlockingJsonParser@27f723
创建非阻塞实例
值得注意的是,上面截图的11个方法中,最后一个并非重载。它创建的是一个非阻塞JSON解析器,也就是NonBlockingJsonParser
,并且它还没有指定入参(数据源)。
NonBlockingJsonParser
是Jackson在2.9版本新增的的一个解析器,目标是进一步提升效率、性能。但它也有局限的地方:只能解析使用UTF-8编码的内容,否则抛出异常。
当然喽,现在UTF-8编码几乎成为了标准编码手段,问题不大。但是呢,我自己玩了玩NonBlockingJsonParser
,发现复杂度增加不少(玩半天才玩明白),效果却并不显著,因此这里了解一下便可,至少目前不建议深入探究。
小贴士:不管是Spring还是Redis的反序列化,使用的均是普通的解析器(阻塞IO)。因为JSON解析过程从来都不会是性能瓶颈(特殊场景除外)
JsonFactory的Feature
除了JsonGenerator和JsonParser有Feature来控制行为外,JsonFactory也有自己的Feature特征,来控制自己的行为,可以理解为它对读/写均生效。
同样的也是一个内部枚举类:
public enum Feature {
INTERN_FIELD_NAMES(true),
CANONICALIZE_FIELD_NAMES(true),
FAIL_ON_SYMBOL_HASH_OVERFLOW(true),
USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING(true)
}
小贴士:枚举值均为bool类型,括号内为默认值
每个枚举值都控制着JsonFactory不同的行为。
INTERN_FIELD_NAMES(true)
这是Jackson所谓的key缓存:对JSON的字段名是否调用String#intern
方法,放进字符串常量池里,以提高效率,默认是true。
小贴士:Jackson在调用String#intern之前使用
InternCache
(继承自ConcurrentHashMap)挡了一层,以防止高并发条件下intern效果不显著问题
intern()方法的作用这个老生常谈的话题了,解释为:当调用intern方法时,如果字符串池已经包含一个等于此String对象的字符串(内容相等),则返回池中的字符串。否则,将此 String放进池子里。下面写个例子增加感受感受:
@Test
public void test2() {
String str1 = "a";
String str2 = "b";
String str3 = "ab";
String str4 = str1 + str2;
String str5 = new String("ab");
System.out.println(str5.equals(str3)); // true
System.out.println(str5 == str3); // false
// str5.intern()去常量池里找到了ab,所以直接返回常量池里的地址值了,因此是true
System.out.println(str5.intern() == str3); // true
System.out.println(str5.intern() == str4); // false
}
可想而知,开启这个小功能的意义还是蛮大的。因为同一个格式的JSON串被多次解析的可能性是非常之大的,想想你的Rest API接口,被调用多少次就会进行了多少次JSON解析(想想高并发场景)。这是一种用空间换时间的思想,所以小小功能,大大能量。
小贴士:如果你的应用对内存很敏感,你可以关闭此特征。但,真的有这种应用吗?有吗?
值得注意的是:此特征必须是CANONICALIZE_FIELD_NAMES
也为true(开启)的情况下才有效,否则是无效的。
CANONICALIZE_FIELD_NAMES(true)
是否需要规范化属性名。所谓的规范化处理,就是去字符串池里尝试找一个字符串出来,默认值为true。规范化借助的是ByteQuadsCanonicalizer
去处理,简而言之会根据Hash值来计算每个属性名存放的位置~
小贴士:ByteQuadsCanonicalizer拥有一套优秀的Hash算法来规范化属性存储,提高效率,抵御攻击(见下特征)
此特征开启了,INTERN_FIELD_NAMES
特征的开启才有意义~
FAIL_ON_SYMBOL_HASH_OVERFLOW(true)
当ByteQuadsCanonicalizer
处理hash碰撞达到一个阈值时,是否快速失败。
什么时候能达到阈值?官方的说明是:若触发了阈值,这基本可以确定是Dos(denial-of-service)攻击,制造了非常多的相同Hash值的key,这在正常情况下几乎是没有发生的可能性的。
所以,开启此特征值,可以防止攻击,在提高性能的同时也确保了安全。
USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING(true)
是否使用BufferRecycler、ThreadLocal、SoftReference
来有效的重用底层的输入/输出缓冲区。这个特性在后端服务(JavaEE)环境下是很有意义的,提效明显。但是对于在Android环境下就不见得了~
总而言之言而总之,JsonFactory的这几个特征值都建议开启,也就是维持默认即可。
定制读/写实例
读写行为的控制是通过各自的Feature来控制的,JsonFactory作为一个功能并非单一的工厂类,需要既能够定制化读JsonParser,也能定制化写JsonGenerator。
为此,对应的API它都提供了三份(一份定制化自己的Feature):
public JsonFactory enable(JsonFactory.Feature f);
public JsonFactory enable(JsonParser.Feature f);
public JsonFactory enable(JsonGenerator.Feature f);
public JsonFactory disable(JsonFactory.Feature f);
public JsonFactory disable(JsonParser.Feature f);
public JsonFactory disable(JsonGenerator.Feature f);
// 合二为一的Configure方法
public JsonFactory configure(JsonFactory.Feature f, boolean state);
public JsonFactory configure(JsonParser.Feature f, boolean state);
public JsonFactory configure(JsonGenerator.Feature f, boolean state);
使用示例:
@Test
public void test3() throws IOException {
String jsonStr = "{\"age\":18, \"age\": 28 }";
JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
try (JsonParser jsonParser = factory.createParser(jsonStr)) {
// 使用factory定制将不生效
// factory.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jsonParser.getCurrentName();
if ("age".equals(fieldname)) {
jsonParser.nextToken();
System.out.println(jsonParser.getIntValue());
}
}
}
}
运行程序,抛出异常。证明特征开启成功,符合预期。
com.fasterxml.jackson.core.JsonParseException: Duplicate field 'age'
at [Source: (String)"{"age":18, "age": 28 }"; line: 1, column: 17]
在使用JsonFactory定制化读/写实例的时需要特别注意:请务必确保在factory.createXXX()
之前配置好对应的Feature特征,若在实例创建好之后再弄的话,对已经创建的实例无效。
小贴士:实例创建好后若你还想定制,可以使用实例自己的对应API操作
JsonFactoryBuilder
JsonFactory负责基类和实现类的双重任务,是比较重的,分离得也不彻底。同时,现在都2020年了,对于这种构建类工厂如果还不用Builder模式就现在太out了,书写起来也非常不便:
@Test
public void test4() throws IOException {
JsonFactory jsonFactory = new JsonFactory();
// jsonFactory自己的特征
jsonFactory.enable(JsonFactory.Feature.INTERN_FIELD_NAMES);
jsonFactory.enable(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES);
jsonFactory.enable(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING);
// JsonParser的特征
jsonFactory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
jsonFactory.enable(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER);
// JsonGenerator的特征
jsonFactory.enable(JsonGenerator.Feature.QUOTE_FIELD_NAMES);
jsonFactory.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
// 创建读/写实例
// jsonFactory.createParser(...);
// jsonFactory.createGenerator(...);
}
功能实现上没毛病,但总显得不够优雅。同时上面也说了:定制化操作一定得在create创建动作之前执行,这全靠程序员自行控制。
Jackson在2.10版本新增了一个JsonFactoryBuilder
构件类,让我们能够基于builder模式优雅的构建出一个JsonFactory
实例。
小贴士:2.10版本是2019.09发布的
比如上面例子的代码使用JsonFactoryBuilder
可重构为:
@Test
public void test4() throws IOException {
JsonFactory jsonFactory = new JsonFactoryBuilder()
// jsonFactory自己的特征
.enable(INTERN_FIELD_NAMES)
.enable(CANONICALIZE_FIELD_NAMES)
.enable(USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING)
// JsonParser的特征
.enable(ALLOW_SINGLE_QUOTES, ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)
// JsonGenerator的特征
.enable(QUOTE_FIELD_NAMES, ESCAPE_NON_ASCII)
.build();
// 创建读/写实例
// jsonFactory.createParser(...);
// jsonFactory.createGenerator(...);
}
对比起来,使用Builder模式优雅太多了。
因为JsonFactory是线程安全的,因此一般情况下全局我们只需要一个JsonFactory实例即可,推荐使用JsonFactoryBuilder
去完成你的构建。
小贴士:使用JsonFactoryBuilder确保你的Jackson版本至少是2.10版本哦~
SPI方式
从源码包里发现,JsonFactory是支持Java SPI方式构建实例的。
文件内容为:
com.fasterxml.jackson.core.JsonFactory
因此,我可以使用Java SPI的方式得到一个JsonFactory实例:
@Test
public void test5() {
ServiceLoader<JsonFactory> jsonFactories = ServiceLoader.load(JsonFactory.class);
System.out.println(jsonFactories.iterator().next());
}
运行程序,妥妥的输出:
com.fasterxml.jackson.core.JsonFactory@4abdb505
这种方式,玩玩即可,在这里没实际用途。
总结
本文围绕JsonFactory工厂为核心,讲解了它是如何创建、定制读/写实例的。对于自己的实例的创建共有三种方式:
- 直接new实例
- 使用
JsonFactoryBuilder
构建(需要2.10或以上版本) - SPI方式创建实例
其中方式2是被推荐的,如果你的版本较低,就老老实实使用方式1呗。至于方式3嘛,玩玩就行,别当真。
至此,jackson-core的三大核心内容:JsonGenerator、JsonParser、JsonFactory
全部介绍完了,它们是jackson 其它所有模块 的基石,需要掌握扎实喽。
下篇文章更有意思,会分析Jackson里Feature机制的设计,使用补码、掩码来实现是高效的体现,同时设计上也非常优美,下文见。
相关推荐:
- Fastjson到了说再见的时候了
- 1. 初识Jackson -- 世界上最好的JSON库
- 2. 妈呀,Jackson原来是这样写JSON的
- 3. 懂了这些,方敢在简历上说会用Jackson写JSON
- 4. JSON字符串是如何被解析的?JsonParser了解一下
5. JsonFactory工厂而已,还蛮有料,这是我没想到的的更多相关文章
- centos clamav杀毒软件安装配置及查杀,没想到linux下病毒比windows还多!
centos clamav杀毒软件安装配置及查杀,没想到linux下病毒比windows还多! 一.手动安装 1.下载(官网) cd /soft wget http://www.clam ...
- sharding-jdbc 分库分表的 4种分片策略,还蛮简单的
上文<快速入门分库分表中间件 Sharding-JDBC (必修课)>中介绍了 sharding-jdbc 的基础概念,还搭建了一个简单的数据分片案例,但实际开发场景中要远比这复杂的多,我 ...
- Spring测试框架JUnit4.4 还蛮详细的
TestContext 可以运行在 JUnit 3.8.JUnit 4.4.TestNG 等测试框架下. Spring的版本2.5+JUnit4.4+log4j1.2.12 @RunWith(Spri ...
- 没想到MySQL还会问这些...
前言 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 在前一阵子,大哥问过我:"你知道MySQL的原子性是怎么保 ...
- 没想到 Hash 冲突还能这么玩,你的服务中招了吗?
背景 其实这个问题我之前也看到过,刚好在前几天,洪教授在某个群里分享的一个<一些有意思的攻击手段.pdf>,我觉得这个话题还是有不少人不清楚的,今天我就准备来“实战”一把,还请各位看官轻拍 ...
- N皇后求解。万万没想到,只用一个一维数组就搞定了。还体现了回溯。
一.啥是N皇后?先从四皇后入手 给定一个4x4的棋盘,要在棋盘上放置4个皇后.他们的位置有这样的要求,每一列,每一行,每一对角线都能有一个皇后. 你可能会对这个对角线有疑惑,其实就是每一个小正方形的对 ...
- 6. 二十不惑,ObjectMapper使用也不再迷惑
一滴水,用显微镜看,也是一个大世界.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众 ...
- 7. Jackson用树模型处理JSON是必备技能,不信你看
每棵大树,都曾只是一粒种子.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BA ...
- Java二进制和位运算,这一万字准能喂饱你
基础不牢,地动山摇.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BAT的乌托 ...
随机推荐
- CentOS7 firewalld docker 端口映射问题,firewall开放端口后,还是不能访问,解决方案
# 宿主机ip: 192.168.91.19 docker run -itd --name tomcat -p 8080:8080 tomcat /usr/local/apache-tomcat-9. ...
- Redis(一)简介及安装、测试
一.Redis简介: 关于关系型数据库和nosql数据库 关系型数据库是基于关系表的数据库,最终会将数据持久化到磁盘上,而nosql数据 库是基于特殊的结构,并将数据存储到内存的数据库.从性能上而言, ...
- NoSQL和SQL怎么选用?
NoSQL 有分很多种,其中key-value NoSQL (Redis, MemcacheD, etc) 的选用相对比较清楚些,大多是当后端Data storage的cache层来用.这篇主要想请教 ...
- 【Laravel 】faker数据填充详解
安装 在laravel中已经自动集成,无需手动安装.如需在其他地方使用,可使用以下命令进行安装. composer require fzaninotto/faker 为Faker指定中文支持 可通过在 ...
- three.js 制作一个三维的推箱子游戏
今天郭先生发现大家更喜欢看我发的three.js小作品,今天我就发一个3d版本推箱子的游戏,其实webGL有很多框架,three.js并不合适做游戏引擎,但是可以尝试一些小游戏.在线案例请点击博客原文 ...
- 恕我直言,我也是才知道ElasticSearch条件更新是这么玩的
背景 ElasticSearch 的使用度越来越普及了,很多公司都在使用.有做日志搜索的,有做商品搜索的,有做订单搜索的. 大部分使用场景都是通过程序定期去导入数据到 ElasticSearch 中, ...
- Python unichr() 函数
描述 unichr() 函数 和 chr() 函数功能基本一样, 只不过是返回 unicode 的字符.高佣联盟 www.cgewang.com 注意: Python3 不支持 unichr(),改用 ...
- CPU监控 线段树裸题
LINK:bzoj3064 此题甚好码了20min停下来思考的时候才发现不对的地方有点坑... 还真不好写来着 可这的确是线段树的裸题...我觉得我写应该没有什么大问题 不过思路非常的紊乱 如果是自己 ...
- Archlinux 最新安装方法 (2020.07.01-x86_64)之虚拟机 BIOS 安装
话不多说,直接上干货 准备 去Arch 官网,选择一个合适的国内镜像站下载 Arch 安装包 ISO,地址如下: https://www.archlinux.org/download/ 一.创建虚拟机 ...
- C# 实现线程的常用几种方式
前言 在各个开发语言中,线程是避免不了的,或许通过表象看不出来,但是真的无处不在.就比如一个Web程序,平时或许只注重增删改查的开发,根本没有编写相关多线程的的代码,但是请求内部的时候,已经分配了对应 ...