即使设置了 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)失效,但事实并非如此。如您所见,错误日志指向的 serialVersionUID18486...-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的主要内容,如果未能解决你的问题,请参考以下文章

面试题:serialVersionUID

IDEA自动生成 serialVersionUID 设置

为啥Java忽略我的serialVersionUID?

让Java运行时忽略serialVersionUIDs?

IDEA生成serialVersionUID的警告

idea生成serialVersionUID