Should I declare Jackson's ObjectMapper as a static field?
杰克逊图书馆的ObjectMapper类似乎是线程安全的。
这是否意味着我应该将我的ObjectMapper声明为这样的静态字段?
1 2 3
| class Me {
private static final ObjectMapper mapper = new ObjectMapper();
} |
而不是像这样的实例级字段?
1 2 3
| class Me {
private final ObjectMapper mapper = new ObjectMapper();
} |
是的,这是安全的,推荐的。
您所引用的页面中唯一的警告是,一旦映射器被共享,您就不能修改它的配置;但是您没有更改配置,所以这是可以的。如果您确实需要更改配置,那么您可以从静态块中进行更改,这样也可以。
编辑:(2013/10)
在2.0及以上版本中,可以通过注意到还有更好的方法来增加上述内容:使用ObjectWriter和ObjectReader对象,这些对象可以由ObjectMapper构造。它们是完全不可变的,线程安全的,这意味着在理论上甚至不可能导致线程安全问题(如果代码试图重新配置实例,则ObjectMapper可能发生这种问题)。
- 你有参考资料吗?
- 奥普斯我知道你是杰克逊的开发者。感谢您的优秀软件!我把它和jsonObject和gjson进行了比较,发现Jackson满足了我的需要。
- 嘿,没问题——很高兴听到这个
- @斯塔克曼:如果在调用ObjectMapper#setDateFormat()之后ObjectMapper仍然是线程安全的,我有点担心。众所周知,SimpleDateFormat不是线程安全的,因此,除非它在每个writeValue()之前克隆(我怀疑)例如SerializationConfig,否则ObjectMapper不会是线程安全的。你能揭穿我的恐惧吗?
- DateFormat确实是在遮光罩下克隆的。很可疑,但你被掩盖了。:)
- 谢谢,我在DateBasedDeserializer和DateTimeSerializerBase中找到了克隆的代码。
- 在大型企业应用程序的单元/集成测试期间,我遇到了一些奇怪的行为。当把objectmapper作为静态的最终类属性时,我开始面临permgen问题。有人愿意解释可能的原因吗?我使用的是Jackson DataBind 2.4.1版。
- @alejoceballo我的猜测是,您可能正在使用"随机"(任意的、无边界的集合)属性名(如UUID或数字ID)解析文档。如果是这样,规范化可能会变得有问题——然而,对于静态映射器的使用来说,实际上不应该有什么问题。
- 只是为日期映射添加一个提示:Jackson似乎没有使用"yyyy-MM-dd'T'HH:mm:ss.SSSXXX的标准ISO格式。如果我们必须像我一样一直替换日期映射器,不要忘记实现克隆方法。如果不这样做,我想我有一些穿线问题。
- 嗨,Staxman,您能帮助如何从Objectmapper构造ObjectWriter和ObjectReader吗?我还没有找到使用杰克逊图书馆2.7.3的方法。提前通知。
- @米克罗斯克里文,你看过《江户记》吗?!方法被命名为writer()和reader()(还有一些readerFor()和writerFor()。
- 对不起,@staxman你说得对,我错过了是我的错。谢谢你的反馈。
- 当你说"一旦映射器被共享就不能修改它的配置"时,你的意思是说如果完成了,杰克逊抛出了一个异常,还是我必须在那里小心?例如,实例是否在每个"mapper.with()"调用上克隆?我在某个地方读到,这是推荐的方法,例如对于基于请求的决策,比如漂亮地打印JSON。
- 没有mapper.with()调用(因为杰克逊中的"with"意味着构造一个新实例,并执行线程安全)。但对于配置更改:不进行检查,因此必须保护对ObjectMapper的配置访问。至于"copy()":是的,它创建了一个新的拷贝,可以根据相同的规则完全(重新)配置:首先完全配置它,然后使用,这样就可以了。有不平凡的相关成本(因为copy不能使用任何缓存处理程序),但这是安全的方法,是的。
- …至于"为什么不看看是否有人试图重新配置":这将需要额外的状态保持、同步。对于Jackson 3.x来说,使用builder模式可能是有意义的,使ObjectMapper不可变类似于ObjectReader/ObjectWriter:但这是主要的API更改,因此不能用2.x来完成。另一种防止reconfig的方法是只公开ObjectReader和ObjectWriter;保持对mapper的访问只对系统的一小部分可用。
尽管Objectmapper是线程安全的,但我强烈建议不要将其声明为静态变量,尤其是在多线程应用程序中。这并不是因为这是一个糟糕的做法,而是因为你面临着严重的死锁风险。我是根据自己的经验来讲的。我创建了一个具有4个相同线程的应用程序,这些线程从Web服务获取和处理JSON数据。根据线程转储,我的应用程序经常在以下命令上暂停:
1
| Map aPage = mapper. readValue(reader, Map. class); |
除此之外,表现不好。当我用基于实例的变量替换静态变量时,停止消失,性能提高了四倍。也就是说,240万个JSON文档是在40分钟56秒内处理的,而不是之前的2.5小时。
- 加里的回答完全有道理。但是,为每个类实例创建一个ObjectMapper实例可能会阻止锁,但在以后的GC中可能会非常重要(假设为您创建的类的每个实例都创建一个objectmapper实例)。中间路径方法可以是,而不是在应用程序中只保留一个(公共)静态的ObjectMapper实例,而是在每个类中声明一个(私有)静态的ObjectMapper实例。这将减少一个全局锁(通过按类分布负载),并且也不会创建任何新对象,因此GC也会亮起。
- 当然,保持ObjectPool是最好的方法,因此可以提供最好的GC和Lock性能。对于ApacheComon的ObjectPool实现,可以参考以下链接。commons.apache.org/proper/commons pool/api-1.6/org/apache/…
- 我建议另一种方法:将静态ObjectMapper保留在某个地方,但只获取ObjectReader/ObjectWriter实例(通过helper方法),保留对其他地方的引用(或动态调用)。这些读写器对象不仅是完全线程安全的WRT重新配置,而且非常轻(WRT映射器实例)。因此保留数千个引用并不会增加太多的内存使用。
- 对ObjectReader实例的调用没有阻塞,也就是说,在多线程应用程序中调用了ObjectReader.readTree,线程不会被阻塞,等待另一个线程,使用Jackson 2.8.x
虽然在线程安全方面声明静态对象映射器是安全的,但是您应该意识到在Java中构造静态对象变量被认为是不好的实践。有关更多详细信息,请参阅为什么静态变量被认为是有害的?(如果你愿意,我的回答)
简而言之,应该避免使用静态,因为这样会使编写简洁的单元测试变得困难。例如,使用静态最终对象映射器,不能将JSON序列化换成伪代码或no-op。
此外,静态final阻止您在运行时重新配置Objectmapper。现在您可能没有想到这样做的原因,但是如果您将自己锁定到一个静态的最终模式中,那么只要拆除类加载器,就不会让您重新初始化它。
在对象映射器的情况下,这是很好的,但一般来说这是不好的实践,并且没有优势比使用单例模式或控制反转来管理长寿的对象。
- 我建议尽管静态有状态单例通常是一个危险信号,但在本例中,共享单个(或少量)实例是有意义的,有足够的理由。人们可能希望使用依赖注入来实现这一点;但同时,值得问一下是否有实际的或潜在的问题需要解决。这尤其适用于测试:仅仅因为某些情况下可能有问题,并不意味着它是供您使用的。所以:意识到问题,很好。假设"一刀切",就不是那么好了。
- 显然,理解与任何设计决策相关的问题是很重要的,并且如果您可以在不给用例带来问题的情况下做一些事情,那么根据定义,您不会导致任何问题。然而,我认为静态实例的使用没有任何好处,并且随着代码的发展或被交付给其他可能不理解您的设计决策的开发人员,它为将来的重大问题打开了大门。如果您的框架支持备选方案,那么没有理由不避免静态实例,当然它们也没有任何优势。
- 我认为这个讨论涉及到非常一般和不太有用的切线。我认为最好怀疑静态的单粒子。我只是对这个特定案例的用法非常熟悉,我认为人们无法从一套通用指南中得出具体的结论。所以我就不谈了。
- 后期评论,但反对者映射器是否特别反对这个概念?它公开了readerFor和writerFor,根据需要创建ObjectReader和ObjectWriter实例。所以我想说,将初始配置的映射器放在静态的某个地方,然后根据需要使用每个案例配置来获取读写器?
如果您不想将其定义为静态最终变量,但希望节省一些开销并保证线程安全,那么我从这个pr中学到了一个技巧。
1 2 3 4 5 6 7 8 9 10 11 12
| 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将附加到线程上,该线程可能是池的一部分。
- @肯斯顿崔不应该是个问题,阿法尤。线程来来往往,线程局部变量来来往往。取决于同时线程的数量,您可能负担不起内存,但我看不到"泄漏"。
- @但是,如果线程是从线程池(例如,像Tomcat这样的容器)创建/返回的,它将保持不变。在某些情况下,这可能是需要的,但我们需要知道一些事情。
com.fasterxml.jackson.databind.type.typefactory.u hashmapsuperinterfacechain(hierarchictype)
1 2 3 4 5 6 7 8
| 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已同步。在高负载情况下,我看到了相同的竞争。
可能是避免使用静态对象映射器的另一个原因
- 一定要查看最新版本(可能还要指出您在这里使用的版本)。已经根据报告的问题对锁进行了改进,并为Jackson 2.7完全重写了类型解析(F.ex)。虽然在这种情况下,TypeReference的使用成本有点高:如果可能,将它解析为JavaType将避免相当多的处理(TypeReferences不能——不幸的是——缓存的原因是我不会在这里深入探讨),因为它们是"完全解析的"(超级类型、通用类型等)。