Java Jackson ObjectMapper 教学与注意事项

Jackson ObjectMapper 是 Java 中应用非常广泛的序列化、反序列化的工具,它可以帮助我们简单、快速将 Java 物件与 json 之间作转换,就连 Spring Framework 将它作为预设转换器。不过,一旦使用的人多,错误的写法也就层出不穷,如果没有按照正确做法,将很容易导致问题,本文将描述如何避免与改善。

问题描述

你能看出这段程式码有什么问题吗 ?

public String toJson(Something something) throws JsonProcessingException {    ObjectMapper objectMapper = new ObjectMapper();    return objectMapper.writeValueAsString(something);}

问题在于 new ObjectMapper()

这段程式码是很经典的错误,而且这种错误随处可见,很多人却没注意到。事实上,执行 new ObjectMapper() 是非常昂贵的,在系统遭遇高併发(High Concurrency)情况下,这种写法很容易出现效能瓶颈。根据这篇文章的实验,若每次序列化/反序列化都使用 new ObjectMapper,比起共用一个 ObjectMapper,执行时间至少相差五倍,因此要尽快修正。

解法

解法很简单,根据官方文件指出,ObjectMapper 是 thread-safe,因此只要共用同一个 instance,而不要每次都 new 即可,否则代价很高。我常用的作法有:

解法1. 宣告成员变数

若你的 ObjectMapper 不需要任何 configure,其实 Spring 已经帮我们建好一个预设的,直接注入即可,当然这里还是建议使用 Constructor Based Dependency Injection,可以看我写的这篇文章

@Servicepublic class MyService {    private final ObjectMapper objectMapper;        @Autowired    public MyService(ObjectMapper objectMapper) {        this.objectMapper = objectMapper;    }    public String toJson(Something something) throws JsonProcessingException {        return objectMapper.writeValueAsString(something);    }}

ObjectMapper 强大的地方在于,它有很多参数可视需求设定。这种写法可以在 new 的同时一起做 configure:

@Servicepublic class MyService {    private static final ObjectMapper objectMapper =         new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);    public String toJson(Something something) throws JsonProcessingException {        return objectMapper.writeValueAsString(something);    }}

解法2. @Configuration

如果你需要全域设定,或是有多个不同设定的 ObjectMapper,建议使用此方法,并且注入时要用 @Qualifier,否则将会注入预设的 ObjectMapper Bean。

@Configurationpublic class JacksonConfiguration {    @Bean("customObjectMapper")    public ObjectMapper customObjectMapper() {        return new ObjectMapper()            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)            .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);    }}

解法3. 包装成 Util (推荐)

这是我最常用的作法,我在专案中通常都只有一个 ObjectMapper,因此全部的 class 都共用它就够了,这时可包装成 Util 方便全域使用。例外处理的部分,就依各专案需求而定,没有最佳的设计,只有最适合自己的设计。

public class JsonUtil {    private final static ObjectMapper objectMapper =                 new ObjectMapper()                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);    private JsonUtil() {    }    public static String toJson(Object obj) {        try {            return objectMapper.writeValueAsString(obj);        } catch (JsonProcessingException e) {            log.error("Occur error during parsing data to json: {}", obj, e);            return "";        }    }    public static <T> T toObject(String json, Class<T> objectClass) {        try {            return objectMapper.readValue(json, objectClass);        } catch (IOException e) {            log.error("Occur error during mapping json to an object: {}", json, e);            return null;        }    }}

使用起来非常方便简单。

String json = JsonUtil.toJson(something);Something something = JsonUtil.toObject(json, Something.class);

结论

尽量不要在每次序列化/反序列化使用时都 new ObjectMapper();,这样的代价是昂贵的,比起共用同一个实例,两者的效能可以相差很多倍。ObjectMapper 是 thread-safe 的物件,所以本文介绍的解法概念上是一样的,就是请放心的共用同一个 ObjectMapper 实例。这是很重要的,虽然简单但别小看它,也许一个小动作可以拯救你的一天。

References

本文转录自我的部落格 https://kaisheng714.github.io/articles/object-mapper

更多你可能会感兴趣的文章

Java SimpleDateFormat 的错误用法多此一举! 不要这样用 Java 8 Optional

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章