前言
开发过程中某个类并没有指定serialVersionUID,并且该类是通过序列化存储在数据库中的,并没有转换为json存储,就导致在该类添加新字段之后,原来的数据不能进行反序列化,提示反序列化异常。
复现过程
创建测试类
@Data
public class User implements Serializable {
private String userName;
private String age;
}
写入序列化对象到文件
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User();
user.setUserName("张三");
user.setAge("18");
// 写入序列化数据
writeObject(user);
}
/**
* 写入序列化
*
* @param obj 需要写入的对象
*/
public static Path writeObject(Object obj) throws IOException {
Path path = getPath();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(path))) {
objectOutputStream.writeObject(obj);
}
return path;
}
public static Path getPath() {
return Paths.get("G:\\临时\\1");
}
测试读取
public static void main(String[] args) throws IOException, ClassNotFoundException {
Object object = readObject(getPath());
// 输出结果: User(userName=张三, age=18)
System.out.println(object);
}
/**
* 读取序列化对象
*
* @return 反序列化之后的对象
*/
public static Object readObject(Path path) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(path));
return objectInputStream.readObject();
}
修改serialVersionUID
@Data
public class User implements Serializable {
// 添加serialVersionUID字段
private static final long serialVersionUID = 1L;
private String userName;
private String age;
}
测试读取
public static void main(String[] args) throws IOException, ClassNotFoundException {
Object object = readObject(getPath());
// 触发异常:local class incompatible: stream classdesc serialVersionUID = 7769644417338494813, local class serialVersionUID = 1
System.out.println(object);
}
忽略serialVersionUID读取类信息
public class CompatibleInputStream extends ObjectInputStream {
public CompatibleInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
Class localClass;
try {
localClass = Class.forName(resultClassDescriptor.getName());
} catch (ClassNotFoundException e) {
return resultClassDescriptor;
}
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
if (localClassDescriptor != null) {
final long localSUID = localClassDescriptor.getSerialVersionUID();
final long streamSUID = resultClassDescriptor.getSerialVersionUID();
if (streamSUID != localSUID) {
try {
Field suid = resultClassDescriptor.getClass().getDeclaredField("suid");
suid.setAccessible(true);
suid.set(resultClassDescriptor, localSUID);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return resultClassDescriptor;
}
}
编写新读取方法
/**
* 获取序列化数据
*
* @return 反序列化之后的对象
*/
public static void getObject(Path path) throws IOException, ClassNotFoundException {
InputStream inputStream = Files.newInputStream(path);
// 解析反序列化数据
ObjectInputStream s = new CompatibleInputStream(inputStream);
// 读取对象
Object readObject = s.readObject();
System.out.println(readObject);
}
测试新读取方法
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 输出 User(userName=张三, age=18)
getObject(getPath());
}
此致便完成了忽略serialVersionUID不一致,导致无法反序列化读取的问题。
治本之法
上次解决方案只是忽略serialVersionUID不一致来读取,不过每次都要使用CompatibleInputStream来读取,便有些麻烦,其实我们可以在解析之后,再将其写入到文件或数据库中,这样便不用每次使用CompatibleInputStream来读取了。
修该旧序列化数据的serialVersionUID
/**
* 修改序列化数据
*
* @return 反序列化之后的对象
*/
public static void updateObject(Path path) throws IOException, ClassNotFoundException {
InputStream inputStream = Files.newInputStream(path);
// 解析反序列化数据
ObjectInputStream s = new CompatibleInputStream(inputStream);
// 读取对象
Object readObject = s.readObject();
// 写入数据
Path objPath = writeObject(readObject);
// 读取数据
Object obj = readObject(objPath);
System.out.println(obj);
}
测试
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 输出结果为:User(userName=张三, age=18)
updateObject(getPath());
}
至此便完成了修改serialVersionUID的功能,这样只需执行一次,另外我们添加了serialVersionUID 的值为1,下次无论是新增还是删除字段,都不会发生序列化的问题了,不过,最优的解决办法还是在创建类时就手动创建serialVersionUID 的值,也就不会遇到今天的问题了。