Serialization Proxy
Serialization is often complex for real-world classes. Here comes a nice trick which may be useful in your tool box.
Problem Description
I just had the problem that I had a class which is similar to an enum
because it defines constant
values. But it also needed to be user-extensible, so I couldn’t use enum
as I usually would.
So what I had was some kind of extensible singleton which I’ll just christian pseudo-constant
for now.
Internally it is storing data which is inherently unserializable, so just extending the
Serializable
interface
would be useless. So how should I do the serialization?
Solution
Luckily each of the values was already required to define a unique signature (a String
in this
case) and to register itself so it could be looked up based on this signature. So basically on
serialization I could just write out this signature, then restore the registered value from the
deserialized signature.
At first glance the supported methods for serializing objects are not perfectly helpful:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
/*ANY-ACCESS-MODIFIER*/ Object writeReplace() throws ObjectStreamException;
/*ANY-ACCESS-MODIFIER*/ Object readResolve() throws ObjectStreamException
First Try: Ugly
At second glance I can replace an object with readResolve()
, but that does not have
access to the stream. So I’d have to implement a combination of readObject()
(which has access
to the stream) and readResolve()
to replace what I had read with what I wanted.
readObject()
already runs on a new object for which I could read the signature,
store it somewhere (there was no field for it yet, but that could basically be added),
then I could return the real object from readResolve()
by looking up the signature.
For that I’d have to make all other fields transient
and only leave the signature field
for serialization. This seemed possible, but had a certain ugliness to it, mostly for introducing
an otherwise completely unnecessary field.
Second Try: Awful
My second idea was even worse, because I thought that I could put the burden on the classes which were using my pseudo-constants. The idea was to add two static methods
void writePseudoConstant(java.io.ObjectOutputStream out, PseudoConstant constant) {
// write out the signature
}
PseudoConstant readPseudoConstant(java.io.ObjectInputStream in) {
// read the signature, then return the registered constant
}
These could be used by classes with PseudoConstant
fields to serialize and deserialize
these fields. But implementing self-defined writeObject()
and readObject()
methods for non-trivial classes really is a
pita, and mostly fragile or
even plain impossible.
Last Try: Nice and Clear
In the end I followed the old programmer’s rule if you are stuck insert an indirection
and implemented a dedicated proxy class which is serialized and deserialized instead of my
pseudo-constant class. For this my class implements Serializable
and just the
writeReplace()
method:
public class PseudoConstant
implements java.io.Serializable
{
// ... lots of non-serializable fields ...
public String signature()
{
// return signature
}
private Object writeReplace() throws java.io.ObjectStreamException
{
return new PseudoConstantSerializationProxy(this);
}
// [...]
public static PseudoConstant getRegistered(String signature)
{
// return the registered constant for the given signature
}
}
and the proxy class looks like
final class PseudoConstantSerializationProxy
implements java.io.Serializable
{
private static final long serialVersionUID = -396647803428144681L;
private final String signature;
/**
* Constructor.
* @param constant pseudo constant for which this proxy is used
*/
PseudoConstantSerializationProxy(PseudoConstant constant)
{
this.signature = constant.signature();
}
/**
* Resolve this proxy back to the original pseudo constant.
* @return pseudo constant
*/
Object readResolve() throws ObjectStreamException
{
return PseudoConstant.getRegistered(signature);
}
}
In most contexts this should be a static
inner class of PseudoConstant
, to not pollute the
public name space. But in my case it was preferable this way because I had different
types of pseudo-constants which could all use the same proxy.