Monday, February 3, 2014

ObjectStreamClass: Peeking at a Java Object's Serialization

ObjectStreamClass can be a useful class to analyze the serialization characteristics of a serialized class loaded in the JVM. This post looks at some of the information this class provides about a loaded serialized class.

ObjectStreamClass provides two static methods for lookup of a class: lookup(class) and lookupAny(Class). The first, lookup(Class), will only return an instance of ObjectStreamClass when the provided class is serializable and returns null if the provided class is not serializable. The second, lookupAny(Class) returns an instance of ObjectStreamClass for the provided class regardless of whether it's serializable or not.

Once an instance of ObjectStreamClass is provided via the static "lookup" methods, that instance can be queried for class name, for serial version UID, and for serializable fields.

To demonstrate use of ObjectStreamClass, I first list the code listings for two simple classes that will be part of the demonstration. One class, Person, is Serializable, but has a transient field. The other class, UnserializablePerson, is nearly identical, but it is not Serializable.

Person.java
package dustin.examples.serialization;

import java.io.Serializable;

/**
 * Person class intended for demonstration of ObjectStreamClass.
 * 
 * @author Dustin
 */
public class Person implements Serializable
{
   private final String lastName;
   private final String firstName;
   transient private final String fullName;

   public Person(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.fullName = this.firstName + " " + this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFullName()
   {
      return this.fullName;
   }

   @Override
   public String toString()
   {
      return this.fullName;
   }
}
UnserializablePerson.java
package dustin.examples.serialization;

/**
 * Person class intended for demonstration of ObjectStreamClass.
 * 
 * @author Dustin
 */
public class UnserializablePerson
{
   private final String lastName;
   private final String firstName;
   private final String fullName;

   public UnserializablePerson(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.fullName = this.firstName + " " + this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFullName()
   {
      return this.fullName;
   }

   @Override
   public String toString()
   {
      return this.fullName;
   }
}

With two classes in place to run use in conjunction with ObjectStreamClass, it's now time to look at a simple demonstration application that shows use of ObjectStreamClass.

ObjectStreamClassDemo.java
package dustin.examples.serialization;

import static java.lang.System.out;

import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;

/**
 * Demonstrates use of ObjectStreamDemo.
 * 
 * @author Dustin
 */
public class ObjectStreamClassDemo
{
   /**
    * Displays class name, serial version UID, and serializable fields as
    * indicated by the provided instance of ObjectStreamClass.
    * 
    * @param serializedClass 
    */
   public static void displaySerializedClassInformation(
      final ObjectStreamClass serializedClass)
   {
      final String serializedClassName = serializedClass.getName();
      out.println("Class Name: " + serializedClassName);
      final long serializedVersionUid = serializedClass.getSerialVersionUID();
      out.println("serialversionuid: " + serializedVersionUid);
      final ObjectStreamField[] fields = serializedClass.getFields();
      out.println("Serialized Fields:");
      for (final ObjectStreamField field : fields)
      {
         out.println("\t" + field.getTypeString() + " " + field.getName());
      }
   }

   /**
    * Main function that demonstrates use of ObjectStreamDemo.
    * 
    * @param arguments Command line arguments; none expected.
    */
   public static void main(String[] arguments)
   {
      // Example 1: ObjectStreamClass.lookup(Class) on a Serializable class
      out.println("\n=== ObjectStreamClass.lookup(Serializable) ===");
      final ObjectStreamClass serializedClass = ObjectStreamClass.lookup(Person.class);
      displaySerializedClassInformation(serializedClass);

      // Example 2: ObjectStreamClass.lookup(Class) on a class that is not
      //            Serializable (which will result in a NullPointerException
      //            when trying to access null returned from 'lookup'
      out.println("\n=== ObjectStreamClass.lookup(Unserializable) ===");
      try
      {
         final ObjectStreamClass unserializedClass =
            ObjectStreamClass.lookup(UnserializablePerson.class);
         displaySerializedClassInformation(unserializedClass);
      }
      catch (NullPointerException npe)
      {
         out.println("NullPointerException: Unable to lookup unserializable class with ObjectStreamClass.lookup.");
      }

      // Example 3: ObjectStreamClass.lookupAny(Class) works without the
      //            NullPointerException, but only provides name of the class as
      //            Serial Version UID and serialized fields do not apply in the
      //            case of a class that is not serializable.
      out.println("\n=== ObjectStreamClass.lookupAny(Unserializable) ===");
      final ObjectStreamClass unserializedClass =
          ObjectStreamClass.lookupAny(UnserializablePerson.class);
      displaySerializedClassInformation(unserializedClass);
   }
}

The comments in the source code above indicate what is being demonstrated. The output from running this class is shown in the next screen snapshot.

When the output shown above is correlated with the code before it, we can make several observations related to ObjectStreamClass. These include the fact that the transient field of a serializable class is not returned as one of the serializable fields. We also see that ObjectStreamClass.lookup(Class) method returns null if the class provided to it is not serializable. ObjectStreamClass.lookupAny(Class) returns an instance of ObjectStreamClass for classes that are not serializable, but only the class's name is available in that case.

The code above showed a Serial Version UID for Person.java of 1940442894442614965. When serialver is run on the command line, the same Serial Version UID is generated and displayed.

What's nice about the ability to programatically calculate the same Serial Version UID as would be calculated by the serialver tool that comes with the Oracle JDK is that one could explicitly add the same Serial Version UID to generated code as would be implicitly added anyway. Any JVM-friendly script or tool (such as one written in Groovy) that needs to know the implicit Serial Version UID of a class could use ObjectStreamClass to obtain that Serial Version UID.

No comments: