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;

    }
   
}

3 comments:

  1. I keep re-reading this, but I cannot see the connection between step 1, the creation of the class DataHolder, and its part in steps 2 and/or 3. It would help if there was some reference to DataHolder in some of the sample code for steps 2 and 3.

    ReplyDelete
    Replies
    1. I am extremeley sorry for this miss out. I have updated the code to use the DataHolder. The idea is when the the class gets serialized, on the call to writeReplace, we remove the wrapper and pass a DataHolder.
      When we deserialize, if we find the class to be DataHolder, we rewire the proxy again.

      Delete
  2. Hello, this is a few years later but I am trying to make this work... for me, the oos.writeObject in TestSerialization - serializeObject causes endless recursion, because it calls writeObject, which calls writeReplace in the object, which creates a new DataHolder, which calls writeObject, which calls writeReplace, which creates a new DataHolder, etc.

    Am I missing something?

    Thank you

    ReplyDelete

Blog Archive