CSC/ECE 517 Fall 2009/wiki2 18 i7
Serialization Proxy Pattern
Problem
When designing a class, it is preferred to have them be immutable because of guaranteed thread safety and consistent object state. An immutable class is one where the state cannot be modified after it is instantiated.
A problem arises in Java when needing a class to be serializable. In order to have a class be serializable the Serializable interface must be implemented. A class that is serializable is one that can be represented on disk as a sequence of bits. The reason this is a problem is not on serialization, but rather on deserialization. During deserialization of a class, Java requires that the first non-serializable superclass have a no-argument constructor. Java uses this constructor to instantiate the object and populate the fields. This may be the case if the superclass is one that is immutable. The answer here in most cases would be to make the class and superclass mutable and provide a no argument constructor in the superclass. Serialization proxy pattern aims to solve this problem.
Design
The idea behind the serialization proxy pattern is simple. On serialization, instead of having to remove the immutability that exists in the superclass and the class that needs to be serialized, a proxy class is serialized instead of the target class. A proxy class in this context would be a class where enough information is stored in it to reconstruct the target class. On deserialization, instead of returning an instance of the proxy class, the target class is instantiated with the information provided by the proxy class and returned in place of it. The following diagram shows the design of what happens on serialization and deserialization.
Implementation Details
Serialization with writeReplace
During serialization there is a method that Java looks for which is called writeReplace. If writeReplace exists, the value returned from this method will be serialized in place of the current class. This is where we would return a proxy object which stores enough information to reconstruct what we actually want to serialize. An example of an implementation of writeReplace can be identified in the following code snippet. In brief, there is a simple class called MyClass which holds a single integer value and there is an inner class SerializationProxy. The SerializationProxy class used as the serialization proxy object in the writeReplace method when serializing instances of MyClass.
public class MyClass implements Serializable { private final int value; public MyClass(int value) { this.value = value; } public int getValue() { return this.value; } private Object writeReplace() { return new SerializationProxy(this); } private static final class SerializationProxy implements Serializable { private final int value; public SerializationProxy(MyClass myClass) { this.value = myClass.getValue(); } } }
In this example, if an instance of MyClass was to be serialized Java would look to first see if there was a method implemented called writeReplace as shown in the example. If this method does exist, the return value of this method would be serialized instead. In this example, a new instance of SerializationProxy would first be created and constructed with the integer parameter from MyClass and that object would be serialized.
Deserialization with readResolve
During deserialization there is a method that Java looks for which is called readResolve. If readResolve exists, the value returned from this method will instead be the result of the deserialization of this object. This method would be implemented in the proxy class and the constructor for the class we want to reconstruct would be invoked and returned as the value returned by readResolve. An example implementation of readResolve would be the following which would be added inside of the SerializationProxy class from the previous example.
private Object readResolve() { return new MyClass(this.value); }
In this example, if an object is deserialized, Java looks for a method called readResolve. If readResolve exists, the return value of readResolve is returned instead of this object. In this example, the SerializationProxy would first be deserialized and then Java will look for and find readResolve. A new instance of MyClass would then be created with the integer parameter from the SerializationProxy and the instance of MyClass would be returned as the result of the deserialization.
Idiomatic Usage
The idiom for implementing serialization proxy pattern is using an inner class that is declared as private, static, and final. The reason the class is declared private is because it should not be visible to anyone other then the enclosing class, static is because it should not share any instance variables with the parent, and final because this class should never be extended. Additionally since the class is hidden because it is an inner class, the serialization proxy used will be transparent to all users of this class regarding serialization. This is a good thing because we don't want users of this class to have to deal with the serialization proxy. An example of this structure is the following.
public class MyClass implements Serializable { ... private static final SerializationProxy implements Serializable { ... } }
Conclusion
Using the serialization proxy pattern brings a two important benefits to the table when dealing with serialization of immutable classes. First, serialization is made possible for classes where a no argument constructor in the superclass would be needed without losing immutability of classes that may already be in place. Second, the usage of serialization proxy pattern is completely transparent to the clients of the class, they do not ever need to know that there is a serialization proxy.
References
2. jGuru - Are classes that implement Serializable required to have no-argument constructors?