少年易学老难成,一寸光阴不可轻。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的 专栏供以避免费学习。关注公众号【 BAT的乌托邦】逐个击破,深刻掌握,拒绝浅尝辄止。
各位好,我是YourBatman。前面用四篇文章介绍完了Jackson底层流式API的读(JsonParser)、写(JsonGenerator)操做,咱们清楚的知道,这哥俩都是abstract抽象类,使用时并无显示的去new它们的(子类)实例,均经过一个工厂来搞定,这便就是本文的主角JsonFactory
。java
经过名称就知道,这是工厂设计模式。Jackson它并不建议你直接new读/写实例,由于那过于麻烦。为了对使用者屏蔽这些复杂的构造细节,因而就有了JsonFactory
实例工厂的出现。git
可能有的人会说,一个对象工厂有什么好了解的,很简单嘛。非也非也,一件事情自己的复杂度并不会凭空消失,而是从一个地方转移到另一个地方,这另一个地方指的就是JsonFactory。所以按照本系列的定位,了解它你绕不过去。程序员
2.11.0
5.2.6.RELEASE
2.3.0.RELEASE
JsonFactory是Jackson的(最)主要工厂类,用于 配置和构建JsonGenerator
和JsonParser
,这个工厂实例是线程安全的,所以能够重复使用。github
做为一个实例工厂,它最重要的职责固然是建立实例对象。本工厂职责并不单一,它负责读、写两种实例的建立工做。算法
JsonGenerator它负责向目的地写数据,所以强调的是目的地在哪?如何写?json
如截图所示,一共有六个重载方法用于构建JsonGenerator实例,多个重载方法目的是对使用者友好,咱们能够认为最终效果是同样的。好比,底层实现是:segmentfault
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它负责从一个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解析过程历来都不会是性能瓶颈(特殊场景除外)
除了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不一样的行为。
这是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(开启)的状况下才有效,不然是无效的。
是否须要规范化属性名。所谓的规范化处理,就是去字符串池里尝试找一个字符串出来,默认值为true。规范化借助的是ByteQuadsCanonicalizer
去处理,简而言之会根据Hash值来计算每一个属性名存放的位置~
小贴士:ByteQuadsCanonicalizer拥有一套优秀的Hash算法来规范化属性存储,提升效率,抵御攻击(见下特征)
此特征开启了,INTERN_FIELD_NAMES
特征的开启才有意义~
当ByteQuadsCanonicalizer
处理hash碰撞达到一个阈值时,是否快速失败。
何时能达到阈值?官方的说明是:若触发了阈值,这基本能够肯定是Dos(denial-of-service)攻击,制造了很是多的相同Hash值的key,这在正常状况下几乎是没有发生的可能性的。
因此,开启此特征值,能够防止攻击,在提升性能的同时也确保了安全。
是否使用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操做
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版本哦~
从源码包里发现,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工厂为核心,讲解了它是如何建立、定制读/写实例的。对于本身的实例的建立共有三种方式:
JsonFactoryBuilder
构建(须要2.10或以上版本)其中方式2是被推荐的,若是你的版本较低,就老老实实使用方式1呗。至于方式3嘛,玩玩就行,别当真。
至此,jackson-core的三大核心内容:JsonGenerator、JsonParser、JsonFactory
所有介绍完了,它们是jackson 其它全部模块 的基石,须要掌握扎实喽。
下篇文章更有意思,会分析Jackson里Feature机制的设计,使用补码、掩码来实现是高效的体现,同时设计上也很是优美,下文见。
Author | A哥(YourBatman) |
---|---|
我的站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |