时间跟其他数据的序列化
# 时间跟其他数据的序列化
# 序列化
# 简介
- 简单版
序列化:将内存中的对象
或数据结构
转换为可存储或传输的格式(如JSON、XML、二进制流等)。
- 详细版
序列化是指将一个对象的状态(即对象的成员变量)转换为可以存储或传输的形式(如字节数组、文件或网络流)的过程。
在Java中,要使一个类的对象能够被序列化,该类需要实现 Serializable
接口。这个接口是一个标记接口,它本身不包含任何方法,只是表明该类的对象可以被序列化。
反序列化就是将上述过程反过来。
- 简单版
反序列化:将序列化后的数据恢复为原始的对象或数据结构。
- 详细版
反序列化则是序列化的逆过程,指的是从输入流中读取字节数据并重新构建对象的过程。通过反序列化,可以从先前序列化的对象状态中恢复对象。
# 为什么要序列化
- 持久化存储:内存中的对象是瞬态的,序列化后可将状态保存到文件或数据库,实现持久化。
瞬态对象:指仅存在于内存中的对象,程序终止后对象状态立即丢失
- 跨平台传输:网络通信或跨语言交互时,序列化为通用格式(如JSON)确保接收方能正确解析。
- 深拷贝与状态传递:通过序列化/反序列化实现对象的深度复制,或在不同上下文中传递完整状态(如分布式系统)。
如果不序列化,你会面临如下几个问题:
- 数据无法复用:程序重启后,内存中的对象状态丢失,无法恢复。
- 传输限制:复杂对象无法直接通过网络传输(如非基本类型的嵌套结构)。
- 跨语言障碍:不同编程语言的内存结构差异导致直接交换数据困难。
之所以要对数据进行序列化,还有一个重要的点是,为了支持远程过程调用。
- 支持远程调用:RPC、REST API等依赖序列化传递参数和结果。
# 常见序列化方式
# Java原生序列化
// 序列化到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
oos.writeObject(user); // 写入对象
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User restoredUser = (User) ois.readObject(); // 恢复对象
}
2
3
4
5
6
7
8
9
- 注意:类需实现
Serializable
,敏感字段可用transient
修饰排除序列化。
# JSON序列化
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user); // 对象→JSON
User deserializedUser = mapper.readValue(json, User.class); // JSON→对象
2
3
Json序列化常用的工具有,Jackson,FastJson,Gson等
# 序列化带来的问题
- 性能开销:序列化/反序列化过程消耗CPU和内存,大数据量时可能成为瓶颈。
- 安全隐患:反序列化不可信数据可能导致注入攻击(如Java反序列化漏洞)。
- 版本兼容性:类结构变更后,旧序列化数据可能无法正确还原(需处理
serialVersionUID
或迁移策略)。 - 数据冗余:某些格式(如XML)包含元数据,体积较大,影响传输效率。
# 扩展
在进行第三方接口对接时,通常将数据序列化成Json字符串,为什么不序列化成二进制呢?
参考答案:跨语言兼容,人类可读。
- JSON vs 二进制:JSON可读性强但体积大;Protobuf、Avro等二进制格式高效但需Schema。
# 时间的格式化
在 Java 中,LocalDateTime
类型数据(它是JDK8新增的一种数据格式,不包含时区)返回给前端时 必须进行格式化处理,否则可能导致以下问题:
- Java 的
LocalDateTime
是不可变的日期时间类,但 JSON 库(如 Jackson)默认无法直接序列化LocalDateTime
对象为字符串。如果不显式指定格式,返回的可能是null
或undefined
(如 [2] 中提到的“后端返回LocalDateTime
类型前端显示undefined
”)。 - JSON 库可能使用默认的 ISO 格式(如
2025-05-18T12:30:45
),但前端框架(如 JavaScript)可能无法直接解析这种格式,导致显示错误或需额外处理 - 产生歧义:未格式化的数据可能导致解析错误(如
2025-05-18T12:30:45
被误认为 UTC 时间)。
# 解决方案-参考
在 Spring Boot 中,通过配置 ObjectMapper
统一格式:
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.ZoneId;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 注册 Java 8 日期时间模块
mapper.registerModule(new JavaTimeModule());
// 设置时区
mapper.setTimeZone(ZoneId.of("GMT+8"));
// 禁用日期时间转为时间戳(默认启用)
mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
当然进行到这里,还不算结束。
还需要对SpringBoot的消息转换器进行更新才行。再进一步解释就是,Spring Boot默认会配置一些消息转换器,比如MappingJackson2HttpMessageConverter,用于处理JSON数据。如果用户需要自定义这些转换器的行为,比如日期格式,就需要通过extendMessageConverters方法来扩展。
在 Spring Boot 中,extendMessageConverters
是 WebMvcConfigurer
接口提供的一个方法,用于 扩展默认的消息转换器(HttpMessageConverter),而不是完全覆盖它们。如果你只是重写这个方法但没有做任何实际的修改(如添加或调整转换器),那么它并不会对实际的功能产生影响。不过,重写这个方法是必须的,因为这是 Spring Boot 提供的扩展点,只有通过重写才能实现自定义配置。
用代码展示就是:
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new JacksonObjectMapper());
// 将自定义的转换器添加到 converters 列表中
// 插入到最前面,优先使用
converters.add(0, converter);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
扩展
Spring Boot 默认会自动配置一组消息转换器(如
MappingJackson2HttpMessageConverter
),用于处理 JSON、XML 等数据格式。
- 如果你需要 自定义这些转换器的行为(例如修改日期格式、添加自定义序列化逻辑),就需要通过
extendMessageConverters
方法来扩展它们。- 如果你完全替换默认的转换器(例如用 FastJSON 替代 Jackson),则需要重写
configureMessageConverters
方法(而不是extendMessageConverters
)。
最后想说下,格式化的方式,还有其他的方式,上述配置类只是其中一种。
# 其他配置例子
再比如这种:
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//simpleModule
//解决:如果是序列化问题导致 id 值在前后端传输过程中发生变化(如精度丢失、科学计数法等),
// 通常是因为后端返回的 JSON 数据中 id 字段是 Long 类型,而前端 JavaScript 无法处理大整数造成的
simpleModule.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(Long.TYPE, ToStringSerializer.instance);
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
将
LocalDateTime
类型的数据转换为指定的自定义字符串格式以便返回给前端,这属于序列化的一部分。序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。在这个背景下,把
LocalDateTime
转换为字符串形式,是为了能够通过网络(如HTTP响应)正确地传递数据,并确保接收端(例如前端应用)能够准确理解这些数据。
# Long类型的序列化
如果遇到因 Long 类型的 ID 在前后端传输过程中发生精度丢失或被转换为科学计数法的问题,主要是因为 JavaScript 对大整数的支持有限。JavaScript 的 Number
类型是基于 IEEE-754 标准的双精度浮点数,在处理超过一定范围(安全整数范围为 -2^53 + 1 到 2^53 - 1)的大整数时会出现精度丢失问题。
针对这个问题,有几种解决方案:
# Long转String
可以通过 Jackson 的序列化配置来实现自动转换。创建一个自定义的序列化器或者使用注解的方式。
如果你想对整个项目的 Long 类型字段都应用这个规则,可以在配置类中添加如下代码:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意:不要忘记将他注入消息转换器中。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ObjectMapper objectMapper;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 创建自定义的 MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
// 替换默认的转换器
converters.add(0, converter);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Redis数据的序列化
在将数据存储进 Redis 之前,也需要对数据进行序列化。
主要原因是:因为 Redis 的键值存储要求键和值为 字节数据(bytes) 或 字符串(string),更详细一点就是Redis 的 SET
命令只接受字符串或字节数据,直接存储复杂对象(如字典、列表、自定义类实例)会报错。
# 默认的序列化
在 Spring Boot 项目中,即使引入了 spring-boot-starter-data-redis
依赖,仍然需要显式配置 Redis 的序列化方式。这是因为 Spring Data Redis 默认使用 JdkSerializationRedisSerializer
(Java 原生序列化),这种方式存在以下问题:
- 类路径依赖:反序列化时需要类的全限定名,若类结构变更或类路径丢失,会抛出异常。
- 生成的 Key 不友好:默认序列化后的 Key 是二进制格式,难以调试和查看。
- 兼容性差:不支持跨语言访问 Redis 数据。
因此,推荐使用 JSON 序列化(如 Jackson)替代默认的 Java 原生序列化。
# 重新序列化
下面是一个完整的配置和代码示例:
确保项目中包含 Jackson 依赖(spring-boot-starter-data-redis
本身不包含 Jackson):
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2
3
4
当然你也可以用其他JSON库进行序列化,或者通过其他方式进行序列化。以下方案仅供参考。
配置 RedisTemplate 使用 JSON 序列化
创建一个配置类,覆盖默认的 RedisTemplate
配置:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置 Key 的序列化器为 String
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 设置 Value 的序列化器为 Jackson JSON
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule()); // 支持 LocalDateTime 等时间类型
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
return template;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 一个问题
SpringBoot项目引入,spring-boot-starter-data-redis后,使用的redis操作工具是——RedisTemplate吗?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2
3
4
5
答案:是的
在 Spring Boot 项目中引入 spring-boot-starter-data-redis
依赖后,默认使用的 Redis 操作工具是 RedisTemplate
。它是 Spring Data Redis 提供的核心类,用于与 Redis 交互,支持对字符串、哈希、列表、集合、有序集合等数据结构的操作。
RedisTemplate
是一个模板类,封装了 Redis 的底层操作(如连接管理、序列化/反序列化、数据操作等),开发者无需直接处理 Redis 客户端(如 Lettuce 或 Jedis)的复杂逻辑。它提供了以下功能:
- 通用操作:如设置键值对(
opsForValue()
)、操作哈希(opsForHash()
)、操作列表(opsForList()
)等。 - 序列化配置:默认使用 Java 原生序列化(
JdkSerializationRedisSerializer
),但可以自定义为 JSON、字符串等格式。 - 连接池管理:通过
RedisConnectionFactory
管理连接池(如 Lettuce 或 Jedis)。