0%

java 序列化Serializable 的反射调用方法

java 序列化和反序列化过程中,留下了一些方法作为钩子,可以让我们自定义序列化和反序列化的流程和结果,主要是两组五个 Method,在要实现序列化的类中定义这些方法,运行中将通过反射调用。

writeObject()、readObject()、writeReplace() 与 readResolve()

1
2
3
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

writeObject 和 readObject 方法分别负责写入和读取对象状态,readObjectNoData 负责初始化对象状态。

1
2
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

writeReplace 方法可以返回某个对象,接下来将对此方法的返回值进行序列化;readResolve 可以返回一个对象,作为反序列化的结果值。

使用这两个方法,可以在序列化和反序列化的过程中将对象实例替换为任意其他的对象实例,偷梁换柱,比如,在实现了 Serializable 的类 SerializableTest 中,定义了如下 writeReplace 方法,可以替换掉序列化的值。

1
2
private Object writeReplace() throws ObjectStreamException {
return new Integer(1);

使用如下代码进行序列化和反序列化如下,

1
2
3
4
5
6
7
8
SerializableTest test = new SerializableTest();

ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("a"));
o.writeObject(test);

ObjectInputStream in = new ObjectInputStream(new FileInputStream("a"));
Object result = in.readObject();
System.out.println(result.getClass().getSimpleName());

以上代码的输出将会是:

1
Integer

进行反序列化,默认将返回一个值为 1 的 Integer 对象。

各个 Method 的执行顺序

writeObject、readObject、writeReplace 和 readResolve 的执行顺序,如果在 writeReplace 和 readResolve 中将操作对象替换为其他类的实例,那么之后的操作将是对那一对象执行的,该对象没有定义其他方法时,反射自然也不会调用。

如果在 writeReplace 和 readResolve 中没有替换为其他类的实例,那么以上四个方法的执行顺序是:

1
writeReplace -> writeObject -> readObject -> readResolve

序列化与单例

以一个简单的单例模式举例:

1
2
3
4
5
6
7
8
public class SerializableTest implements Serializable {

public static final SerializableTest t = new SerializableTest();

private SerializableTest() {

}
}

将构造器设置为 private 权限,使用 static 和 final,这样运行中获得的实例都应是同一个对象,但是使用序列化/反序列话时,这将被打破。如下:

1
2
3
4
5
6
7
ObjectOutputStream oou = new ObjectOutputStream(new FileOutputStream("serializable.a"));
oou.writeObject(new SerializableTest());
oou.close();

ObjectInputStream oin = new ObjectInputStream(new FileInputStream("serializable.a"));
SerializableTest t1 = (SerializableTest) oin.readObject();
System.out.println(t == t1);

此时将输出false,也就是说反序列化读入的对象与原对象不是同一个。

为了解决这个问题,我们在 SerializableTest 类中添加一个 readResolve() 方法,返回我们希望的对象,如下:

1
2
3
protected Object readResolve() throws ObjectStreamException {
return t;
}

我们在 readResolve() 方法返回我们希望的实例,这样再次运行,将输出true,反序列化得到的是原来的对象。

debug 一下可以追踪到,在 ObjectInputStream 类的 readOrdinaryObject(boolean unshared) 方法有以下代码:

1
2
3
4
5
6
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
......

再继续,可以看到 invokeReadResolve(Object obj) 方法有以下代码:

1
2
3
4
5
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
......

也就是说,以上的“单例模式”,以及常用的双重校验锁等,在反序列化的情况下会构建出新的对象,添加 readResolve() 方法可以在反序列化时保持单例。