Java Serializable 定制
2016-05-29 [java
serialize
序列化
]
接着 上文 说到的变更问题。 如果 User 在后来的某一个时间又发生了改变,比如你认为把 age 属性改为 birthday 更科学。 但是由于之前的版本已经在线上运行了一段时间,磁盘上的所有 .ser 文件中并没有保存 birthday 而是 age, 那么代码修改之后,虽然用 serialVersionUID 向前兼容了, 但是通过 Java 默认的 readObject 并不能将 age 转换为 birthday ,so 让我们定制一下吧。
writeObject && readObject
如果希望由自己完成整个序列化,可以在类上声明该方法并实现
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
反序列化同理:
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
测试一下:
CustomizedUser.java
public class CustomizedUser implements Serializable {
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
this.id = in.readInt();
this.nickname = in.readUTF();
this.password = in.readUTF();
this.birthday = (LocalDate) in.readObject();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(id);
out.writeUTF(nickname);
out.writeUTF(password);
out.writeObject(birthday);
}
...
public static void main(String[] args) throws IOException,
ClassNotFoundException {
CustomizedUser u = new CustomizedUser();
u.id = 1;
u.nickname = "zhangsan";
u.password = "123";
u.birthday = LocalDate.of(1990, 9, 12);
u.gender = 1;
u.serialize("cuser.1.ser");
u.deserialize("cuser.1.ser");
System.out.println(u);
}
}
输出:
{ id:1, nickname:zhangsan, password:123, age:26 }
但是,自定义整个序列化方案并不能太好地兼容老的代码,Java 随之又提出了新的解决方式。
writeReplace && readResolve
也就是说,在序列化和反序列化的过程中增加一层。例如 Web 前端和服务端交互的数据结构往往是 Data Transfer Object(Value Object),而在这些 JSON 和数据库表之间,还有一层称为 Persistent Object 的对象存在。
writeReplace 和 readResolve 方法的返回值就充当了这一层角色。
public class AlternativeUser implements Serializable {
...
private Object writeReplace() throws ObjectStreamException {
return new UserSerializer(this);
}
public static void main(String[] args) throws IOException,
ClassNotFoundException {
AlternativeUser u = new AlternativeUser();
u.id = 1;
u.nickname = "zhangsan";
u.password = "123";
u.age = 5;
u.gender = 1;
u.serialize("aluser.ser");
u.deserialize("aluser.ser");
System.out.println(u);
}
...
}
class UserSerializer extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
UserSerializer(AlternativeUser au) {
this.put("id", au.id);
this.put("nickname", au.nickname);
this.put("password", au.password);
this.put("age", au.age);
this.put("gender", au.gender);
}
private Object readResolve() throws ObjectStreamException {
AlternativeUser au = new AlternativeUser();
au.id = (int) this.get("id");
au.nickname = (String) this.get("nickname");
au.password = (String) this.get("password");
au.age = (int) this.get("age");
au.gender = (byte) this.get("gender");
return au;
}
}
最后输出的结果如下:
{ id:1, nickname:zhangsan, password:123, age:5, gender:1 }
UserSerializer 看起来使用比较奇怪,这是因为 readResolve 方法没有提供直接访问 readObject 方法返回值的方式,导致我们不得不在另外一个类中使用它。
然后,回到我们之前的兼容性问题,在 UserSerializer.readResolve 方法中我们终于可以通过判断 map 中是否存在对应的 field 来初始化反序列化的返回值。
到目前,我们已经做到了版本兼容,实现的方式就是:
- 增加一层 Serialize Object。
- 饶一个弯,在代理中使用 readResolve 而不是类本身。
参考资料
- java.io.Serializable
- 5 things you didn’t know about … Java Object Serialization
- Java Object Serialization Specification
- 《深入理解Java7 - 成富》
<<<EOF