
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序列化与反序列化是一把威力巨大但亦需谨慎使用的"双刃剑"。它赋予了对象持久化与网络传输的能力,但同时也埋下了性能陷阱与安全漏洞的隐患。
在使用过程中要注意工具的统一,网络传输实现序列化接口等,消灭潜在的危机。