Answers:
是的,建议您这样做是安全的。
您所引用页面的唯一警告是,一旦共享了映射器,您将无法修改它的配置。但是您不更改配置,这样就可以了。如果确实需要更改配置,则可以从静态块执行此操作,也可以。
编辑:(2013/10)
在2.0及更高版本中,可以通过注意到还有一个更好的方法来增强上述功能:使用ObjectWriter
和ObjectReader
对象,可以通过构造ObjectMapper
。它们是完全不变的,线程安全的,这意味着从理论上讲甚至不可能引起线程安全问题(ObjectMapper
如果代码尝试重新配置实例,则可能发生此问题)。
DateFormat
确实被克隆了。那里很可疑,但是你被遮住了。:)
ObjectMapper
吗?方法被命名为writer()
和reader()
(和一些readerFor()
,writerFor()
)。
mapper.with()
调用(因为Jackson中的“ with”表示构造新实例和线程安全执行)。但是关于配置更改:不进行检查,因此ObjectMapper
必须保护对配置的访问。至于“ copy()”:是的,它会创建一个新的新副本,该副本可以根据相同的规则进行完全(重新)配置:先完全配置,然后再使用,这样就可以了。有不平凡的成本相关联(因为副本不能使用任何缓存的处理程序),但这是安全的方法,是的。
尽管ObjectMapper是线程安全的,但我强烈建议不要将其声明为静态变量,尤其是在多线程应用程序中。甚至不是因为这是一种不好的做法,而是因为您面临着严重的死锁风险。我是根据自己的经验讲的。我创建了一个具有4个相同线程的应用程序,这些线程正在从Web服务获取和处理JSON数据。根据线程转储,我的应用程序经常停在以下命令上:
Map aPage = mapper.readValue(reader, Map.class);
除此之外,性能也不佳。当我将静态变量替换为基于实例的变量时,停滞消失了,性能提高了三倍。也就是说,在40分钟56秒内处理了240万个JSON文档,而不是之前的2.5个小时。
ObjectMapper
为每个类实例创建一个实例可以防止锁定,但是稍后可能会对GC造成很大的负担(请为您创建的类的每个实例想象一个ObjectMapper实例)。可以采用一种中间路径方法,而不是在ObjectMapper
整个应用程序中仅保留一个(公共)静态实例,而可以在每个类中声明一个(私有)静态实例。这将减少全局锁定(通过按类分配负载),也不会创建任何新对象,因此也可以打开GC。ObjectMapper
ObjectPool
是你可以用最好的方式-从而提供最佳GC
和Lock
表演。您可以参考以下链接以了解apache-common的ObjectPool
实现。 commons.apache.org/proper/commons-pool/api-1.6/org/apache/...
ObjectMapper
地方,但只得到ObjectReader
/ ObjectWriter
实例(通过辅助方法),保留那些在其他地方引用(或动态调用)。这些读取器/写入器对象不仅是完全线程安全的wrt重新配置,而且非常轻量(wrt映射器实例)。因此,保留数千个引用不会增加太多内存使用。
尽管就线程安全而言,声明静态ObjectMapper是安全的,但是您应该意识到,在Java中构造静态Object变量被认为是不好的做法。有关更多详细信息,请参见为什么将静态变量视为邪恶?(如果您愿意,我的回答)
简而言之,应避免使用静态方法,因为这样会使编写简洁的单元测试变得困难。例如,对于静态最终ObjectMapper,您无法将JSON序列化换成虚拟代码或无操作。
另外,静态的final阻止您在运行时重新配置ObjectMapper。您可能现在没有想到这样做的原因,但是如果您将自己锁定在静态的最终模式中,那么只需拆除类加载器,就可以重新初始化它。
在使用ObjectMapper的情况下,它很好,但是通常是不好的做法,与使用单例模式或控制反转来管理寿命长的对象相比,它没有任何优势。
readerFor
和writerFor
其创建ObjectReader
和ObjectWriter
按需实例。因此,我想将具有初始配置的映射器放置在静态某处,然后根据需要使用按情况配置的读取器/写入器?
如果您不想将其定义为静态最终变量,但想节省一些开销并确保线程安全,则可以从此PR中了解到一个技巧。
private static final ThreadLocal<ObjectMapper> om = new ThreadLocal<ObjectMapper>() {
@Override
protected ObjectMapper initialValue() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
};
public static ObjectMapper getObjectMapper() {
return om.get();
}
归功于作者。
ObjectMapper
将被附加到可能是池一部分的线程上。
com.fasterxml.jackson.databind.type.TypeFactory._hashMapSuperInterfaceChain(HierarchicType)
com.fasterxml.jackson.databind.type.TypeFactory._findSuperInterfaceChain(Type, Class)
com.fasterxml.jackson.databind.type.TypeFactory._findSuperTypeChain(Class, Class)
com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(Class, Class, TypeBindings)
com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(JavaType, Class)
com.fasterxml.jackson.databind.type.TypeFactory._fromParamType(ParameterizedType, TypeBindings)
com.fasterxml.jackson.databind.type.TypeFactory._constructType(Type, TypeBindings)
com.fasterxml.jackson.databind.type.TypeFactory.constructType(TypeReference)
com.fasterxml.jackson.databind.ObjectMapper.convertValue(Object, TypeReference)
com.fasterxml.jackson.databind.type.TypeFactory类中的_hashMapSuperInterfaceChain方法已同步。在高负载下看到相同的竞争。
避免静态ObjectMapper的另一个原因
TypeReference
使用起来有点昂贵:如果可能的话,将其解决JavaType
将避免大量处理(TypeReference
不幸的是,由于我不会在这里介绍的原因,无法对其进行缓存),因为它们是“完全解析的”(超级类型,通用类型等)。
ObjectMapper
在ObjectMapper#setDateFormat()
调用之后是否仍然是线程安全的。众所周知,这SimpleDateFormat
不是线程安全的,因此ObjectMapper
除非它例如SerializationConfig
在每个线程之前克隆writeValue()
(我怀疑),否则不会如此。你能揭穿我的恐惧吗?