Serialization/Externalization - SLATE

computerharpySoftware and s/w Development

Dec 2, 2013 (3 years and 4 months ago)

54 views



Serialization

Topics


What is Serialization


Basics of Serialization


The
transient

keyword


Changing the default reading and writing


Security Issues


Versioning


Basic Idea


Save everything to a stream



java.io

defines two serialization streams


ObjectOutputStream

for saving


ObjectInputStream

for restoring



Create the stream and tell it “save this object”
using a standard API


Serialization


Allows reading and writing of whole objects to
any
input

or
output

stream



Why serialization?


Persistence

Storing objects in files and databases


Marshalling

Passing whole objects between VMs over
the network

Serializable Objects


Implement the
java.io.Serializable

interface


Serializable

doesn’t define any method (it’s a
marker interface)



Must have a zero
-
argument (default) constructor


Otherwise,
deserialization

won’t work


Writing an Object


Create an
ObjectOutputStream

from any
output stream



Use
writeObject(Object obj)




Handle


IOException
,


NotSerializableException
, and


InvalidClassException


Reading an Object


Create an
ObjectInputStream

from any input
stream


Use its
Object readObject()


Returned type must be downcast to actual type


Handle


IOException,

and


ClassNotFoundException

Example

import java.io.*;


public class Person implements Serializable {


public String firstName;


public String lastName;


private String password;


transient Thread worker;



public Person(String firstName, String lastName,


String password) {


this.firstName = firstName;


this.lastName = lastName;


this.password = password;


}



public String toString() {


return new String( lastName + ", " + firstName);


}

}


Example of Writing

class WritePerson {


public static void main(String [] args)



throws IOException{


Person p = new Person("Fred", "Wesley",








"cantguessthis");


ObjectOutputStream oos = null;


try {


oos = new ObjectOutputStream(


new FileOutputStream("Person.ser"));


oos.writeObject(p);


oos.flush();


} catch (Exception e) {


e.printStackTrace();


} finally {


if (oos != null)




oos.close();


}

}

Example of Reading

class ReadPerson {


public static void main(String [] args)



throws IOException {


ObjectInputStream ois = null;


try {



ois = new ObjectInputStream(


new FileInputStream("Person.ser"));



Object o = ois.readObject();



System.out.println("Read object " + o);


} catch (Exception e) {


e.printStackTrace();


} finally {


if (ois != null) {



ois.close();


}


}

}

The Standard Mechanism


ObjectOutputStream

is responsible for
tracking whether an object has already been
serialized



If it has, simply write out a handle


Otherwise, write out the object



Either the object is default serializable or it
requires special handling


Default Serialization


In most cases, serializing an instance consists of
saving variable values


If an object is default serializable, the
serialization mechanism will use reflection to:


get all the fields


get their values


save them to the stream


recursing when the value is an object


Things to watch out for


An object is default
-
serializable iff all the non
-
static, non
-
transient data members are
serializable


An object can easily inherit “serializability”


If it has a non
-
serializable data member, you need to
be careful


Anonymous inner classes can’t be serialized

Serialization & Class Versioning

import java.io.*;


public class Person implements Serializable {


public String firstName;


public String lastName;


public int age;


private String password;


transient Thread worker;



public Person(String firstName, String lastName,


int age, String password) {


this.firstName = firstName;


this.lastName = lastName;


this.age = age;


this.password = password;


}



public String toString() {


return new String( lastName + “, “ + firstName + “, ” + age);


}

}


Serialization & Class Versioning

class ReadPerson {


...


public static void main(String [] args)






throws IOException {


ObjectInputStream ois = null;


try {



ois = new ObjectInputStream(




new FileInputStream(

"Person.ser"));



Object o = ois.readObject();



System.out.println("Read object " + o);


} catch (Exception e) {


e.printStackTrace();


} finally {


if (ois != null) {




ois.close();



}


}


}

}

Serialization & Class Versioning


64
-
bit fingerprint (
serialVersionUID
) for the
class is calculated


The
serialVersionUID

is based on all the
serializable fields of a class


If the class changes in any significant way, the
serialVersionUID

changes too


When object is read from an object stream,
system ensures that version UIDs of
ObjectStreamClass and local class are the same


If not,
java.io.InvalidClassException

exception is thrown

Setting Class Version


Add following line to the class:

static final long serialVersionUID = /*some long integer*/;



Java serialization will use that ID, instead of calculating
one for you



The
serialver

command
-
line tool in JDK 1.2 lets you
extract a serialVersionUID from an existing class



Recompile the original class (Person), and issue the
command:

% serialver <classname>

% serialver Person

static final long serialVersionUID=4070409649129120458L;


Add this entire line to the new version of your class
(Person), and recompile

Why Class Versioning?


It prevents nasty bugs that might appear if two
versions of a class were truly incompatible in
some way




your code is aware that the some data (for
example
age
) value might not be set correctly
when loading (
Person
) in its original format


Customizing Serialization


You can customize serialization for your classes
by providing two methods for it:

private void writeObject(ObjectOutputStream out)


throws IOException

private void readObject(ObjectInputStream in)


throws IOException, ClassNotFoundException



The
writeObject
method controls what
information is saved

private void writeObject(ObjectOutputStream s)


throws IOException {


s.defaultWriteObject();


//
customized serialization code


}



Customizing Serialization


The
readObject

method either reads the information written by
the corresponding
writeObject

method or can be used to update
the state of the object after it has been restored

private void readObject(ObjectInputStream s)


throws IOException {


s.defaultReadObject();


//
customized deserialization code



...


//
followed by code to update the object,


// if necessary


}



The
writeObject

and
readObject

methods are responsible for
serializing only the immediate class. Any serialization required by
the superclasses is handled automatically

Example

public class Person implements Serializable {


public String fullName;


int age;


private String password;


transient Thread worker;



static final long serialVersionUID =






4070409649129120458L;



static String crypt(String input, int offset) {


StringBuffer sb = new StringBuffer();


for (int n=0; n < input.length(); n++) {


sb.append((char) (offset+input.charAt(n)));


}


return sb.toString();


}

Example Contd.


private void writeObject(ObjectOutputStream os)


throws IOException {



password = crypt(password, 3);


System.out.println(




"Password encyrpted as " + password);


os.defaultWriteObject();


password = crypt(password,
-
3);


}



private void readObject(ObjectInputStream stream)


throws IOException, ClassNotFoundException {



stream.defaultReadObject();


password = crypt(password,
-
3);


System.out.println(




"Password decrypted to " + password);


}

Example contd.


// Constructor


public Person(String firstName,





String lastName,


String password,





int age) {


this.fullName = lastName + ", " + firstName;


this.password = password;


this.age = age;


}



public String toString() {


return new String(“name:” + fullName +”age:“ +











age);


}

} // end Person class

Problem with the Code?


The new class has no field names that match
the
lastName

and
firstName

fields in the
Person.ser

file, so these fields are ignored



Conversely, the fullName field does not exist in
the
Person.ser

file, so a correct
fullName

value isn't materialized



The problem is with default
readObject
, which
tries to match stream fields by name to fields in
the class

Example

//Replace readObject with this new version:

private void readObject( ObjectInputStream ois)


throws IOException, ClassNotFoundException {


ois.defaultReadObject();


ObjectInputStream.GetField gf = ois.readFields();


fullName = (String) gf.get( "fullName", null);



if (fullName == null) {


//Uh
-
oh Old version. Calculate fullName:


String last = (String) gf.get ("lastName", null);


String first = (String) gf.get ("firstName", null);


fullName = lastName + ", " + firstName;


}


age = gf.get ("age", 0);


password = (String) gf.get ("password", null);


password = crypt (password,
-
3);


System.out.println ("Password decrypted to " +
password);

}

Conclusion


The final
Person

class does a lot more work than the
original, which simply implemented the
Serializable

interface



The final version specifies a
serialVersionUID
,
manages the state of the
password

field, and names
the fields you want to read



This additional work is the price you pay for a major
benefit:
the ability to evolve your code over time



With these techniques, your persistent classes become
backwards compatible, that is, they add new capabilities
without losing capabilites they already had

Externalization of Objects


JDK also has the
java.io.Externalizable

class.


Externalization is different from serialization.


Th
Externalizable

interface extends
Serializable

and adds two methods one must
implement:

public abstract void
readExternal(ObjectInput in) throws
IOException, ClassNotFoundException


public abstract void
writeExternal(ObjectOutput out) throws
IOException

Externalized Objects
--
More


Externalized Objects are similar to serialized
objects, except that the objects themselves
control the field creation during reading and
writing.


The
readExternal

and
writeExternal

are called with
readObject()

and
writeObject()


This produces greater security, and offers the
possibility of selective serialization


Constructors for externalized objects must be
public

class Data implements Externalizable {

inti;

String s;

public Data() {

System.out.println("Data default constructor");

}

public Data(String x, int a) {

System.out.println("Second constructor");

s = x; i = a;

}

public String toString() {

return s + i;

}

public void writeExternal(ObjectOutput out)

throws IOException {

out.writeObject(s);

out.writeInt(i);

}



public void readExternal(ObjectInput in) {

s = (String)in.readObject();

i = in.readInt();

}

public static void main(String[] args)

throws IOException, ClassNotFoundException {

Data d = new Data("String value",1514);

System.out.println(d);

ObjectOutputStream o = new ObjectOutputStream(

New FileOutputStream("data.out"));

o.writeObject(d);

o.close();


// Now deserialize

ObjectInputStream in = new ObjectInputStream(

new FileInputStream("data.out"));

d = (Data)in.readObject();

}

}


Serialization & the transient
State


There may be time when we want to serialize
an object, but omit certain key features, like
passwords or other sensitive data.


Even objects and data identified as private
get serialized. How does one keep data out
of externalized objects?


Solution: use the
transient

keyword:

private transient String password = “pssst.”;


Data and methods identified as
transient

are not serialized.