引言
在Java生态中,处理JSON几乎是每个开发者的必修课。而提到JSON处理库,Jackson无疑是最受欢迎的选择之一。作为Jackson的核心类,ObjectMapper承担着Java对象与JSON数据之间相互转换的重任。然而,在实际开发中,我见过太多这样的代码:
java
public void doSomething() { ObjectMapper mapper = new ObjectMapper(); // 使用mapper进行转换... }甚至在循环中、每次请求处理方法里都新建一个ObjectMapper。每次看到这样的代码,我都想大喊:“别再像个二货一样一直new了!”
频繁创建ObjectMapper实例不仅浪费资源,还可能引发性能问题。本文将从为什么不能频繁new、正确使用方式、配置详解、性能优化、常见陷阱到源码分析,为你带来一份关于ObjectMapper的终极指南。全文约2万字,建议收藏后慢慢阅读。
一、ObjectMapper简介
1.1 Jackson 概述
Jackson是Java平台上用于处理JSON格式数据的开源库,目前已成为事实上的标准(Spring Boot默认集成了Jackson)。它提供了三种核心处理方式:
数据绑定(Data Binding):通过
ObjectMapper在POJO和JSON之间转换。树模型(Tree Model):使用
JsonNode节点树表示JSON。流式API(Streaming API):基于
JsonParser和JsonGenerator的高性能读写。
其中,ObjectMapper属于数据绑定层,也是我们最常用的入口。
1.2 ObjectMapper的核心功能
ObjectMapper提供了丰富的API,主要包括:
readValue(...):将JSON字符串、字节数组、文件、输入流等反序列化为Java对象。writeValue(...):将Java对象序列化为JSON字符串、字节数组、文件等。convertValue(...):在两种Java类型之间转换(如Map转POJO)。treeToValue(...)/valueToTree(...):在树模型和数据绑定之间转换。
此外,它还支持泛型、多态、注解、自定义序列化器等高级特性。
二、为什么不要频繁new ObjectMapper?
很多开发者认为创建个对象而已,能有多大开销?那么让我们深入分析一下,频繁new ObjectMapper到底有什么问题。
2.1 对象创建开销
虽然new ObjectMapper()本身并不算特别重,但也不是轻量级操作。看源码会发现,构造方法中会初始化很多内部组件:
java
public ObjectMapper() { this(null, null, null); } // 最终会调用 public ObjectMapper(JsonFactory jf, DefaultSerializerProvider sp, DefaultDeserializationContext dc) { // 初始化一系列特性、注册模块、创建缓存等... }具体包括:
创建
JsonFactory(如果没有传入的话)。初始化
SerializerProvider、DeserializationContext。注册默认的
Serializers、Deserializers。构建用于缓存序列化器/反序列化器的
ConcurrentHashMap。设置各种Feature默认值。
这些初始化操作虽然不是非常耗时(可能几毫秒),但如果在一个高频接口中每次请求都新建,累积下来就不可忽视。在并发场景下,频繁创建对象还会加重GC负担。
2.2 内部缓存的浪费
ObjectMapper内部维护了多个缓存,最核心的是:
序列化器缓存:
_rootDeserializers(反序列化器缓存)、_serializerCache(序列化器缓存)等。类型缓存:
_typeFactory中缓存的JavaType。注解缓存:
_annotationIntrospector中缓存的注解信息。
如果每次new一个新的ObjectMapper,这些缓存就需要重新构建。当系统中有大量POJO类型时,缓存重建的开销非常明显。更严重的是,这些缓存无法被多个ObjectMapper共享,导致内存浪费。
2.3 线程安全问题
ObjectMapper本身是线程安全的,只要在配置完成后不再修改配置,就可以安全地在多线程间共享。源码中明确说明:
ObjectMapper instances are fully thread-safe as long as NO configuration changes occur after initial construction.
因此,我们完全可以只创建一个实例,在应用全局复用。
2.4 性能对比测试
为了直观感受频繁new ObjectMapper的影响,我们做一个小型基准测试。使用JMH(Java Microbenchmark Harness)分别测试:
每次都new ObjectMapper再执行一次转换。
复用同一个ObjectMapper执行一次转换。
测试环境:JDK 8,i7-8700 CPU,16GB内存。
测试代码片段:
java
@Benchmark @BenchmarkMode(Mode.Throughput) public void testNewMapper(Blackhole blackhole) throws Exception { ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue("{\"name\":\"test\"}", User.class); blackhole.consume(user); } @Benchmark @BenchmarkMode(Mode.Throughput) public void testReuseMapper(Blackhole blackhole) throws Exception { User user = SHARED_MAPPER.readValue("{\"name\":\"test\"}", User.class); blackhole.consume(user); }结果(吞吐量,越高越好):
| 模式 | 吞吐量(ops/ms) |
|---|---|
| 每次都new | ≈ 120 |
| 复用同一个 | ≈ 950 |
复用实例的性能几乎是每次都new的8倍!这还只是简单的转换,如果POJO结构复杂,差异会更明显。
因此,结论非常明确:请始终重用ObjectMapper实例。
三、正确使用ObjectMapper的最佳实践
3.1 单例模式
最简单的做法是将ObjectMapper定义为static final字段,在类加载时初始化一次。
java
public class JsonUtils { private static final ObjectMapper MAPPER = new ObjectMapper(); private JsonUtils() {} public static ObjectMapper getMapper() { return MAPPER; } public static String toJson(Object obj) throws JsonProcessingException { return MAPPER.writeValueAsString(obj); } public static <T> T fromJson(String json, Class<T> clazz) throws IOException { return MAPPER.readValue(json, clazz); } }这种方式简单直接,适用于小型项目。但缺点是无法灵活配置(比如不同场景可能需要不同的日期格式),且难以在测试中替换。
3.2 在Spring中配置为Bean
如果你使用Spring(包括Spring Boot),强烈建议将ObjectMapper定义为Bean,并由容器管理生命周期。
Spring Boot自动配置:Spring Boot已经自动配置了一个ObjectMapperBean,我们可以直接注入使用。
java
@Service public class UserService { @Autowired private ObjectMapper objectMapper; // 使用objectMapper }如果需要自定义配置,可以在配置类中提供Jackson2ObjectMapperBuilder或ObjectMapperBean:
java
@Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { return Jackson2ObjectMapperBuilder.json() .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .modules(new JavaTimeModule()) .build(); } }这样既能复用实例,又保持了配置的灵活性。
3.3 模块注册
Jackson通过模块(Module)机制扩展功能。常见的模块有:
JavaTimeModule:支持Java 8日期时间API(LocalDate、LocalDateTime等)。JodaModule:支持Joda-Time。ParameterNamesModule:支持基于构造方法参数名的反序列化(需配合-parameters编译选项)。KotlinModule:支持Kotlin数据类。
注册方式:
java
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new ParameterNamesModule());
3.4 配置的线程安全性
一旦ObjectMapper被配置完成(比如在Spring容器初始化后),就不应该再修改其配置。如果在运行时修改(例如调用configure方法),则可能导致线程安全问题。因此,最佳实践是在构建完成后就“冻结”配置。
四、ObjectMapper的配置详解
ObjectMapper提供了丰富的配置选项,通过SerializationFeature、DeserializationFeature、MapperFeature等枚举来控制序列化/反序列化行为。下面逐一介绍常用配置。
4.1 SerializationFeature(序列化特性)
| 特性 | 说明 |
|---|---|
WRITE_DATES_AS_TIMESTAMPS | 将日期输出为时间戳(默认true),设为false则输出为ISO-8601格式字符串。 |
INDENT_OUTPUT | 格式化输出,使JSON具有缩进,便于阅读。 |
WRITE_NULL_MAP_VALUES | 是否序列化Map中值为null的条目(默认true)。 |
WRITE_EMPTY_JSON_ARRAYS | 是否序列化空的集合为[](默认true)。 |
FAIL_ON_EMPTY_BEANS | 如果对象没有任何可序列化的属性,是否抛出异常(默认true)。 |
ORDER_MAP_ENTRIES_BY_KEYS | 序列化Map时按键排序(默认false)。 |
WRITE_SELF_REFERENCES_AS_NULL | 将循环引用(自引用)写为null,而不是抛出异常。 |
示例:
java
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
4.2 DeserializationFeature(反序列化特性)
| 特性 | 说明 |
|---|---|
FAIL_ON_UNKNOWN_PROPERTIES | JSON中存在未知属性时是否失败(默认true)。建议设为false。 |
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT | 将空字符串""反序列化为null对象(默认false)。 |
ACCEPT_SINGLE_VALUE_AS_ARRAY | 允许单个值反序列化为数组(如JSON是1,目标类型是List<Integer>)。 |
UNWRAP_ROOT_VALUE | 是否展开根值(配合@JsonRootName使用)。 |
READ_UNKNOWN_ENUM_VALUES_AS_NULL | 遇到未知枚举值时反序列化为null,而不是抛异常。 |
READ_DATE_TIMESTAMPS_AS_NANOSECONDS | 读取时间戳时是否作为纳秒处理。 |
常用配置:
java
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
4.3 MapperFeature(通用特性)
| 特性 | 说明 |
|---|---|
USE_ANNOTATIONS | 是否使用Jackson注解(默认true)。 |
AUTO_DETECT_CREATORS | 是否自动检测构造方法、静态工厂方法(默认true)。 |
AUTO_DETECT_FIELDS | 是否自动检测字段(默认true)。 |
AUTO_DETECT_GETTERS | 是否自动检测getter方法(默认true)。 |
AUTO_DETECT_SETTERS | 是否自动检测setter方法(默认true)。 |
REQUIRE_SETTERS_FOR_GETTERS | 是否要求getter必须有对应的setter才能序列化(默认false)。 |
ALLOW_FINAL_FIELDS_AS_MUTATORS | 是否允许将final字段作为mutator(通过反射修改),默认false。 |
INFER_PROPERTY_MUTATORS | 是否推断属性修改器(例如根据字段推断setter),默认true。 |
SORT_PROPERTIES_ALPHABETICALLY | 序列化时按属性名排序(默认false)。 |
4.4 JsonParser/JsonGenerator特性
除了上述特性,还可以通过JsonParser.Feature和JsonGenerator.Feature控制底层解析/生成行为。
JsonParser.Feature(解析时):
ALLOW_COMMENTS:允许JSON中包含注释。ALLOW_UNQUOTED_FIELD_NAMES:允许字段名不带引号(非标准)。ALLOW_SINGLE_QUOTES:允许字符串使用单引号。ALLOW_NUMERIC_LEADING_ZEROS:允许数字前导零(如0123)。ALLOW_NON_NUMERIC_NUMBERS:允许NaN、Infinity等非数值数值。
JsonGenerator.Feature(生成时):
QUOTE_FIELD_NAMES:字段名是否加引号(默认true)。WRITE_NUMBERS_AS_STRINGS:将数字写为字符串。WRITE_BIGDECIMAL_AS_PLAIN:将BigDecimal写为普通数字(不使用科学计数法)。
启用方式:
java
objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS); objectMapper.disable(JsonGenerator.Feature.QUOTE_FIELD_NAMES);
4.5 日期格式配置
日期处理是常见需求。默认情况下,Date类型会被序列化为时间戳(毫秒)。可以通过以下方式统一配置日期格式:
java
// 方式一:禁用时间戳,使用ISO-8601字符串 objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 方式二:自定义日期格式(例如yyyy-MM-dd HH:mm:ss) objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));对于Java 8时间API,需要注册JavaTimeModule,并可以进一步配置:
java
JavaTimeModule module = new JavaTimeModule(); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); objectMapper.registerModule(module);4.6 属性命名策略
Jackson支持将Java字段名转换为JSON字段名的命名策略。内置的策略有:
LOWER_CAMEL_CASE:小驼峰(默认),如userName->userName。UPPER_CAMEL_CASE:大驼峰,如userName->UserName。SNAKE_CASE:下划线分隔,如userName->user_name。UPPER_SNAKE_CASE:大写+下划线,如userName->USER_NAME。LOWER_CASE:全小写,如userName->username。KEBAB_CASE:中划线分隔,如userName->user-name。
设置方式:
java
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
也可以自定义命名策略,继承PropertyNamingStrategy。
4.7 自定义序列化器和反序列化器
当默认行为无法满足需求时,可以编写自定义序列化器/反序列化器。
自定义序列化器(继承JsonSerializer<T>):
java
public class CustomDateSerializer extends JsonSerializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); @Override public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException { String formatted = dateFormat.format(value); gen.writeString(formatted); } }注册到ObjectMapper:
java
SimpleModule module = new SimpleModule(); module.addSerializer(Date.class, new CustomDateSerializer()); objectMapper.registerModule(module);
自定义反序列化器(继承JsonDeserializer<T>):
java
public class CustomDateDeserializer extends JsonDeserializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String dateStr = p.getText(); try { return dateFormat.parse(dateStr); } catch (ParseException e) { throw new IOException("Failed to parse date: " + dateStr, e); } } }注册方式相同。
4.8 其他常用配置
忽略未知属性:全局配置
FAIL_ON_UNKNOWN_PROPERTIES为false。空值处理:可以通过
setSerializationInclusion(JsonInclude.Include.NON_NULL)全局忽略值为null的属性。也可以使用Include.NON_EMPTY忽略空字符串、空集合等。默认类型信息:
enableDefaultTyping()用于处理多态类型,但需注意安全风险(可能导致反序列化漏洞),建议使用@JsonTypeInfo注解更安全。
五、性能优化与高级用法
5.1 重用ObjectReader和ObjectWriter
ObjectMapper是线程安全的,但每次调用readValue或writeValue时,都会创建一个新的ObjectReader/ObjectWriter(虽然轻量但仍有开销)。如果某个特定类型的转换非常频繁,可以考虑预先创建ObjectReader并重用。
ObjectReader和ObjectWriter是ObjectMapper的变体,它们是不可变的、线程安全的,可以缓存起来重复使用。
java
ObjectMapper mapper = new ObjectMapper(); ObjectReader userReader = mapper.readerFor(User.class); ObjectWriter userWriter = mapper.writerFor(User.class); // 多次使用 User user = userReader.readValue(jsonString); String json = userWriter.writeValueAsString(user);
这对于特定类型的反复转换有性能提升,尤其是在并发场景下。
5.2 使用JsonNode树模型
当不需要将JSON绑定到具体POJO,或者JSON结构动态变化时,可以使用树模型(JsonNode)。
java
JsonNode root = mapper.readTree(jsonString); String name = root.get("name").asText(); int age = root.get("age").asInt(); JsonNode addressNode = root.get("address");树模型适合部分读取、遍历或修改JSON,但性能比数据绑定稍低。如果只是提取少量字段,使用流式API更高效。
5.3 流式API(JsonParser/JsonGenerator)
对于超大JSON或极致性能要求,可以使用流式API直接解析或生成JSON,避免构建中间树或对象。
解析示例:
java
JsonFactory factory = new JsonFactory(); JsonParser parser = factory.createParser(jsonString); while (parser.nextToken() != JsonToken.END_OBJECT) { String fieldName = parser.getCurrentName(); if ("name".equals(fieldName)) { parser.nextToken(); String name = parser.getText(); } // ... } parser.close();生成示例:
java
JsonGenerator generator = factory.createGenerator(System.out); generator.writeStartObject(); generator.writeStringField("name", "test"); generator.writeNumberField("age", 20); generator.writeEndObject(); generator.close();流式API是Jackson最高性能的处理方式,但编码较为繁琐。
5.4 处理泛型
当目标类型是泛型(如List<User>、Map<String, User>)时,不能直接传入List.class,因为类型擦除会导致Jackson无法获知元素类型。需要使用TypeReference:
java
List<User> users = mapper.readValue(jsonString, new TypeReference<List<User>>() {});或者通过TypeFactory构造JavaType:
java
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, User.class); List<User> users = mapper.readValue(jsonString, type);
5.5 多态类型处理
当接口或抽象类有多个实现时,需要在JSON中包含类型信息,以便正确反序列化。Jackson提供了多种方式,最常用的是注解@JsonTypeInfo。
示例:
java
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog"), @JsonSubTypes.Type(value = Cat.class, name = "cat") }) public interface Animal { String getName(); } public class Dog implements Animal { ... } public class Cat implements Animal { ... }序列化时,会多出一个type字段指明具体类型。反序列化时,根据type值创建对应的子类实例。
5.6 注解的使用
Jackson提供了丰富的注解,掌握它们可以避免大量配置代码。
| 注解 | 作用 |
|---|---|
@JsonProperty | 指定JSON字段名,或标记属性为可序列化/反序列化。 |
@JsonIgnore | 忽略某个属性。 |
@JsonIgnoreProperties | 在类级别忽略一组属性。 |
@JsonInclude | 指定属性包含条件(如Include.NON_NULL)。 |
@JsonFormat | 指定日期格式、数字格式等。 |
@JsonCreator | 标记构造方法或静态工厂方法,用于反序列化。 |
@JsonPropertyOrder | 指定属性序列化顺序。 |
@JsonAlias | 定义反序列化时接受的别名。 |
@JsonSerialize/@JsonDeserialize | 指定自定义序列化器/反序列化器。 |
示例:
java
public class User { @JsonProperty("user_name") private String userName; @JsonIgnore private String password; @JsonFormat(pattern = "yyyy-MM-dd") private Date birthday; @JsonCreator public User(@JsonProperty("user_name") String userName) { this.userName = userName; } // ... }5.7 混合注解(Mix-in)
如果无法修改POJO源码(如第三方类),可以使用混合注解。创建一个混入接口,将注解加在接口上,然后告诉ObjectMapper将接口的注解应用到目标类。
java
// 假设第三方类 ThirdPartyClass,没有注解 public interface ThirdPartyClassMixIn { @JsonProperty("custom_name") String getName(); } objectMapper.addMixIn(ThirdPartyClass.class, ThirdPartyClassMixIn.class);这样,在序列化/反序列化ThirdPartyClass时,就会应用混入接口中的注解。
六、常见陷阱与解决方案
6.1 循环引用
如果对象之间存在双向引用(如A引B,B引A),序列化时会抛出JsonMappingException(Infinite recursion)。解决方法:
使用
@JsonIgnore:在一端忽略引用。使用
@JsonManagedReference和@JsonBackReference:适用于父子关系,父用@JsonManagedReference,子用@JsonBackReference。使用
@JsonIdentityInfo:通过对象ID引用,避免重复展开。
示例(@JsonIdentityInfo):
java
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class User { private int id; private List<Order> orders; } public class Order { private int id; private User user; // 引用回User }序列化后,每个对象第一次出现时完整输出,后续只输出ID引用。
6.2 未知属性导致异常
默认情况下,如果JSON中包含POJO中没有的属性,反序列化会抛出UnrecognizedPropertyException。解决方案:
全局禁用:
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)在类上使用
@JsonIgnoreProperties(ignoreUnknown = true)
6.3 空值处理
默认情况下,值为null的属性也会被序列化。如果想忽略null值,可以使用:
java
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
其他选项:
Include.NON_EMPTY:忽略null、空字符串、空集合、空数组等。Include.NON_DEFAULT:忽略与默认值相同的属性。
6.4 日期时间处理
日期处理常见问题:
默认输出时间戳,不利于阅读。
时区问题:Jackson默认使用UTC,如果应用服务器时区不同,可能导致时间偏差。
推荐配置:
java
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setTimeZone(TimeZone.getDefault()); // 使用系统默认时区 mapper.registerModule(new JavaTimeModule());
6.5 集合类型处理
反序列化集合时,如果泛型擦除,需要指定类型(见5.4)。另外,注意处理空集合:默认会序列化为[],可通过SerializationFeature.WRITE_EMPTY_JSON_ARRAYS控制。
6.6 多线程环境下的问题
虽然ObjectMapper本身线程安全,但如果在运行时修改配置(如调用configure),则可能破坏线程安全。确保配置阶段完成后不再修改。如果需要不同配置,可以创建多个ObjectMapper实例(但尽量少)或使用ObjectReader/ObjectWriter(它们可以有不同的配置且线程安全)。
6.7 反序列化漏洞
Jackson历史上出现过反序列化漏洞(如CVE-2019-12384),主要与多态类型处理有关。如果启用了enableDefaultTyping(),攻击者可能构造恶意JSON触发远程代码执行。建议:
避免使用
enableDefaultTyping(),改用@JsonTypeInfo指定允许的子类型。升级Jackson到最新版本。
如果必须使用多态,考虑添加白名单(
activateDefaultTypingAsProperty等方式)。
七、源码分析(简要)
7.1 内部缓存结构
ObjectMapper内部有几个重要的缓存:
_rootDeserializers:ConcurrentHashMap<JavaType, JsonDeserializer<Object>>,缓存根级别的反序列化器。_serializerCache:ConcurrentHashMap<Object, JsonSerializer<Object>>,缓存序列化器(键为Class或JavaType)。_typeFactory:用于构造JavaType,内部也缓存了已创建的JavaType。
这些缓存在首次使用某个类型时填充,后续转换直接命中缓存,避免重复创建序列化器/反序列化器。因此,重用ObjectMapper可以充分利用缓存。
7.2 序列化器/反序列化器的查找过程
以反序列化为例,readValue最终调用_findRootDeserializer:
java
protected JsonDeserializer<Object> _findRootDeserializer(DeserializationContext ctxt, JavaType valueType) { JsonDeserializer<Object> deser = _rootDeserializers.get(valueType); if (deser != null) { return deser; } // 未命中,创建新的反序列化器 deser = ctxt.findRootValueDeserializer(valueType); if (deser == null) { throw new JsonMappingException(...); } _rootDeserializers.put(valueType, deser); return deser; }创建反序列化器涉及注解解析、类型推断等复杂过程,成本较高。缓存的存在大幅提升了性能。
7.3 为什么重用能提升性能
缓存复用:多次转换同一类型,直接从缓存获取序列化器/反序列化器。
避免重复解析注解:类上的注解只在第一次处理时解析。
减少对象创建:避免了每次重新创建
JsonFactory、SerializerProvider等。GC压力小:缓存中的对象常驻内存,减少了新生代对象的数量。
八、与其他JSON库对比
8.1 Gson
Google的Gson库以API简洁著称,但功能上不如Jackson丰富。性能方面,两者相近,但Jackson在复杂对象和大文件处理上通常更优。Gson默认需要公开字段或getter,而Jackson更灵活。Jackson的流式API和树模型也比Gson更强大。
8.2 Fastjson
阿里Fastjson曾以极快速度闻名,但近年来漏洞频发,且社区活跃度下降。Fastjson在性能上曾优于Jackson,但Jackson经过优化已不相上下。考虑到安全性和维护性,官方和社区更推荐Jackson。
8.3 JSON-B (Java EE标准)
JSON-B是Java EE的JSON绑定标准,其参考实现(如Yasson)底层可能使用Jackson或Gson。但JSON-B功能相对基础,复杂场景仍需Jackson。
8.4 小结
Jackson在功能、性能、扩展性和社区活跃度上综合表现最佳,是大多数项目的首选。而正确使用ObjectMapper,更能发挥其优势。
九、实战案例
9.1 Spring Boot中配置ObjectMapper
在Spring Boot中,我们可以通过application.yml或配置类自定义ObjectMapper。
application.yml配置:
yaml
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 default-property-inclusion: non_null serialization: indent_output: true write_dates_as_timestamps: false deserialization: fail_on_unknown_properties: false
配置类方式:
java
@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); builder.timeZone(TimeZone.getTimeZone("GMT+8")); builder.serializationInclusion(JsonInclude.Include.NON_NULL); builder.featuresToDisable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES ); builder.featuresToEnable(SerializationFeature.INDENT_OUTPUT); builder.modules(new JavaTimeModule()); }; } }9.2 在微服务中的使用
在微服务间通信时,常用JSON作为数据交换格式。使用全局单例ObjectMapper,配合Spring的RestTemplate或WebClient,可以高效处理序列化/反序列化。
RestTemplate配置:
java
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder, ObjectMapper objectMapper) { // 自定义消息转换器,使用已有的ObjectMapper MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); return builder.messageConverters(converter).build(); }9.3 日志打印JSON
在打印日志时,将对象转为JSON字符串可以方便查看。但要注意避免日志过多影响性能。可以使用ObjectMapper的writeValueAsString,但建议在日志级别为DEBUG时才执行序列化:
java
if (log.isDebugEnabled()) { log.debug("User info: {}", objectMapper.writeValueAsString(user)); }对于生产环境,可以考虑使用LogstashEncoder等工具直接输出JSON格式日志。
十、总结与建议
10.1 核心要点回顾
永远不要频繁new ObjectMapper,应使用单例或Spring Bean重用。
ObjectMapper是线程安全的,配置完成后即可放心共享。
充分利用缓存,避免重复构建序列化器/反序列化器。
合理配置特性,满足业务需求的同时提升性能。
对于特定类型的高频转换,考虑缓存ObjectReader/Writer。
注意安全漏洞,避免使用全局默认类型。