springboot项目中, 序列化对象需要注意什么?
Posted 洪宏鸿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot项目中, 序列化对象需要注意什么?相关的知识,希望对你有一定的参考价值。
在Spring Boot项目中,序列化对象时需要注意以下几点:
-
选择合适的序列化库:根据项目需求,选择适合的序列化库,如Java自带的序列化,或者使用其他库如Jackson、Gson、Fastjson等。
-
避免循环引用:在序列化对象时,确保对象图中没有循环引用。循环引用会导致无限递归,可能引发栈溢出异常。可以使用
@JsonIgnore
注解或其他库提供的类似注解来忽略循环引用的字段。 -
不要序列化敏感信息:在序列化对象时,确保不要序列化包含敏感信息的字段,例如用户密码、令牌等。可以使用
@JsonIgnore
或其他库提供的类似注解来忽略这些字段。 -
序列化版本控制:为序列化对象添加
serialVersionUID
,以便在对象结构发生变化时仍能正确反序列化。在Java自带的序列化中,可以通过在Java自带的序列化中,可以通过实现java.io.Serializable
接口并为类添加serialVersionUID
来实现版本控制。对于其他序列化库,根据其文档来处理版本控制。 -
处理空值:根据项目需求,处理对象中的空值字段。例如,可以使用
@JsonInclude(JsonInclude.Include.NON_NULL)
(针对Jackson库)等注解来忽略值为null的字段。 -
自定义序列化:如果需要对某个字段进行特殊处理,例如格式化日期、加密等,可以使用自定义序列化器。例如,在Jackson库中,可以通过实现
JsonSerializer
接口来创建自定义序列化器,并使用@JsonSerialize
注解指定序列化器。 -
支持多态:如果对象涉及到多态,需要确保序列化库能正确处理子类和父类的关系。对于Jackson库,可以使用
@JsonTypeInfo
和@JsonSubTypes
等注解来处理多态。 -
性能优化:在大型项目中,在大型项目中,序列化性能可能成为瓶颈。可以采用以下方法优化序列化性能:
a. 选择高性能的序列化库:不同的序列化库性能有所不同。在选择序列化库时,可以对比各库的性能,并根据项目需求选择合适的库。
b. 使用缓存:在反复序列化相同对象时,可以使用缓存来降低性能开销。例如,将已序列化的对象存储在内存缓存中,以避免重复序列化。
c. 使用流式序列化:在处理大量数据时,可以考虑使用流式序列化。流式序列化可以将数据一边读取,一边序列化,从而降低内存消耗和提高性能。例如,在Jackson库中,可以使用JsonGenerator
实现流式序列化。
d. 并行序列化:如果项目支持并发处理,可以使用多线程对对象进行并行序列化,从而提高整体性能。在实现并行序列化时,确保序列化库和使用的资源是线程安全的。可以使用线程池、CompletableFuture
等技术来管理并行任务。
e. 使用简化的数据结构:避免序列化过于复杂的对象图,以减少序列化时间。可以考虑使用DTO(数据传输对象)或VO(视图对象)等模式,将领域模型转换为简化的数据结构,以便序列化。
f. 压缩数据:如果序列化后的数据量较大,可以考虑对序列化后的数据进行压缩,以减少传输时间和带宽消耗。例如,可以使用GZIP或其他压缩算法对序列化后的数据进行压缩。
g. 针对特定场景优化:根据项目需求,可以针对特定场景进行优化。例如,如果项目中频繁地序列化相同类型的对象,可以为这些对象创建专用的序列化器,以提高性能。这些专用序列化器可以针对对象的特性进行优化,例如使用预编译的模式来减少序列化时间。
h. 序列化格式选择:根据项目需求和目标平台,选择合适的序列化格式。例如,JSON和XML适合文本传输和跨平台兼容性,而二进制格式如Protocol Buffers、MessagePack等可能在性能和空间占用方面有更好的表现。
i. 适当的配置:根据具体序列化库的特性,调整配置以实现最佳性能。例如,在Jackson库中,可以使用@JsonInclude
注解减少不必要的字段输出,或者启用特性如WRITE_DATES_AS_TIMESTAMPS
以减少日期序列化的开销。
总之,当在Spring Boot项目中序列化对象时,需要注意一系列问题,包括循环引用、敏感信息处理、性能优化等。通过合理的设计和优化,可以确保序列化过程既安全又高效。
SpringBoot+MyBatis Plus对Map中Date格式转换的处理
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式
问题
现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需要检索但是又需要结构化的存储, 会在数据库中产生很多 JSON 类型的字段, 与 Jackson 做对象的序列化和反序列化配合非常方便.
如果 JSON 都是类定义的, 这个序列化和反序列化就非常透明 -- 不需要任何干预, 写进去是什么, 读出来就是什么. 但是如果 JSON 在 Java 代码中是定义为一个 Map, 例如 Map<String, Object> 那么就有问题了, 对于 Date 类型的数据, 在存入之前是 Date, 取出来之后就变成 Long 了.
SomePO po = new SomePO();
//...
Map<String, Object> map = new HashMap<>();
map.put("k1", new Date());
po.setProperties(map);
//...
mapper.insert(po);
//...
SomePO dummy = mapper.select(po.id);
// 这里的k1已经变成了 Long 类型
Object k1 = dummy.getProperties().get("k1");
原因
不管是使用原生的 MyBatis 还是包装后的 MyBatis Plus, 在对 JSON 类型字段进行序列化和反序列化时, 都需要借助类型判断, 调用对应的处理逻辑, 大部分情况, 使用的是默认的 Jackson 的 ObjectMapper, 而 ObjectMapper 对 Date 类型默认的序列化方式就是取时间戳, 对于早于1970年之前的日期, 生成的是一个负的长整数, 对于1970年之后的日期, 生成的是一个正的长整数.
查看 ObjectMapper 的源码, 可以看到其对Date格式的序列化和反序列化方式设置于_serializationConfig 和 _deserializationConfig 这两个成员变量中, 可以通过 setDateFormat() 进行修改
public class ObjectMapper extends ObjectCodec implements Versioned, Serializable
//...
protected SerializationConfig _serializationConfig;
protected DeserializationConfig _deserializationConfig;
//...
public ObjectMapper setDateFormat(DateFormat dateFormat)
this._deserializationConfig = (DeserializationConfig)this._deserializationConfig.with(dateFormat);
this._serializationConfig = this._serializationConfig.with(dateFormat);
return this;
public DateFormat getDateFormat()
return this._serializationConfig.getDateFormat();
默认的序列化反序列化选项, 使用了一个常量 WRITE_DATES_AS_TIMESTAMPS, 在类 SerializationConfig 中进行判断, 未指定时使用的是时间戳
public SerializationConfig with(DateFormat df)
SerializationConfig cfg = (SerializationConfig)super.with(df);
return df == null ? cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) : cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
实际的转换工作在 SerializerProvider 类中, 转换方法为
public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) throws IOException
if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS))
gen.writeNumber(timestamp);
else
gen.writeString(this._dateFormat().format(new Date(timestamp)));
public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException
if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS))
gen.writeNumber(date.getTime());
else
gen.writeString(this._dateFormat().format(date));
解决
局部方案
1\\. 字段注解
这种方式可以用在固定的类成员变量上, 不改变整体行为
public class Event
public String name;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
public Date eventDate;
另外还可以自定义序列化反序列化方法, 实现 StdSerializer
public class CustomDateSerializer extends StdSerializer<Date>
//...
就可以在 @JsonSerialize 注解中使用
public class Event
public String name;
@JsonSerialize(using = CustomDateSerializer.class)
public Date eventDate;
2\\. 修改 ObjectMapper
通过 ObjectMapper.setDateFormat() 设置日期格式, 改变默认的日期序列化反序列化行为. 这种方式只对调用此ObjectMapper的场景有效
private static ObjectMapper createObjectMapper()
ObjectMapper objectMapper = new ObjectMapper();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
objectMapper.setDateFormat(df);
return objectMapper;
因为 ObjectMapper 一般是当作线程安全使用的, 而 SimpleDateFormat 并非线程安全, 在这里使用是否会有问题? 关于这个疑虑, 可以查看 这个链接
@StaxMan: I am a bit concerned if ObjectMapper is still thread-safe after ObjectMapper#setDateFormat() is called. It is known that SimpleDateFormat is not thread safe, thus ObjectMapper wont be unless it clones e.g. SerializationConfig before each writeValue() (I doubt). Could you debunk my fear? – dma_k Aug 2, 2013 at 12:09
DateFormat is indeed cloned under the hood. Good suspicion there, but you are covered.
以上是关于springboot项目中, 序列化对象需要注意什么?的主要内容,如果未能解决你的问题,请参考以下文章
Windows平台整合SpringBoot+KAFKA__第3部分_代码部分(结束)
SpringBoot入门篇--对于JSON数据的返回以及处理二