五年后,当我通过谷歌偶然发现这篇文章后,我发现我最初的答案并不令人满意。另一种解决方案是完全不使用反射,并使用 Boann 建议的技术。
它还利用方法返回的GetField类ObjectInputStream#readFields()
,根据序列化规范,必须在私有readObject(...)
方法中调用该类。
FinalExample#fields
该解决方案通过将检索到的字段存储在由反序列化过程创建的临时“实例”的临时瞬态字段(称为 )中,使字段反序列化显式化。然后所有对象字段都被反序列化并被readResolve(...)
调用:创建一个新实例,但这次使用构造函数,丢弃带有临时字段的临时实例。实例使用实例显式恢复每个字段GetField
;这是检查任何参数的地方,就像任何其他构造函数一样。如果构造函数抛出异常,则将其转换为 anInvalidObjectException
并且该对象的反序列化失败。
包含的微基准确保此解决方案不比默认序列化/反序列化慢。事实上,它在我的电脑上:
Problem: 8.598s Solution: 7.818s
然后这里是代码:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import org.junit.Test;
import static org.junit.Assert.*;
public class FinalSerialization {
/**
* Using default serialization, there are problems with transient final
* fields. This is because internally, ObjectInputStream uses the Unsafe
* class to create an "instance", without calling a constructor.
*/
@Test
public void problem() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
WrongExample x = new WrongExample(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
WrongExample y = (WrongExample) ois.readObject();
assertTrue(y.value == 1234);
// Problem:
assertFalse(y.ref != null);
ois.close();
baos.close();
bais.close();
}
/**
* Use the readResolve method to construct a new object with the correct
* finals initialized. Because we now call the constructor explicitly, all
* finals are properly set up.
*/
@Test
public void solution() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
FinalExample x = new FinalExample(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
FinalExample y = (FinalExample) ois.readObject();
assertTrue(y.ref != null);
assertTrue(y.value == 1234);
ois.close();
baos.close();
bais.close();
}
/**
* The solution <em>should not</em> have worse execution time than built-in
* deserialization.
*/
@Test
public void benchmark() throws Exception {
int TRIALS = 500_000;
long a = System.currentTimeMillis();
for (int i = 0; i < TRIALS; i++) {
problem();
}
a = System.currentTimeMillis() - a;
long b = System.currentTimeMillis();
for (int i = 0; i < TRIALS; i++) {
solution();
}
b = System.currentTimeMillis() - b;
System.out.println("Problem: " + a / 1000f + "s Solution: " + b / 1000f + "s");
assertTrue(b <= a);
}
public static class FinalExample implements Serializable {
private static final long serialVersionUID = 4772085863429354018L;
public final transient Object ref = new Object();
public final int value;
private transient GetField fields;
public FinalExample(int value) {
this.value = value;
}
private FinalExample(GetField fields) throws IOException {
// assign fields
value = fields.get("value", 0);
}
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
fields = stream.readFields();
}
private Object readResolve() throws ObjectStreamException {
try {
return new FinalExample(fields);
} catch (IOException ex) {
throw new InvalidObjectException(ex.getMessage());
}
}
}
public static class WrongExample implements Serializable {
private static final long serialVersionUID = 4772085863429354018L;
public final transient Object ref = new Object();
public final int value;
public WrongExample(int value) {
this.value = value;
}
}
}
注意:每当类引用另一个对象实例时,可能会泄漏由序列化过程创建的临时“实例”:对象解析仅在读取所有子对象后发生,因此子对象可能保留对临时对象的引用。类可以通过检查GetField
临时字段是否为空来检查此类非法构造实例的使用。只有当它为空时,它是使用常规构造函数而不是通过反序列化过程创建的。
自我提醒:也许五年后会有更好的解决方案。回头见!