Validation
It is not always enough to merely restore the state of a serialized object. You may need to verify that the value of a field still makes sense, you may need to notify another object that this object has come into existence, or you may need to have the entire graph of the object available before you can finish initializing it.
Most obviously, you may need to check the class invariants on an object you deserialize. In Java, class invariants are normally enforced by explicit code in setters and constructors that checks method preconditions as well as testing to see that no internal code can violate the invariants given that the preconditions hold. Object deserialization bypasses this careful infrastructure completely. There's absolutely nothing to stop someone from reaching right into the serialized bytes of your Clock object and setting the time to 13:00.
Certainly, this would be a nasty thing to do, but it's possible. Some may object that these sorts of shenanigans are also enabled by the Reflection API, particularly through the setAccessible( ) method. However, at least setAccessible( ) only functions from code running inside your own VM. If you're reading a serialized object some other system has passed to you or left sitting around on the disk, you have no idea what might have been done to it or why. You need to be wary of accepting arbitrary serialized objects from untrusted sources.
For example, suppose an application maintains a map of Person objects, each of which is identified primarily by its social security number. Let's further suppose that the application doesn't allow two Person objects with the same social security number to exist at the same time. You can use an ObjectInputValidation to check each Person object as its deserialized to make sure it doesn't duplicate the social security number of a person already in the map.
The ObjectInputStream's registerValidation( ) method specifies the ObjectInputValidation object that will be notified of the object after its entire graph has been reconstructed but before readObject( ) has returned it. This gives the validator an opportunity to make sure that the object doesn't violate any implicit assertions about the state of the system.
public void registerValidation(ObjectInputValidation oiv, int priority) throws NotActiveException, InvalidObjectException
This method is invoked inside the readObject( ) method of the object that needs to be validated. Every time the readObject( ) method is called to read an object, that object is registered with the stream as needing to be validated when the rest of the graph is available. Invoking the registerValidation( ) method from anywhere except the readObject( ) method throws a NotActiveException. The oiv argument is the object that implements the ObjectInputValidation interface and that will validate deserialized objects. Most of the time, this is the object that has the readObject( ) method; that is, objects tend to validate themselves. The priority argument determines the order in which objects will be validated if there's more than one registered ObjectInputValidation object for the class. Validators with higher priorities are invoked first.
The ObjectInputValidation interface declares a single method, validateObject( ):
public abstract void validateObject( ) throws InvalidObjectException
If the object is invalid, validateObject( ) tHRows an InvalidObjectException.
Example 13-7 demonstrates with a class that implements the previously described scheme for avoiding duplicate social security numbers.
Example 13-7. Person
import java.util.*; import java.io.*; public class Person implements Serializable, ObjectInputValidation { static Map thePeople = new HashMap( ); private String name; private String ss; public Person(String name, String ss) { this.name = name; this.ss = ss; thePeople.put(ss, name); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.registerValidation(this, 5); in.defaultReadObject( ); } public void validateObject( ) throws InvalidObjectException { if (thePeople.containsKey(this.ss)) { throw new InvalidObjectException(this.name + " already exists"); } else { thePeople.put(this.ss, this.name); } } public String toString( ) { return this.name + " " + this.ss; } public static void main(String[] args) throws IOException, ClassNotFoundException { Person p1 = new Person("Rusty", "123-45-5678"); Person p2 = new Person("Beth", "321-45-5678"); Person p3 = new Person("David", "453-45-5678"); Person p4 = new Person("David", "453-45-5678"); Iterator iterator = thePeople.values().iterator( ); while (iterator.hasNext( )) { System.out.println(iterator.next( )); } ByteArrayOutputStream bout = new ByteArrayOutputStream( ); ObjectOutputStream oout = new ObjectOutputStream(bout); oout.writeObject(p1); oout.writeObject(p2); oout.writeObject(p3); oout.writeObject(p4); oout.flush( ); oout.close( ); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray( )); ObjectInputStream oin = new ObjectInputStream(bin); try { System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); } catch (InvalidObjectException ex) { System.err.println(ex); } oin.close( ); // now empty the map and try again thePeople.clear( ); bin = new ByteArrayInputStream(bout.toByteArray( )); oin = new ObjectInputStream(bin); try { System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); System.out.println(oin.readObject( )); } catch (InvalidObjectException ex) { System.err.println(ex); } oin.close( ); iterator = thePeople.values().iterator( ); while (iterator.hasNext( )) { System.out.println(iterator.next( )); } } } |
Here's the output:
Beth Rusty David java.io.InvalidObjectException: Rusty already exists Rusty 123-45-5678 Beth 321-45-5678 David 453-45-5678 Beth Rusty David java.io.InvalidObjectException: David already exists