java - 即使设置了 serialVersionUID,反序列化也会引发 InvalidClassException
问题描述
前段时间我发布了一个序列化/反序列化用户对象的应用程序。
public String serializeUser(final User user) {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
final ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
objectOutputStream.close();
} catch (final IOException exception) {
...
}
return new String(Base64.encode(byteArrayOutputStream.toByteArray(), DEFAULT));
}
public User deserializeString(final String userString) {
final byte userBytes[] = Base64.decode(userString.getBytes(), DEFAULT);
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(userBytes);
final ObjectInputStream objectInputStream;
final User user;
try {
objectInputStream = new ObjectInputStream(byteArrayInputStream);
user = (User) objectInputStream.readObject();
objectInputStream.close();
} catch (final IOException | ClassNotFoundException exception) {
...
}
return user;
}
该对象是这样实现的:
public class User implements Serializable {
private String email;
private String name;
...
}
然后,在修改了我的对象(我添加了一个新字段)之后,我学到了一个艰难的方法,即serialVersionUID
在对象定义发生变化的情况下必须设置,否则反序列化器将无法识别存储的对象(因为它将自动生成serialVersionUID
)。所以我继续这样做:
public class User implements Serializable {
private static final long serialVersionUID = 123L;
...
}
但是现在我已经重新发布了带有这些更改的应用程序,我不断收到错误报告,表明该对象无法反序列化:
引起:java.io.InvalidClassException: com.myproject.he; 本地类不兼容:流classdesc serialVersionUID = 184861231695454120,本地类serialVersionUID = -2021388307940757454
我非常清楚设置新的串行版本会使任何以前的串行版本(link1,link2)无效,但事实并非如此。正如您所看到的,错误日志指向的(和)与我手动设置给我的班级的完全不同serialVersionUID
(18486...
和)。-20213...
User
123L
我错过了什么?
如果它有任何相关性,我正在使用带有默认配置的 Proguard。
解决方案
谷歌搜索了一段时间后,我偶然发现了这个问题:如何阻止 ProGuard 从类中剥离 Serializable 接口。所以我继续深入研究 APK(可以在 Android Studio中分析 APKUser
),找到我的类,并查看它的字节码:
class public Lcom/myproject/h/e;
.super Ljava/lang/Object;
.source "User.java"
# interfaces
.implements Ljava/io/Serializable;
# annotations
...
# instance fields
.field private a:Ljava/lang/String;
.field private b:Ljava/lang/String;
...
# direct methods
...
如您所见,静态字段serialVersionUID
无处可寻。因此,我继续按照文档的建议添加了以下配置:
[应用程序] 可能包含已序列化的类。根据它们的使用方式,它们可能需要特别注意。[...] 有时,序列化的数据会被存储,并在稍后读回到可序列化类的更新版本中。然后必须注意这些类与其未处理的版本和未来的处理版本保持兼容。在这种情况下,相关类很可能具有
serialVersionUID
字段。那么以下选项应该足以确保随着时间的推移保持兼容性:
-keepnames class * implements java.io.Serializable`
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
因此,如果我转到新生成的 APK 并检查我的对象的字节码,它现在指定serialVersionUID
不要丢弃或重命名:
class public Lcom/myproject/model/User;
.super Ljava/lang/Object;
.source "User.java"
...
# static fields
.field private static final serialVersionUID:J
# instance fields
...
我希望该应用程序在反序列化我的对象时不会再遇到任何问题。
推荐阅读
- python - pygame.error: 视频系统未初始化 pygame.init() 已调用
- augmented-reality - Spark AR 补丁:在 3 个画布之间切换
- python - 如何使用正则表达式在字符串中重复多次的模式
- c++ - 如果你有两个同名的函数(重载),并且有一个转换构造函数,会调用转换构造函数吗?
- c# - 有没有办法通过在 c# 中结束子字符串来对字符串列表进行排序?
- python-3.x - 如何在 tkinter 的单个应用程序窗口中打开子框架?
- java - 如何在 GitHub 中调用 API
- if-statement - 我可以使用什么公式或方法从使用表格的范围内的值中减去金额
- machine-learning - 当梯度下降算法已经处于局部最小值时它如何工作
- r - R:图书馆(闪亮)与图书馆(DT):创建
对于 Plot 上的不同颜色