乐趣区

关于jackson: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 的(最)次要工厂类,用于 配置和构建 JsonGeneratorJsonParser,这个工厂实例是 线程平安 的,因而能够重复使用。

作为一个实例工厂,它最重要的职责当然是创立实例对象。本工厂职责并不繁多,它负责读、写两种实例的创立工作。

创立 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 工厂为外围,解说了它是如何创立、定制读 / 写实例的。对于本人的实例的创立共有三种形式:

  1. 间接 new 实例
  2. 应用 JsonFactoryBuilder 构建(须要 2.10 或以上版本)
  3. 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
E-mail yourbatman@qq.com
微 信 fsx641385712
沉闷平台
公众号 BAT 的乌托邦(ID:BAT-utopia)
常识星球 BAT 的乌托邦
每日文章举荐 每日文章举荐

退出移动版