少年易学老难成,一寸时光不可轻。本文已被 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); }
示例:
@Testpublic 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@cb51256com.fasterxml.jackson.core.json.UTF8JsonGenerator@59906517
创立JsonParser实例
JsonParser它负责从一个JSON字符串中提取出值,因而它强调的是数据从哪来?如何解析?
如截图所示,一共11个重载办法(其实最初一个不属于重载)用于构建JsonParser实例,它的底层实现是依据不同的数据媒介,应用了不同的解决形式,最终生成UTF8StreamJsonParser/ReaderBasedJsonParser
。
你会发现这几个重载办法均无需咱们指定编码集,那它是如何确定应用何种编码去解码形如byte[]数组这种数据起源的呢?这得益于其外部的编码主动发现机制实现,也就是ByteSourceJsonBootstrapper#detectEncoding()
这个办法。
示例:
@Testpublic 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@5f3a4b84com.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放进池子里。上面写个例子减少感触感触:
@Testpublic 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);
应用示例:
@Testpublic 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了,书写起来也十分不便:
@Testpublic 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
可重构为:
@Testpublic 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实例:
@Testpublic 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理解一下
关注A哥
Author | A哥(YourBatman) |
---|---|
集体站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
常识星球 | BAT的乌托邦 |
每日文章举荐 | 每日文章举荐 |