tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 探索SpringBoot

  • 常用功能实现

    • SpringBoot常用功能实现_1
    • SpringBoot中拦截器与日志
    • 多环境配置与数据绑定
    • 异步方法(线程池)的实现
    • controller参数接收
    • SpringBoot中关于日志的更好使用
    • 异常捕获的一些细节
    • 时间跟其他数据的序列化
      • 序列化
        • 简介
        • 为什么要序列化
        • 常见序列化方式
        • Java原生序列化
        • JSON序列化
        • 序列化带来的问题
        • 扩展
      • 时间的格式化
        • 解决方案-参考
        • 其他配置例子
      • Long类型的序列化
        • Long转String
      • Redis数据的序列化
        • 默认的序列化
        • 重新序列化
        • 一个问题
      • 更多的序列化
    • 面向切面跟自定义注解的结合
  • Security认证授权

  • 扩展

  • 实战与注意事项

  • 其它

  • 《SpringBoot》笔记
  • 常用功能实现
EffectTang
2025-05-19
目录

时间跟其他数据的序列化

# 时间跟其他数据的序列化

# 序列化

# 简介

  • 简单版

序列化:将内存中的对象或数据结构转换为可存储或传输的格式(如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();  // 恢复对象
}
1
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→对象
1
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;
    }
}
1
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);
    }
}
1
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);
    }
}
1
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;
    }
}
1
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);
    }
}
1
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 原生序列化),这种方式存在以下问题:

  1. 类路径依赖:反序列化时需要类的全限定名,若类结构变更或类路径丢失,会抛出异常。
  2. 生成的 Key 不友好:默认序列化后的 Key 是二进制格式,难以调试和查看。
  3. 兼容性差:不支持跨语言访问 Redis 数据。

因此,推荐使用 JSON 序列化(如 Jackson)替代默认的 Java 原生序列化。

# 重新序列化

下面是一个完整的配置和代码示例:

确保项目中包含 Jackson 依赖(spring-boot-starter-data-redis 本身不包含 Jackson):

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
1
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;
    }
}
1
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>

1
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)。

# 更多的序列化

上次更新: 2025/05/21, 15:29:11
异常捕获的一些细节
面向切面跟自定义注解的结合

← 异常捕获的一些细节 面向切面跟自定义注解的结合→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
数据加密与安全
05-17
03
异常捕获的一些细节
05-14
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式