IDEA 自动加 serialVersionUID 字段
报告实现 Serializable
且不声明 serialVersionUID
字段的类.
如果没有 serialVersionUID
字段, 对该类做出的任何更改都将导致以前序列化的版本不可读.
serialVersionUID 是啥?
所述的 serialVersionUID 属性是用来序列的标识符/反序列化的对象序列化类.
序列化运行时与每个可序列化的类关联一个版本号, 称为 serialVersionUID, 在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类.
如果接收者为对象加载的类 serialVersionUID 与相应的发送者的类不同, 则反序列化将导致 InvalidClassException. 可序列化的类可以 serialVersionUID 通过声明一个 serialVersionUID 必须为 static, final 和 type 的字段来显式声明其自身 long:
private static final long serialVersionUID = 42L;
如果可序列化的类未显式声明一个 serialVersionUID, 则序列化运行时将根据 serialVersionUID 该类的各个方面为该类计算默认值, 如 Java 对象序列化规范中所述.
但是, 强烈建议所有可序列化的类显式声明 serialVersionUID 值, 因为默认 serialVersionUID 计算对类详细信息高度敏感, 而类详细信息可能会根据编译器的实现而有所不同, 因此可能在 InvalidClassExceptions 反序列化期间导致意外情况.
因此, 为了保证 serialVersionUID 不同 Java 编译器实现之间的值一致, 可序列化的类必须声明一个显式 serialVersionUID 值. 还强烈建议明确 serialVersionUID 声明尽可能使用 private 修饰符, 因为此类声明仅适用于立即声明的类-serialVersionUID 字段作为继承成员没有用.
序列号 UID
简而言之, 我们使用 serialVersionUID 属性记住 Serializable 类的版本, 以验证加载的类和序列化的对象是否兼容.
不同类的 serialVersionUID 属性是独立的. 因此, 不同的类不必具有唯一的值.
不声明会怎样?
如 Java(TM) 对象序列化规范中所讲述的, 如果可序列化类没有显式声明 serialVersionUID, 则序列化运行时将根据类的各个方面计算该类的默认 serialVersionUID 值.
但是, 强烈建议所有可序列化类显式声明 serialVersionUID 值, 因为默认的 serialVersionUID 计算对类详细信息高度敏感, 这些详细信息可能因编译器实现而异, 因此在反序列化过程中可能会导致意外的 InvalidClassExceptions.
因此, 为了保证在不同的 java 编译器实现中 SerialVersionId 值是一致的, 可序列化类必须声明一个显式的 SerialVersionId 值. 还强烈建议显式 serialVersionUID 声明尽可能使用 private 修饰符, 因为此类声明仅适用于立即声明的类——serialVersionUID 字段不可用作继承成员.
踩坑
Java Object Serialization 会使用对象中的 serialVersionUID 常量属性作为该对象的版本号, 进行反序列化时会校验该版本号是否一致, 如果不一致会导致序列化失败, 抛出 InvalidClassException 异常
默认情况下, JVM 为每一个实现了 Serializable 的接口的类生成一个 serialVersionUID(long), 这个 ID 的计算规则是通过当前类信息 (类名, 属性等) 去生成的, 所以当属性有变更时这个 serialVersionUID 也一定会发生变更
这个 serialVersionUID 的生成, 和所使用的 JDK 有关, 不同的 JDK 可能会生成不一样的版本号, 所以最好是手动生成一个, 大多数 JAVA IDE 都会提供这个生成的功能.
serialVersionUID 不生成呢 idea 常规设置下也不会报错, 甚至连警告都没有, 其次就是大部分人接受的信息就是 serialVersionUID 建议是要生成一个唯一的, 但是不生成也没关系, jvm 会帮你生成一个.
问题就在这里了, 如果你的类继承了 Serializable, 然后你也没生成 serialVersionUID, 然后你又用到了序列化, 那么你的程序运行也是没有任何问题的, 但是你坑的将是后来人!!! 也许就是将来的你自己.
serialVersionUID 没有的情况下, jvm 生成了一个你不可见的, 然后当你修改过这个类以后, 你就会体会到其中的酸爽, jvm 生成的 serialVersionUID 变了, 就会导致你反序列化失败, 导致大量的线上问题, 当然这可能是某一次请求, 你更新缓存后就好了, 但是影响面是巨大的, 影响的用户数量也是巨大的, 对于一些严谨的公司或场景, 是决定不允许这种情况出现的.
serialVersionUID 不一致兼容处理
处理这个不一致也很简单, 既然反序列化时使用 ObjectInputStream 来实现, 那么这里自定义一个 CompatibleInputStream 继承 ObjectInputStream, 然后重写 readClassDescriptor 方法即可
当遇到目标数据 Class 版本号和本地 Class 版本号不一致时, 默认使用本地版本的 Class
public class CompatibleInputStream extends ObjectInputStream {
private static Logger logger = LoggerFactory.getLogger(CompatibleInputStream.class);
public CompatibleInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
Class localClass; // the class in the local JVM that this descriptor represents.
try {
localClass = Class.forName(resultClassDescriptor.getName());
} catch (ClassNotFoundException e) {
logger.error("No local class for " + resultClassDescriptor.getName(), 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.
final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
s.append("local serialVersionUID = ").append(localSUID);
s.append(" stream serialVersionUID = ").append(streamSUID);
Exception e = new InvalidClassException(s.toString());
logger.error("Potentially Fatal Deserialization Operation.", e);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}
return resultClassDescriptor;
}
}
文章评论