Saturday, January 1, 2011

How to serialize and de-serialize CGLIB proxy objects across JVM

Most of us know how to serialize and de-serialize normal Java objects. Make the class implement the marker interface Serializable and invoke the writeObject/readObject methods, and it just works. It is pretty easy and straight forward. Even if serialization and de-serialization happens in two different JVMs, for normal java objects it is easy, because as long as you have the class definition available in both the JVM, it just works fine, without any additional work.

But the situation is a bit different for CGLIB proxy objects. Serializing and de-serializing CGLIB proxy objects across JVMs must be done in a different way. The main challenge with CGLIB proxy objects are that the proxy class itself is created during runtime, and therefore, even if we serialize it perfectly from one JVM, another JVM has no clue what the class is and therefore, the de-serialization will fail. Most people using Spring AOP in a clustered server environment might have come across the issue where CGLIB proxy objects which are created at runtime in one node could not be re-loaded in another node during a failover.

We will discuss one method using which we can successfully recreate CGLIB proxy objects across different JVM. We are assuming that the class being proxied and the logic to create the proxy object are available in both JVMs.

Let us take this case. We have a java class A. A CGLIB proxy class and object, pA, is created during runtime. Our requirement is to recreate the proxy which is in JVM1 inside JVM2.

Since we know it is not possible to serialize the proxy as-is and de-serialize it. So we need to modify the serialization-deserialization process a bit. We will do it by overriding the in-built functions of the Object class writeReplace and readResolve.

The writeReplace method allows a class of an object to nominate its own replacement in the stream before the object is written. By implementing the writeReplace method, a class can directly control the types and instances of its own instances being serialized.

The signature is as below
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

The readResolve method does just the same thing, but when the object is de-serialized.
The signature is as below
      ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;


In our case, our approach is to serialize the data in the proxy object and hold it in a different class (basically removing the proxy wrapper) and send the new class to JVM2. At JVM2, the new class will be de-serialized and the logic to create the proxy will be re-invoked to re-create the proxy.

So here is what we do

Step -1
Create two new classes, one which is base class for all proxiable objects and one which will act as a holder for the serialized object. The DataHolder class will have a byte[] to hold the serialized object. The constructor will accept any object and serialize the object and hold it. The readResolve method will be implemented to convert the byte[] to the object and will invoke the necessary logic to wrap the proxy again.

public class BaseProxiableClass implements Serializable{
    private static final long serialVersionUID = 1L;
}

public class DataHolder implements Serializable {

    private static final long serialVersionUID = 1L;
    private byte[] data;

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
   

    public DataHolder (Serializable serItem) {
        super();
        try {           
            //create a byte array output stream
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(serItem);
            setData(baos.toByteArray());
            baos.close();
            oos.close();           
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object readResolve() throws ObjectStreamException {
         BaseProxiableClass  returnValue = null;
        try {      
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais);
            returnValue = ( BaseProxiableClass ) ois.readObject();
            bais.close();
            ois.close();
            returnValue =  <have the logic to wrap the object in proxy here>
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return returnValue;
    }
}




Step – 2
Modify the class A to implement a writeReplace method

public class A extends BaseProxiableClass implements Serializable{
    private String value = “”;
    public String getValue(){
      return value;
    }
    public void setValue(String value){
      this.value = value;
    }
    private static final long serialVersionUID = 1L;
    /**
     * This method will be invoked whenever a serialization call happen on the
     * CGLIB wrapper. Returning "this" might seem weird. But see it in the context of
     * proxy. When the method is invoked on the proxy, it delegates the call to the
     * actual object, which in-turn returns the real object back. nice way to remove the
     * wrapper
     */
    public Object writeReplace() throws ObjectStreamException {
        return new DataHolder(this);
    }
}


Step – 3
Perform the serialization on pA (the proxy object) and send it to the JVM2. It should successfully return a proxy in JVM2 with the same data. Here are the classes which I used to test the same.

public class TestSerialization {

    public static void main(String[] args) {
       
        A a = new A();
        a.setValue(“Test Data”);
        A pA = <logic for creating CGLIB proxy>
        serializeObject(pA);
    }

    private static void serializeObject(Object obj) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;

        try {

            fos = new FileOutputStream(“c:\serObj.ser”, false);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(obj);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (oos != null)
                    oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}


public class TestDeSerialization {

    public static void main(String[] args) {
       
        A pA = deSerializeObject();
        pA.getValue(); //Should return “Test Data”
    }

    private static Object deSerializeObject() {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        Object returnObject = null;
        try {

            fis = new FileInputStream(“c:\serObj.ser”);
            ois = new ObjectInputStream(fis);
            returnObject = ois.readObject();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (ois != null)
                    ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return returnObject;

    }
   
}

Blog Archive