|
| |
The Externalizable Interface
If a class implements the Externalizable
interface, the ObjectInputStream
and ObjectOutputStream
classes use that object's readExternal()
and writeExternal()
methods to read and write its state during serialization.
The Externalizable
interface extends from Serializable:
package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
/**
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or
* calling the writeObject method of ObjectOutput for objects, strings
* and arrays.
* @exception IOException Includes any I/O exceptions that may occur
* @since JDK1.1
*/
void writeExternal(ObjectOutput out) throws IOException;
/**
* The object implements the readExternal method to restore its
* contents by calling the methods of DataInput for primitive
* types and readObject for objects, strings and arrays. The
* readExternal method must read the values in the same sequence
* and with the same types as were written by writeExternal.
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
* @since JDK1.1
*/
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
} |
By implementing Externalizable,
a class can control whether the state of superclasses is
stored in the stream, and exactly which fields are stored.
Only the identity of an Externalized object is automatically
saved by the stream; the class is responsible for writing and
reading its contents, and must coordinate with its
superclasses to do so.
Unlike Serializable,
the Externalizable
interface does not handle code versioning automatically --
you must provide your own.
Because the state of an Externalizable
object's superclasses can be indirectly manipulated by the writeExternal()
and readExternal()
methods, which are public,
you should use Externalizable
with extreme caution to avoid security problems.
The ObjectInputValidation
Interface
Sometimes you go to great pains to construct a set of
objects correctly, usually by putting the appropriate code in
the class constructor(s). However, because restoring a set of
Serializable
objects does not invoke their constructors, it is often
desireable to ensure that the reconstructed object is indeed
valid. The ObjectInputValidation
interface provides such capabilities.
Here is what it looks like:
package java.io;
/**
* Callback interface to allow validation of objects within a graph.
* Allows an object to be called when a complete graph of objects has
* been deserialized.
*/
public interface ObjectInputValidation {
/**
* Validates the object
* @exception InvalidObjectException If the object cannot validate itself.
* @since JDK1.1
*/
public void validateObject() throws InvalidObjectException;
} |
ObjectInputStream
uses a registration scheme to allow you to register an ObjectInputValidation
object during serialization in.
For example, if we take the RecordedPerson class we used
to do custom serialization earlier, and add to it support for
validation, we get the following class, with color-coded
changes:
package serialization;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.PrintWriter;
public class RecordedPerson
extends Person
implements ObjectInputValidation
{
// Constructor to create a recorded person and
// the associated record file
public RecordedPerson(String name, int age, int id)
{
super(name, age);
m_id = id;
createRecordFile();
}
// Constructor to create a recorded person
// and to open an already existing record file.
public RecordedPerson(int id)
{
super(null, -1);
m_id = id;
setRecordFile();
setBasicData();
}
// Reads the record from the record file
// and returns it as a string.
public String getRecord()
{
String data = "";
BufferedReader in = null;
try
{
in = new BufferedReader(
new FileReader(m_recordFile));
while(true)
{
String line = in.readLine();
if (line == null)
break;
data += line + "\n";
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch(IOException e)
{}
}
}
return data;
}
// Adds a new item to the record file for this person
public void addRecord(String item)
{
PrintWriter out = null;
try
{
out = new PrintWriter(
new FileWriter(
m_recordFile.getAbsolutePath(),
true));
out.println(item);
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if (out != null)
out.close();
}
}
// Creates a new record file with name and age record
// (If the record file already exists, assumes that it's valid)
private void createRecordFile()
{
setRecordFile();
if (!m_recordFile.exists())
{
// Create a new record file
PrintWriter out = null;
try
{
out = new PrintWriter(
new FileWriter(m_recordFile));
out.println(getName() + "," + getAge());
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if (out != null)
out.close();
}
}
else
{
// Assume for simplicity that the name and age
// actually correspond with the current ones.
}
}
// Sets the record File object
// If necessary, creates the subdirectory for record files.
private void setRecordFile()
{
File records = new File("records");
if (!records.exists())
records.mkdir(); // Create directory
m_recordFile = new File(records, "r" + m_id + ".rec");
}
// Reads the record file and extracts the name and age
// and sets those values for the current recorded person.
private void setBasicData()
{
// Extract the name and age from the record
String record = getRecord();
int commaIndex = record.indexOf(',');
int eolIndex = record.indexOf('\n');
String name = record.substring(0, commaIndex);
String age = record.substring(commaIndex+1, eolIndex);
// Set the name and age values
setName(name);
setAge(Integer.parseInt(age));
}
// Creates a string containing RecordedPerson information.
public String toString()
{
String s = super.toString();
s += ", ID=" + m_id + ", recordFile=" + m_recordFile;
return s;
}
private void readObject(ObjectInputStream in)
throws IOException,
ClassNotFoundException
{
in.registerValidation(this, 0);
in.defaultReadObject();
// Ensure that the recordFile is properly set
setRecordFile();
}
public void validateObject()
throws InvalidObjectException
{
if (getAge() > 40)
throw new InvalidObjectException("No persons over 40 allowed");
}
//// Data ////
private int m_id;
private transient File m_recordFile;
} |
Now, when we attempt to serialize this class back in, we
now get the following:
java.io.InvalidObjectException: No persons over 40 allowed
at serialization.RecordedPerson.validateObject(RecordedPerson.java:175)
at java.io.ObjectInputStream.doValidations(ObjectInputStream.java:548)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:437)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:225)
at serialization.RecordedSerializeIn.main(RecordedSerializeIn.java:18)
at symantec.tools.debug.MainThread.run(Agent.java:48)
Serialization failed. |