Wednesday 4 July 2012

Customize Default Serialization Protocol

Serialization is the process of saving an object state in a storage medium (such as a file, or a memory buffer) or to transmit it over a network connection in binary form.The object can be restored (Deserialization) at a later time, and even a later location. With persistence, we can move an object from one computer to another, and have it maintain its state.

In this article we are am going to discuss about how to customize serialization protocol.

In general there are three approaches to serialization in java:


  • Implement Serializable and use default protocol.
  • Implement Serializable and get a chance to modify the default protocol.
  • Implement Externalizable and write your own protocol to implement serialization.

The default serialization mechanism



The object to be persisted must implement the marker interface java.io.Serializable or inherit that implementation from its object hierarchy.The serialization interface has no methods or fields and serves only to inform the compiler that this java class can be serialized.Classes that do not implement this interface will not have any of their state serialized or deserialized.All subtypes of a serializable class are themselves serializable.

The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields. References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written. 

We can use writeObject(Object obj) method of java.io.ObjectOutputStream class for writing graph of serializable objects to a byte stream.

Simlilary the readObject() method of java.io.ObjectInputStream class can be used to recover those objects previously serialized.

Default Serialization Example


In this example a serializable Employee object is saved and restored using the default serialization mechanism.


package serialize;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class DefaultSerializeExample {

 /**

  * @param args

  * @throws ClassNotFoundException

  **/

 public static void main(String[] args) throws ClassNotFoundException {

  ObjectOutputStream out = null;
  ObjectInputStream in = null;

  try {

   out = new ObjectOutputStream(new FileOutputStream("employee.ser"));

   /** Create one Employee object to serialise **/

   Employee emp = new Employee(12345, "Banking");

   /** Serialise the object emp and write to file employee.ser **/

   out.writeObject(emp);

   in = new ObjectInputStream(new FileInputStream("employee.ser"));

   /**Deserialise the object stored in the file employee.ver **/

   Employee emp_copy = (Employee) in.readObject();

   System.out.println(emp_copy);

  } catch (IOException e) {

   e.printStackTrace();

  } finally {

   try {

    out.close();

    in.close();

   } catch (IOException e) {

    e.printStackTrace();

   }

  }

 }

}

/** Serializable class **/

class Employee implements Serializable {

 /**
  * Generated serialVersionUID for versioning of serialised objects.
  * 
  **/

 private static final long serialVersionUID = -417056492237332874L;

 int empno;
 String dept;

 public Employee(int empno, String dept) {

  this.empno = empno;
  this.dept = dept;
 }

 public Employee() {

 }

 /** Override the toString method to print object fields **/

 public String toString() {

  return (" Employee No =" + empno + " & Department =" + dept);

 }

}

The output will be

 Employee No =12345 & Department =Banking


Customize Default Serialization Protocol


Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException

 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;


Notice that both methods are declared private and ofcourse they must be declared private, proving that neither method is inherited and overridden or overloaded. The virtual machine will automatically check to see if either method is declared during serialization and deserialization.If available the custom readObject(ObjectInputStream) and writeObject(ObjectOutputStream) will be used else use the default serialization mechanism. The virtual machine can call private methods of your class whenever it wants but no other objects can.


You can write your custom code inside these two methods and customize the standard behaviour of serialization.


private void writwObject(ObjectInputStream in)
             throws IOException{


  in.defaultWriteObject();      //Serialize the component in the usual way.


  /* Write your custom code here */
}


This method calls the defaultWriteObject() method of the ObjectOutputStream to serialize the object as normal, and then takes care of the post-processing it needs to perform.Similarly



private void readObject(ObjectInputStream in)
             throws IOException, ClassNotFoundException {


  in.defaultReadObject();      // Deserialize the component in the usual way.


  /* Write your custom code here */
}

This method calls the defaultReadObject() method of the ObjectInputStream to deserialize the object as normal, and then takes care of the post-processing it needs to perform.

Let us see one example:

Custom Serialization Example:


In this example the serializable Employee class inheriting a non-serializable class Person.In the default serialization mechanism only the fields of Serializable objects are written out to the stream. You must customize the serialization of your subclass with writeObject(ObjectOutputStream) and readObject(ObjectInputStream) in order to write out and restore any information from the non-serializable superclass that you find necessary.
In this example we are declaring writeObject and readObject methods inside the serializable sub class Employee for explicitly saving and restoring the non-serializable parent class field 'empName' .

package serialize;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class CustomSerialization {

 /**
  * @param args
  * @throws ClassNotFoundException
  **/
 public static void main(String[] args) throws ClassNotFoundException {

  ObjectOutputStream out = null;
  ObjectInputStream in = null;
  try {
   out = new ObjectOutputStream(new FileOutputStream("employee.ser"));

   /** Create one Employee object to serialise **/

   Employee emp = new Employee("John", 12345, "Banking");

   /** Serialise the object emp and write to file employee.ser **/

   out.writeObject(emp);

   in = new ObjectInputStream(new FileInputStream("employee.ser"));

   /** Deserialise the object stored in the file employee.ver **/

   Employee emp_copy = (Employee) in.readObject();

   System.out.println(emp_copy);

  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    out.close();
    in.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

}

/** Non-serializable parent class **/

class Person {

 String empName;

 /**
  * mandatory public non-arg constructor. It will be invoked during
  * deserialization process.
  **/
 public Person() {
  System.out.println("Deserialization:Invoking non-arg Person() constructor");
 }

 public Person(String empName) {
  this.empName = empName;
 }

 /** Override the toString method to print object fields **/

 public String toString() {
  return ("Employee Name =" + empName);
 }
}

/** Serializable sub class **/

class Employee extends Person implements Serializable {

 /**
  * Generated serialVersionUID for versioning of serialised objects.
  * 
  **/
 private static final long serialVersionUID = -417056492237332874L;

 int empno;
 String dept;

 public Employee(String empName, int empno, String dept) {
  super(empName);
  this.empno = empno;
  this.dept = dept;
 }

 public Employee() {

 }

 /** Override the toString method to print object fields **/

 public String toString() {
  return ("Employee Name =" + empName + "Employee No =" + empno
    + " & Department =" + dept);
 }

 /**
  * Declared to customize the deserialization process. The nonserialized
  * super class field 'empName' will be restored explicitly.
  **/

 private void readObject(ObjectInputStream in) throws IOException,
   ClassNotFoundException {

  // Restore the sub class fields.
  in.defaultReadObject();

  System.out
    .println("Deserialization: Restoring Non-Serializable parent class field explicitly.");
  
  // explicitly do the restoring for super class field.
  empName = in.readUTF();

 }

 /**
  * Declared to customize the serialization process. The nonserialized super
  * class field 'empName' will be saved explicitly.
  **/

 private void writeObject(ObjectOutputStream out) throws IOException {

  // Save the sub class fields.
  out.defaultWriteObject();

  System.out
    .println("Serialization: Saving Non-Serializable parent class field explicitly.");

  // explicitly do the saving for super class field.
  out.writeUTF(empName);
 }

}

The output will be

Serialization: Saving Non-Serializable parent class field explicitly.
Deserialization: Invoking non-arg Person() constructor 
Deserialization: Restoring Non-Serializable parent class field explicitly.
Employee Name =John Employee No =12345 & Department =Banking

You can see that value of the non-serializable parent class field 'empName' saved and restored successfully.


Create Your Own Protocol: the Externalizable Interface


The third option for serialization is create your own protocol with the Externalizable interface.To learn more about externalization please visit Externalization in Java.

No comments:

Post a Comment