即使设置了 serialVersionUID,反序列化也会引发 InvalidClassException
Posted
技术标签:
【中文标题】即使设置了 serialVersionUID,反序列化也会引发 InvalidClassException【英文标题】:Deserialization raises InvalidClassException even when serialVersionUID is set 【发布时间】:2019-10-05 22:13:10 【问题描述】:前段时间我发布了一个序列化/反序列化用户对象的应用程序。
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.h.e;本地类不兼容:stream classdesc serialVersionUID = 184861231695454120,本地类serialVersionUID = -2021388307940757454
我非常清楚,设置新的串行版本会使任何以前的串行版本(link1、link2)失效,但事实并非如此。如您所见,错误日志指向的 serialVersionUID
(18486...
和 -20213...
)与我手动设置为 User
类(123L
)的完全不同。
我错过了什么?
如果有任何相关性,我将使用 Proguard 和默认配置。
【问题讨论】:
(FWIW,如果你在JDK中使用serialver
命令行程序,它会给你一个类的序列版本UID。即使你没有指定具体的值,Java序列化机制将为您计算一个。使用相同的 UID 可以读取旧数据。尽管如果您发布了多个版本,但公共方法略有不同(请参阅文档了解详细信息),那么它们将产生不兼容的数据。)跨度>
【参考方案1】:
在谷歌上搜索了一段时间后,我偶然发现了这个问题:How to stop ProGuard from stripping the Serializable interface from a class。所以我继续深入研究 APK(可以在 android Studio 中使用 analyze the APK),找到我的 User
类,并查看它的字节码:
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
无处可寻。所以我继续添加以下配置作为documentation suggests:
[Applications] 可能包含已序列化的类。根据它们的使用方式,它们可能需要特别注意。 [...] 有时,序列化的数据会被存储,并稍后读回可序列化类的更新版本。然后必须注意这些类与其未处理的版本和未来的处理版本保持兼容。在这种情况下,相关类很可能具有
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
...
我希望应用程序在反序列化我的对象时不会再遇到任何问题。
【讨论】:
以上是关于即使设置了 serialVersionUID,反序列化也会引发 InvalidClassException的主要内容,如果未能解决你的问题,请参考以下文章