Java序列化serialVersionUID不一致处理

总结  收藏
0 / 131

Java Object Serialization 会使用对象中的serialVersionUID私有静态常量长整型属性(private static final long)作为该对象的版本号,反序列化时 JVM 会校验该版本号是否和序列化时的一致,如果不一致会导致序列化失败,抛出InvalidClassException异常:

Caused by: java.io.InvalidClassException: org.jasig.cas.authentication.AbstractAuthentication; local class incompatible: stream classdesc serialVersionUID = 6893533826116808126, local class serialVersionUID = -2472566555240528245

默认情况下,JVM 为每一个实现了Serializable的接口的类生成一个serialVersionUID,这个版本ID的计算规则是通过当前类信息(类名、属性、方法、修饰符等)生成的,所以当属性有变更时这个serialVersionUID也一定会发生变更(简单新增空行、空格并不会影响serialVersionUID生成)。

这个serialVersionUID的生成,和所使用的JDK有关,不同的JDK可能会生成不一样的版本ID。

而且考虑到实际业务场景,变更属性修改类是常有的事,如果使用自动生成的版本ID很容易造成serialVersionUID不一致的问题,导致反序列化失败。

所以最好是手动显示生成,大多数 JAVA IDE 都会提供自动生成版本ID的功能。

另外:同一个类在不同的启动环境中,serialVersionUID 可能会不一样。serialVersionUID 是一个标识符,用于在序列化和反序列化过程中验证类的完整性。如果一个类被序列化,它的 serialVersionUID 会被用来检查序列化数据的完整性。在不同的启动环境中,Java 虚拟机(JVM)的实现、版本和配置可能会有所不同,这可能会导致不同的类具有不同的 serialVersionUID。因此,建议为每个类分配一个唯一的 serialVersionUID,以确保序列化数据的正确性。

普通序列化(Serialization) 和 反序列化(Deserialization):

public class SerializeTest {

    public static void main(String[] args) throws Exception {
        // Serialize to a file.
        FileOutputStream f = new FileOutputStream("./tmp");
        ObjectOutput s = new ObjectOutputStream(f);
        s.writeObject("Today");
        s.writeObject(new Person());
        s.flush();

        // Deserialize a string and object from a file.
        FileInputStream in = new FileInputStream("./tmp");
        ObjectInputStream rd = new ObjectInputStream(in);
        System.out.println((String) rd.readObject());
        System.out.println((Person) rd.readObject());
    }
}

使用ObjectOutputStream序列化到文件中,然后使用ObjectInputStream反序列化,这种方式,如果serialVersionUID不一致反序列化时会报错;

处理这个不一致也很简单,既然反序列化时使用ObjectInputStream来实现,那么这里自定义一个CompatibleInputStream继承 ObjectInputStream,然后重写readClassDescriptor方法即可。

当遇到目标数据Class版本号和本地Class版本号不一致时,默认使用本地版本的Class

public class CompatibleInputStream extends ObjectInputStream {

    private static final Logger logger = LoggerFactory.getLogger(CompatibleInputStream.class);

    public CompatibleInputStream(InputStream in) throws IOException {
        super(in);
    }

    @Override
    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        // initially streams descriptor
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
        // the class in the local JVM that this descriptor represents.
        Class localClass;
        try {
            localClass = Class.forName(resultClassDescriptor.getName());
        } catch (ClassNotFoundException e) {
            return resultClassDescriptor;
        }
        ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
        if (localClassDescriptor != null) {
            // only if class implements serializable
            final long localSuid = localClassDescriptor.getSerialVersionUID();
            final long streamSuid = resultClassDescriptor.getSerialVersionUID();
            if (streamSuid != localSuid) {
                // check for serialVersionUID mismatch.
                logger.info("Overriding serialized class version mismatch: " +
                        "local serialVersionUID {} stream serialVersionUID {}",localSuid,streamSuid);
                // Use local class descriptor for deserialization
                resultClassDescriptor = localClassDescriptor;
            }
        }
        return resultClassDescriptor;
    }
}