Vous êtes sur la page 1sur 6

Diigo Web Highlighter (v1.6.3.

5) Highlight
Sun Java Solaris

Feedback Sticky Note


My SDN Account

Bookmark
Communities

Share

SDN Home > Products & Technologies > Java Technology > Reference > Technical Articles and Tips > Developer Technical Articles & Tips > Advanced Language Topics >

Article

Advanced Serialization
Print-friendly Version

Articles Index

By John Zukowski August 2001


Most articles about serialization cover the basics -- essentially, what it is and how to perform the bare minimum. In actuality, the object serialization capabilities are much richer. In this article, you'll get a review of the basics of serialization, followed by explanations of validating serialized streams, manipulating serializable fields through ObjectStreamField, and encrypting object streams.

What is Serialization?
The basic concept of ob ject serialization is the ability to read and write objects to byte streams. These streams can be used for saving session state information by servlets, for sending parameters for Remote Method Invocation (RMI) calls, for saving state information about JavaBean technology components, and for sending objects over the network, as well as many other tasks. To do object serialization you use an ObjectOutputStream to save your objects and an ObjectInputStream to read them back. The serialization process deals with flattening out an object tree such that all the raw data that make up an object, including all the objects referenced by that object, get saved. And, when it's time to read back an object, the original object tree graph gets recreated. Special cases like circular references and multiple references to a single object are preserved such that when the tree graph gets recreated new objects don't magically appear where a reference to another object in the tree should be. In order for an object to support the serialization process, the class must implement the Serializable interface. There are no methods in the interface; the interface just serves as a marker to say that a class can be serialized. However, just adding implements Serializable to a class definition doesn't automatically make a class Serializable. All the instance variables of said class must be Serializable, also. If they aren't and you try to serialize the class an exception would be thrown. To mark an instance variable as not for serialization, you add the transient keyword to the definition. Classes like java.awt.Image and java.lang.Thread, which contain platform-specific implementation information, are not Serializable and should be marked as transient.

transient Image image;

When you serialize an instance of a class, the only things that are saved are the non-static and non-transient instance data. Class definitions are not saved. They must be available when you try to deserialize an object. The basic process of serializing an object is as follows:

ObjectOutputStream oos = new ObjectOutputStream(anOutputStream); Serializable serializableObject = ... oos.writeObject(serializableObject);

Then, to read the the object back, deserialization, you do the reverse:

ObjectInputStream ois = new ObjectInputStream(anInputStream); Object serializableObject = ois.readObject();

The other thing that is frequently explained about serialization is overriding the readObject and writeObject methods to change the default serialization behavior. By overriding these methods you can provide additional information to the output stream, to be read back in at deserialization time:

private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // Write/save additional fields oos.writeObject(new java.util.Date());

} // assumes "static java.util.Date aDate;" declared private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); // Read/initialize additional fields aDate = (java.util.Date)ois.readObject(); }

One common reason to override readObject and writeObject is to serialize the data for a superclass that is not Serializable itself. And, this is where most serialization explanations end.

Validating Streams
When you serialize data to a file or send data across a socket there is no guarantee what you receive is what was written. In the case of saving to a file, a corrupt user can try to use a byte-level editor to change around the bytes to alter the values for when the object is restored. With serialization, a class can provide the ObjectInputStream parameter to readObject with an ObjectInputValidation interface implementer to indicate a desire to perform validation after an object is fully restored. The interface consists of a single method: public void validateObject() throws InvalidObjectException. When implemented, the class's readObject method must register the validator with the ObjectInputStream through registerValidation(ObjectInputValidation, int), the last argument of which is the validator priority, in case there are multiple (higher values are called first):

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ... ois.registerValidation(validator, 0); ... }

To demonstrate, the following class uses validation. If either of the two instance variables is the number 6 the read will fail. The test program, ValidationExample, gets the value for the two instance variables from the command line.

import java.io.*; public class ValidationExample implements Serializable, ObjectInputValidation { private int x, y; public static void main( String args[]) throws Exception { if (args.length != 2) { System.err.println( "Please pass in two numbers"); System.exit(-1); } // Initialize object ValidationExample ve = new ValidationExample(); try { ve.x = Integer.parseInt(args[0]); ve.y = Integer.parseInt(args[1]); } catch (NumberFormatException e) { System.err.println( "Please pass in two numbers"); System.exit(-1); } FileOutputStream fos = new FileOutputStream("val.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ve); oos.close(); try { FileInputStream fis = new FileInputStream("val.ser"); ObjectInputStream ois = new ObjectInputStream(fis); ValidationExample ve2 = (ValidationExample)ois.readObject(); ois.close(); System.out.println(ve2); } catch (InvalidObjectException invalid) { System.err.println(invalid.getMessage()); } } public String toString() { return getClass().getName( ) + "[x=" + x + ",y=" + y + "]";

} private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.registerValidation(this, 0); ois.defaultReadObject(); } public void validateObject() throws InvalidObjectException { if ((x == 6) || (y == 6)) { throw new InvalidObjectException( "6 is an invalid entry. Can't restore."); } } }

Using ObjectStreamField
There are two ways to define what fields get streamed when an object is serialized. By default, every non-static and non-transient field is preserved. However, if your class defines an array of ObjectStreamField objects named serialPersistentFields (that happens to be private, static, and final), then you can explicitly declare the specific fields saved. The order you place fields in the array is the order in which they are written. For instance, in the following class, only the username and counter fields are serialized, not the password.

public class MyClass implements Serializable { private String username; private int counter; private String password; private final static ObjectStreamField[] serialPersistentFields = { new ObjectStreamField( "username", String.class), new ObjectStreamField("counter", int.class) }; ... }

By default, no customization of readObject and writeObject is necessary when you provide a serialPersistentFields setting. Where serialPersistentFields becomes useful is in class evolution. For instance, imagine the original version of a class with two fields:

Point point; Dimension dimension;

Now, imagine version two of the class with only one field:

Rectangle rectangle;

If you want bidirectional serialization from either version of the class to either version of the class, you can create a serialPersistentFields in the second version to map the rectangle to the point and dimension:

private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("point", Point.class), new ObjectStreamField("dimension", Dimension.class) };

Then, in readObject and writeObject you have to do the actual mapping:

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { // Read version one types ObjectInputStream.GetField fields = ois.readFields(); Point point = (Point)fields.get("point", null); Dimension dimension = (Dimension)fields.get("dimension", null); // Convert to version two type rectangle = new Rectangle(point, dimension); }

private writeObject(ObjectOutputStream oos) throws IOException { // Convert to version one types ObjectOutputStream.PutFields fields = oos.putFields(); fields.put("point", rectangle.getLocation()); fields.put("dimension", rectangle.getSize()); // Write version one types oos.writeFields(); }

The version two class must also have the same serialVersionUID as the first version of the class. Just execute the serialver command on the original class version before making the change. For a complete example of an evolving class using serialPersistentFields, see Using Serialization and the Serializable Fields API.

Encrypting Serialized Objects


The Java Cryptography Extension (JCE) provides support for encryption. With regards to serialization, you can use JCE to either encrypt everything along a stream with a CipherOutputStream, or you can seal individual objects with a Cipher through the SealedObject class. Usually, you'd use CipherOutputStream and encrypt a whole stream. However, SealedObject allows you to wrap any Serializable object into a sealed one for later usage, perhaps to save in a servlet session. To demonstrate, the following example uses a CipherOutputStream to encrypt a series of objects written to disk, then uses a CipherInputStream to decrypt the file and read the objects back. The cipher streams act just like any other filtering streams: just add the stream between the file and the object streams. The hardest part of the code is creating the key for the encryption/decryption Cipher.

import import import import

java.io.*; javax.crypto.*; javax.crypto.spec.*; java.awt.*;

public class CipherExample { // Password must be at least 8 characters private static final String password = "zukowski"; public static void main(String args[] ) throws Exception { Point point = new Point(100, 200); Dimension dim = new Dimension(300, 400); Rectangle rect = new Rectangle(point, dim); // Create Key byte key[] = password.getBytes(); DESKeySpec desKeySpec = new DESKeySpec(key); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); // Create Cipher Cipher desCipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); desCipher.init(Cipher.ENCRYPT_MODE, secretKey); // Create stream FileOutputStream fos = new FileOutputStream("out.des"); BufferedOutputStream bos = new BufferedOutputStream(fos); CipherOutputStream cos = new CipherOutputStream(bos, desCipher); ObjectOutputStream oos = new ObjectOutputStream(cos); // Write objects oos.writeObject(point); oos.writeObject(dim); oos.writeObject(rect); oos.flush(); oos.close(); // Change cipher mode desCipher.init(Cipher.DECRYPT_MODE, secretKey); // Create stream FileInputStream fis = new FileInputStream("out.des"); BufferedInputStream bis = new BufferedInputStream(fis); CipherInputStream cis = new CipherInputStream(bis, desCipher); ObjectInputStream ois = new ObjectInputStream(cis);

// Read objects Point point2 = (Point)ois.readObject(); Dimension dim2 = (Dimension)ois.readObject(); Rectangle rect2 = (Rectangle)ois.readObject(); ois.close(); // Compare original with what was read back int count = 0; if (point.equals(point2)) { System.out.println("Points are okay."); count++; } if (dim.equals(dim2)) { System.out.println("Dimensions are okay."); count++; } if (rect.equals(rect2)) { System.out.println("Rectangles are okay."); count++; } if (count != 3) { System.out.println( "Problem during encryption/decryption"); } } }

The second form of encryption is sealing an object. This is done by calling the constructor for SealedObject, just passing the constructor the serializable object to seal and the Cipher to use for encryption:

SealedObject sealedObject = new SealedObject(serializable, cipher);

When it is time to unseal the object, there are three getObject methods you can use: getObject(Cipher c) getObject(Key key) getObject(Key key, String provider) Each of these unsealing methods works like the readObject method from ObjectInputStream -- you have to cast the object returned to the appropriate type. Which method you use depends upon the circumstances, though the second is probably the most frequently used, as it means the decrypter does not need to know the encryption parameters. The first version is also commonly used for when a Cipher object is shared within an application. Here's an example that seals and unseals an AWT Rectangle object.

import import import import

java.io.*; javax.crypto.*; javax.crypto.spec.*; java.awt.*;

public class SealedExample { // Password must be at least 8 characters private static final String password = "zukowski"; public static void main(String args[] ) throws Exception { Point point = new Point(100, 200); Dimension dim = new Dimension(300, 400); Rectangle rect = new Rectangle(point, dim); // Create Key byte key[] = password.getBytes(); DESKeySpec desKeySpec = new DESKeySpec(key); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); // Create Cipher Cipher desCipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); desCipher.init(Cipher.ENCRYPT_MODE, secretKey); // Seal object SealedObject sealedObject = new SealedObject(rect, desCipher); // Change cipher mode desCipher.init(Cipher.DECRYPT_MODE, secretKey); // Unseal object

Rectangle rect2 = (Rectangle)sealedObject.getObject(secretKey); // Just print each out System.out.println(rect); System.out.println(rect2); } }

Other Pointers
There is much more going on with serialization than explained here. The fact that the Java Object Serialization Specification hasn't changed since the 1.2 release of the Java 2 platform doesn't help the situation, with changes to serialization in both the 1.3 release and 1.4 release just increasing the enhancements. One thing that hasn't changed since the beginning though is turning serialization off. Sometimes, when you extend from a class that already extends Serializable, you just don't want your class to be serializable also. In those cases, all you have to do is throw NotSerializableException from your readObject and writeObject methods:

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { throw new NotSerializableException(); } private void writeObject(ObjectOutputStream ois) throws IOException { throw new NotSerializableException(); }

Conclusion
In this article you learned about some of the extra features available for object serialization. While you may not always need all these features, it is good to know and understand the options that are available so that, when necessary, the capabilities can be designed into your systems.

Resources
Java Cryptography Extension (JCE) Serialization Documentation Sun Object Serialization FAQ jGuru Object Serialization FAQ

About the Author


John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. His latest books are Java Collections and Definitive Guide to Swing for Java 2 (2nd ed) from Apress. Contact John at jaz@zukowski.net. Have a question about programming? Use Java Online Support.

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.

About Sun | About This Site | Newsletters | Contact Us | Employment | How to Buy | Licensing | Terms of Use | Privacy | Trademarks

A Sun Developer Network Site Unless otherwise licensed, code in all technical manuals herein (including articles, FAQs, samples) is provided under this License. Sun Developer RSS Feeds

2010, Oracle Corporation and/or its affiliates

Vous aimerez peut-être aussi