Java序列化陷阱揭秘:这5个错误80%的开发者都犯过

01 引言

Java序列化是将Java对象转换为字节流的过程,以便存储或传输;反序列化则是将字节流恢复为Java对象的过程。这一机制使得对象能够超越JVM的生命周期而存在,也便于在网络间传输。

最近总是遇到序列化与反序列化的问题,不是自己遇到就是帮助同事排查类似的问题,本节总结5个常见的错误,看看你有没有踩坑。

  • getter方法异常
  • 序列化与反序列化不一致
  • JavaBean变更
  • 序列化工具混用
  • Serializable

02 getter方法异常

这个错误是一个同事遇到的,给我说他就在getter方法里面改了点东西,结果方法调用序列化时就异常了。而且getter方法本身没有属性,怎么会影响序列化结果呢?

从截图来看,是fastjson 1.2.54在序列化对象的时候报的错。

2.1 场景复现

定义一个实体类,里面定义了一个getter,直接抛出异常。

java 复制代码
@Data
@AllArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;
    private String name;
    private int age;

    public String getTest() {
        throw new RuntimeException("模拟异常。。。。");
    }
}

2.2 测试

测试代码:

java 复制代码
@Test
void test01() {
    User user = new User(1, "鸣人", 18);
    System.out.println(JSON.toJSONString(user));
}

结果:

可以看到:

getTest并没有对应的属性,但是序列化的时候会调用,触发异常。查看源码发现框架会将所有的getter方法,也会作为属性。

所以,序列化本身看似序列化对象的属性,和方法看似没有关系,但是getter方法也会作为属性,获取值时会调用getter方法,从而触发异常。

03 序列化与反序列化不一致

这个是线上经常遇到的,我们先来看一段代码:

java 复制代码
// 存入缓存
redisService.setExpireDate(key, val, date);

// 从缓存中获取值
String val = redisService.get(key)

结果代码发布之后,出现了反序列化异常。看了封装的源码之后,才恍然大悟。

3.1 序列化

代码中采用了自定义的对象流的序列化方式。

3.2 反序列化

Redis的反序列化直接返回了,我们知道不配置的情况下,序列化和反序列化采用的事JDK自带的序列化方式(JdkSerializationRedisSerializer)。

3.3 结论

显然序列化和反序列化的方式不一样,导致反序列化异常。这个问题主要就是因为封装的工具类定义混乱,导致使用者产生歧义,从而容易踩坑。

04 Java的Bean变更

在日常开发中,Java的Bean的字段会根据需求变更。假设v1版本的User已经序列化放在缓存里了。

json 复制代码
{
    "age": 18,
    "id": 1,
    "name": "鸣人",
    "job": "火影"
}

但是由于需求变更job不在需要了,开发人员在v2版本中删除了这个字段。但是缓存中的数据忘记更新了,jackson默认反序列化的时候,就会出现异常。

4.1 模拟案例

java 复制代码
 @Test
void test04() throws JsonProcessingException {
    String jsonStr = "{\"age\":18,\"id\":1,\"name\":\"鸣人\",\"job\":\"火影\"}";

    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println(objectMapper.readValue(jsonStr, User.class));
}

结果:

4.2 解决方案

清除缓存的方法我们就不讲了,我们就单纯从技术上看怎么解决。

增加反序列化的配置:

java 复制代码
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES默认为true,字段不匹配时就会抛出异常。设置成false,就可以解决。

使用注解:

使用注解忽略不存在的字段:

@JsonIgnoreProperties(ignoreUnknown = true)

最终都可以实现反序列化:

05 序列化工具混用

这个不难理解,其实就是序列化与反序列化工具不一致的问题。为什么要单独讲呢?

因为现在工具类泛滥,尤其json的处理。标准的json处理几乎都没有问题,但是遇到特别的数据,就可以出现问题。不仅仅工具类,相同工具类的不同版本也有了能出现问题,这种问题最难排查,因为问题是偶发的,不可固定重现。

json序列化框架有好多,如fastjson、jackjson、gson等。

日期异常案例:

java 复制代码
@Test
void test05()  {
    User user = new User(1, "鸣人", 18);
    user.setBirthday(new Date());
    String jsonString = JSON.toJSONString(user);
    System.out.println(jsonString);

    Gson gson = new Gson();
    System.out.println(gson.fromJson(jsonString, User.class));
}

我们可以看到fastsjson和gson对日期的处理不一致,导致反序列化异常。

06 Serializable

实现Serializable接口,可以保证传输类在序列化后传递,Serializable之前专门讲过一期,这里不在赘述。

对象在使用java.io.ObjectOutputStream序列化的时候必须实现Serializable接口,否则就会序列化失败。

6.1 案例

这里的User没有实现Serializable接口。

java 复制代码
@Test
void test06() {
    User user = new User(1, "鸣人", 18);
    System.out.println(user);
    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
        oos.writeObject(user); // 成功
    } catch (Exception e) {
        e.printStackTrace();
    }
}

6.2 效果

直接报出异常:java.io.NotSerializableException

实现Serializable就恢复正常了。

07 小结

Java序列化与反序列化是一把威力巨大但亦需谨慎使用的"双刃剑"。它赋予了对象持久化与网络传输的能力,但同时也埋下了性能陷阱与安全漏洞的隐患。

在使用过程中要注意工具的统一,网络传输实现序列化接口等,消灭潜在的危机。

相关推荐
程序猿DD6 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
leobertlan9 小时前
痛苦系列 | DSP-03 跨越单位圆:Z变换与零极点分析
算法·程序员
蛋蛋的就会顺顺的9 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
余槐i9 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8269 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡9 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
凤山老林10 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne11 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF12 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
silvamux_12 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi