对象映射器 - 线程安全和性能的最佳实践

总结

我想在下面描述的用例上下文中找到使用和/或线程安全和性能方面的最佳实践。ObjectMapperObjectReader

背景

我有一个帮助器类(Json.java),其中该方法用于从字符串转换为给定(json可映射)类的对象。toObject()ObjectMapperjson

问题/疑问

我已经读过,通常建议它是完全线程安全的,但我主要看到它是在非通用上下文中,其中要读取的类是预定义的。在这种情况下,您认为在线程安全性和性能方面的最佳实践是什么?在代码中,我有三个建议,我可以将它们视为一个起点。ObjectReader

我试图通过源代码和文档来查看,但我的理论Java技能不足以从中得出答案。我也看过SO和其他地方的类似问题,但没有找到任何与我的情况足够接近的问题。jackson-databind

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

public abstract class Json {

    private static final ObjectMapper jsonMapper = new ObjectMapper();
    
    // NOTE: jsonReader is only relevant for Suggestion 3.
    private static final ObjectReader jsonReader = jsonMapper.reader(); 

    // Suggestion 1:
    public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
        return jsonMapper.readValue(json, type);
    }

    // Suggestion 2:
    public static <T> T toObject2(final Class<T> type, final String json) throws IOException {
        return jsonMapper.readerFor(type).readValue(json);
    }

    // Suggestion 3:
    public static <T> T toObject3(final Class<T> type, final String json) throws IOException {
        return jsonReader.forType(type).readValue(json);
    }

    // Remainder of class omitted for brevity.
}

答案 1
private static final ObjectMapper jsonMapper = new ObjectMapper();

构造实例是一项成本相对较高的操作,因此建议创建一个对象并重用它。你做对了。ObjectMapperfinal

// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
    return jsonMapper.readValue(json, type);
}

您总是JSON读取到POJO,因此让我们精确而清晰,并使用.ObjectReader

// Suggestion 2:
public static <T> T toObject2(final Class<T> type, final String json) throws IOException {
    return jsonMapper.readerFor(type).readValue(json);
}

// Suggestion 3:
public static <T> T toObject3(final Class<T> type, final String json) throws IOException {
    return jsonReader.forType(type).readValue(json);
}

真的没有区别。这两种方法都将构造一个新对象:前者 () 将直接为您提供一个完全构建的实例,后者 () 将补充尚未使用的对象并返回一个即用型对象。我宁愿选择选项2,因为我不想保留该字段。ObjectReaderjsonMapper.readerFor(type)jsonReader.forType(type)jsonReader

您不必担心性能或线程安全性。尽管创建一个可能很昂贵(或者从中制作一个副本),但获取和使用s是轻量级的,并且完全线程安全。ObjectMapperObjectReader

Java文档(强调我的):

使用“突变工厂”模式,使实例不可变(因此完全线程安全,无需外部同步);新实例是为不同的配置构造的。实例最初由实例构建,并且可以重用,共享,缓存;这既是因为线程安全,也是因为实例的重量相对较轻ObjectMapper

我最近自己也遇到了这些问题,并决定作为工厂方法。它非常方便,特别是当您想要稍微自定义一个,或者像我一样,调整一个.ObjectMapper#reader(InjectableValues)ObjectReaderDeserializationContext

顺便说一句,这是一个很好的问题。


答案 2

关于并发

ObjectMapper与此处无关。
看起来对你的方案没有帮助。
其规格说:ObjectReaderObjectReader

可用于反序列化参数(如要使用的根类型或要更新的对象,而不是构造新实例)的按序列化配置的生成器对象。

请注意,如果 和 的两个实例的配置在序列化/反序列化客户端调用之间未更改,则和 的两个实例都是线程安全的。
指定的确:ObjectMapperObjectReaderObjectReader

映射器实例是完全线程安全的,前提是实例的所有配置都发生在任何读取或写入调用之前。

虽然在更新其配置的方式上具有不可变的差异,但正如其文档所述,它将返回一个新实例:ObjectReader

使用“突变工厂”模式,使实例不可变(因此完全线程安全,无需外部同步);新实例是为不同的配置构造的。

在你的要求中,你不希望在客户端调用之间更改配置。因此,使用看起来更相关。
所以我会消除3)方式和2)方式,因为这是工厂方法。仍然不重要使用这里。ObjectMapperjsonMapper.readerFor(type)ObjectReaderObjectReader

所以最简单和常见的方法看起来更好:

// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
    return jsonMapper.readValue(json, type);
 }

关于性能

此外,记住是不可变的。因此,2 和 3 方法在每个调用时创建新的实例。它看起来不是性能的良好提示。
是的,这些是轻量级对象,但每次创建它们都需要付出代价。
文档说:ObjectReaderObjectReaderObjectReader

实例最初由ObjectMapper构建,可以重复使用,共享,缓存;这既是因为线程安全,也是因为实例的重量相对较轻。

在那里,您不会重用这些实例。因此,您将失去缓存和性能方面的任何好处。
您可以将它们存储到字段中并重用它们,但只有在您需要提高实际性能并且当然在得出结论之前进行测量时才能这样做。MapObjectMapper

结论:对于您的用例,我认为第一个解决方案(ObjectMapper)