五年后,我通过谷歌偶然发现了这篇文章,发现原来的答案并不令人满意。另一个解决方案是根本不使用反射,并使用Boann建议的技术。
它还利用了由
ObjectInputStream#readFields()
方法返回的
GetField类,根据序列化规范,必须在私有的
readObject(...)
方法中调用。
该解决方案通过将检索到的字段存储在由反序列化过程创建的临时“实例”(称为
FinalExample#fields
)的临时瞬态字段中,使字段反序列化明确。然后反序列化所有对象字段,并调用
readResolve(...)
:创建一个新实例,但这次使用构造函数,丢弃具有临时字段的临时实例。该实例使用
GetField
实例显式恢复每个字段;这是检查任何参数的地方,就像任何其他构造函数一样。如果构造函数抛出异常,则将其转换为
InvalidObjectException
,并且此对象的反序列化失败。
这个微基准测试确保这个解决方案不比默认的序列化/反序列化慢。实际上,在我的电脑上它更快:
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 {
@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);
assertFalse(y.ref != null);
ois.close();
baos.close();
bais.close();
}
@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();
}
@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 {
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
临时字段是否为null来检查使用这种非法构造的实例。只有在它为空时,才是使用常规构造函数创建而不是通过反序列化过程创建的。
自我提示:也许五年后会有更好的解决方案。到时候再见!
final transient
,比如在这个链接中的SessionFactoryImpl.java
文件:https://github.com/hibernate/hibernate-orm/blob/4.3.7.Final/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java - Rudi Wijayatransient
字段可以是final
的。但是,为了使其适用于除默认值(false
/0
/0.0
/null
)以外的其他内容,您需要实现不仅readObject()
还要实现readResolve()
,或者使用反射。 - Christian Hujertransient final
正常工作的问题。https://dev59.com/zJffa4cB1Zd3GeqP_bDs - user6117584