
1. serialVersionUID 问题
前段时间正好也遇到了serialVersionUID 问题, 系统升级后,发现部分App终端办理部分业务的时候会出现系统InvalidClassException报错(如下图),从报错内容看问题是由于serialVersionUID引起

分析这个问题,可以从系统日志中找到到报错对应的堆栈信息,从报错日志看ObjectStreamClass#initNonProxy处理时抛出异常InvalidClassException 。
Exception in thread "main" java.io.InvalidClassException: com.star.sms.service.accept2.dto.purchase.ResourceDTO; local class incompatible: stream classdesc serialVersionUID = -, local class serialVersionUID = at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373) at serialization.Reader.main(Reader.java:16)
要了解报错具体原因还需要查看java.io.ObjectStreamClass的源码,从源码看,ObjectInputSteam流对象读取数据个过程中, 程序会做serialVersionUID对比,对比时发现了serialVersionUID值不同,所以抛出了异常, 并在异常中记录了读取的对象信息和serialVersionUID 值,
//查看java.io.ObjectStreamClass源码 if (serializable == localDesc.serializable && !cl.isArray() && suid.longValue() != localDesc.getSerialVersionUID()){ throw new InvalidClassException(localDesc.name, "local class incompatible: " + "stream classdesc serialVersionUID = " + suid + ", local class serialVersionUID = " + localDesc.getSerialVersionUID()); }
上面抛错的原因就是ResourceDTO对象读取存在问题,查看ResourceDTO发现对象中并没有显示定义serialVersionUID,那么serialVersionUID的值如何来的呢, 并且serialVersionUID有什么作用呢?
public class ResourceDTO implements Serializable { private Long resourceId; private Long resourceTypeId; //略 }
2.什么是序列化
了解serialVersionUID 之前,需要先理解什么是序列化
在创建数据对象的时候,我们经常会让对象实现java.io.Serializable接口,Serializable接口是启用其序列化功能的接口。实现java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化,
java.io.Serializable接口没有任何实现,一般我们把这类接口叫做标识接口
package java.io; public interface Serializable { }
为什么要序列化呢?
序列化是指把java对象转换为字节序列的过程,反序列化是指把字节序列恢复为java对象的过程。 任何类型只要实现了Serializable接口,就可以被保存到文件中,或者作为数据流通过网络发送到别的地方。也可以用管道来传输到系统的其他程序中。 由此可见序列化操作的重要性。
3. serialVersionUID 作用
当Java对象做序列化和反序列化操作的时候,需要验证版本一致性。serialversionuid的作用是标识版本,主要用于程序的版本控制;如果serialversionuid一致,说明他们的版本是一样的;反之就说明版本不同, 比如当我们进行序列化操作一个对象,会把当前的版本serialversionuid写入到文件之中。在运行的时候,它就会监测当前版本的serialversionuid与编写版本是否一致。
Java对象实现java.io.Serializable接口后,可以定义serialVersionUID 也可以不定义serialVersionUID
public class ResourceDTO implements Serializable { //定义serialVersionUID private static final long serialVersionUID = -L; private Long resourceId; private Long resourceTypeId; //略 }
当不定义 时,一般开发工具会有黄色波浪线警告,内容为“The serializable class 类名 does not declare a static final serialVersionUID field of type long”
如果没有定义一个名为serialVersionUID,类型为long的变量,Java序列化机制会根据编译的class自动生成一个serialVersionUID,即隐式声明。默认的serialVersionUID计算时,对类详细信息高度敏感(类名、接口名、成员方法及属性等),并且这些详细信息可能因编译器而异。因此只有同一次编译生成的class才会生成相同的serialVersionUID 。此时如果对某个类进行修改的话,那么版本上面是不兼容的,就会出现反序列化报错问题。
4.文章总结
当理解了serialVersionUID作用后, 上文中提到的系统报错问题原因就十分明显了, ResourceDTO 对象没有声明serialVersionUID。 因此在程序中存在的ResourceDTO对象并非是同一次编译的对象。不同对象造成了反序列化报错, 也正是按照这个思路分析系统最终发现,系统中补丁部署存在问题。
当保证系统中只有一个版本的ResourceDTO 对象问题就可解决,或者显示声明serialVersionUID,即使有不同版本ResourceDTO,保证serialVersionUID相同也可以避免上问题提到的InvalidClassException报错, 所以开发的时候也将常设置serialVersionUID = 1L
public class ResourceDTO implements Serializable { private static final long serialVersionUID = 1L; private Long resourceId; private Long resourceTypeId; //略 }
上一篇:JVM记一次CPU飙升
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/176505.html原文链接:https://javaforall.net
