Serialization is a useful feature built into Java. It converts objects into a bytestream that can be saved to a file or transmitted over a network and then reconstructed later into the same object (perhaps in a different Java VM). The basics are detailed here, with some more advanced features here and the complete API is here. Despite all this documentation, I recently came a across a problem not described. What if an object being deserialized requires a reference to some non-serializable object in the
application. That is how can an object be reconstructed when it contains part of the application's context that can't be serialized. For instance, what if the object has a network connection as a member variable. The network connection can not be meaningfully serialized, so when the object is deserialized it has somehow reconnect itself to the network, which requires some reference to the network context. This may sound like a contrived example, but it occurs in the Red Dwarf Server (previously known as Sun's Darkstar project) where network connections are transparently moved around a server cluster.
The best way to solve this problem is to ensure that the serializable objects are self-contained and do not need any outside context when deserialized. Let's assume that is not possible and move on. Next best is to hook the object to its required context immediately after the deserialization. However, this may not be practicable because the deserialized object is not easily accessible (for example, if it is deep in an object graph - which is the case in Red Dwarf and my code).
Red Dwarf solves the problem by defining a static accessor on the context class and then on the serializable object adding a serialization override method like the below:
private void readObject(ObjectInputStream in) throws Exception {
in.defaultReadObject();
this.contextObject = Context.getContextObject();
}
Checking out the Java serialization source code, I thought there may be some way of extending the deserialization process. The main extension points seem to be the readObject(ObjectInputStream in), writeObject(ObjectOutputStream in) and readResolve() methods added to the serializable object. These are found by reflection on their signature in a private method and can not be changed. There is a method readObjectOverride() on ObjectInputStream(), which allows a bespoke deserialization process to be defined. However, so many of the methods on ObjectInputStream are private that this would be like writing a new process from scratch.
// imports skipped for brevity
public class SerializationTest {
public static class A implements Serializable {
public int a;
public A(int x) {a = x;}
private void readObject(ObjectInputStream in) throws Exception {
in.defaultReadObject();
if (in instanceof MyObjectInputStream) {
System.out.println("Found context=" + ((MyObjectInputStream) in).context);
}
}
}
public static class MyObjectInputStream extends ObjectInputStream {
private final int context;
public MyObjectInputStream(InputStream in, int context) throws Exception {
super(in);
enableResolveObject(true); // requires permissions
this.context = context;
}
protected Object resolveObject(Object obj) throws IOException {
return new A(3);
}
}
public static void main(String[] args) throws Exception {
A test = new A(2);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(test);
out.close();
byte[] buf = bos.toByteArray();
MyObjectInputStream in = new MyObjectInputStream(new ByteArrayInputStream(buf), 9);
A test2 = (A) in.readObject();
System.out.println("Serialized object=" + test2.a);
}
}