For JavaBeans, serialization provides a simple way to save the state of a bean between one session and another. This is also called component persistence. For example, suppose the user of a bean changes the values of several properties. If the bean is serialized to a file and then deserializing the next time the user uses it, the bean is restored exactly as the user left it.
When you serialize a bean, you create a file that has the name of the bean class with a .ser file extension. For example, a serialized bean named MyBean would have the file name of MyBean.ser.
The following topics explain how to serialize and deserialize a JavaBean:
Serializing a bean in the UI Designer
Explains how to serialize a component using the UI Designer.
Instantiating serializable JavaBeans
Describes how to get JBuilder to instantiate a bean using the Beans.instantiate() method and explains why this is desirable.
Serializing an object with code
Describes how to serialize an object using ObjectOutputStream.
Deserializing an object
Describes how to restore an object using ObjectInputStream.
Customizing serialization
and deserialization
Explains how to control serialization and deserialization by
writing your own routines.
Serializing
with the Externalizable interface
Briefly describes how to achieve complete control over the
serialization and deserialization processes.
Versioning
Explains how to maintain serialization compatibility when a class
definition changes slightly.
The dialog box suggests a file name, which is the name of the component with a .ser extension. Keep this name. Specify a directory that is on your class path.
The component is serialized to a file with the name you specified with a .ser file extension. The next time you instantiate that bean using Beans.instantiate(), the .ser file is read into memory.
Beans.instantiate() also performs additional housekeeping tasks that applets need.
You can customize JBuilder so that the code it generates uses Beans.instantiate() instead of new:
Now JBuilder generates code that uses the Beans.instantiate() method to instantiate a JavaBean whenever the bean is dropped on the UI Designer.
To make Beans.instantiate() the default way JBuilder instantiates a bean,
Beans.instantiate() can throw an exception, so it must exist within the jbInit() method where exceptions are expected and handled. Beans are still declared where they were before, but their initializers appear inside jbInit().
For example, here is how code might appear using the new keyword to instantiate an object:
public class ButtonBoxFrame extends JFrame { XYLayout xYLayout = new borland.jbcl.layout.XYLayout(); ButtonControl buttonControl1 = new ButtonControl; ButtonControl buttonControl1 = new ButtonControl; ButtonControl buttonControl1 = new ButtonControl; public ButtonBoxFrame() { try jbInit(); } catch (Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception{ ... }This is how the code appears using Beans.instantiate():
public class ButtonBoxFrame extends JFrame { XYLayout xYLayout1; ButtonControl buttonControl1; ButtonControl buttonControl2; ButtonControl buttonControl3; public ButtonBoxFrame() { try { jbInit(); ) catch (Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { xYLayout1 = (XYLayout) Beans.instantiate(getClass().getClassLoader(), XYLayout.class.getName()); buttonControl1 = (ButtonControl) Beans.instantiate(getClass().getClassLoader(), ButtonControl.class.getName()); buttonControl2 = (ButtonControl) Beans.instantiate(getClass().getClassLoader(), ButtonControl.class.getName()); buttonControl3 = (ButtonControl) Beans.instantiate(getClass().getClassLoader(), ButtonControl.class.getName()); ... }
All of components on the Component Palette implement the Serializable interface.
If you want to control the serialization process yourself in the components you write, or if you want to serialize components that don't extend JBuilder components, you must understand the details of serialization and deserialization. The rest of this chapter is for you.
To serialize an object that implements Serializable, pass the object to the writeObject() method of an ObjectOutputStream. writeObject() writes out all the values of all the fields in the object, including those that are inherited.
Objects can contain other objects, which in turn can contain other objects. Java serialization can handle objects nested within objects automatically. For primitive fields in the object, writeObject() writes the values directly to the stream. When an object field is encountered, however, writeObject() is called again, and if that object contains another object, writeObject() continues to be called recursively until all the objects are written to the stream. All the programmer need do is pass the object to the writeObject() method of ObjectOutputStream and the rest happens automatically.
The following example creates a PersonalData object called mine. The code writes a string and the mine object to a stream, storing them in a file:
public class PersonalData implements Serializable { public int id public int yearOfBirth; public float yearlySalary; } ... PersonalData mine = new PersonalData(101, 1956, 46500.00); FileOutputStream outstream = new FileOutputStream("PersonalData.ser"); ObjectOutputStream out = new ObjectOutputStream(outstream); out.writeObject("My personal data"); // writes a string to the stream out.writeObject(mine); // writes the object to the stream out.close(); // flushes and closes the stream ...
A FileOutputStream object is created and passed to an ObjectOutputStream. When out.writeObject() is called, the string and the mine objects are serialized into a sequence of bytes stored in the PersonalData.ser file.
The following code creates a FileInputStream object and passes it to an ObjectInputStream. Calling readObject() reads the string and the mine object from the file and restores them to their original form.
FileInputStream instream = new FileInputStream("PersonalData.ser"); ObjectInputStream in = new ObjectInputStream(instream); String s = (String)in.readObject(); // reads the string from the stream PersonalData mine = (PersonalData)in.readObject(); // deserializes the bean from the stream
Note that the ObjectInputStream is downcast when the readObject() call is made. Whenever you call readObject() and assign the result to a variable, you must downcast the resulting object.
Occasionally you might want more control over the serialization and deserialization process. This section explains how you can attain that goal.
To prevent a field from being serialized, add the transient keyword to the field's declaration. For example,
class wizzyComponent implements Serializable { private int height; private int width; private transient int mousePosX; // this field will not be serialized private transient int mousePosY; // this field will not be serialized ... }
Now when wizzyComponent is serialized, the mousePosX and mousePosY variables are excluded. To the resulting, serialized object, it's as if these two variables don't exist.
To use this approach, add writeObject() and readObject() methods to your class. Their signatures must appear exactly like this:
private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException;
The signatures of these methods are unusual because they are always declared as private, yet they are never called from the class in which they are declared. Also, they are not part of any interface, yet you must remember to copy the signatures exactly when you add the methods to your component.
Within the method you can activate the default serialization mechanism at any time by calling the defaultWriteObject() method of the ObjectOutputStream that is the argument of this writeObject() method. defaultWriteObject() serializes all the fields that are not marked as transient. You can perform additional processing before or after the call to defaultWriteObject(). For example, here is the writeObject() method of JBCL's ListCore component; it calls defaultWriteObject() immediately, but then creates a hash table and writes that hash table to the stream also:
private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); // write fields to stream Hashtable hash = new Hashtable(3); if (model instanceof Serializable) hash.put("mo", model); // NORES if (viewManager instanceof Serializable) hash.put("vm", viewManager); // NORES if (selection instanceof Serializable) hash.put("se", selection); // NORES s.writeObject(hash); // write hash table to stream } private transient VectorModel model; private transient WritableVectorModel writeModel; private transient VectorViewManger viewManager; private transient WritableVectorSelection selection; ...
The model, writeModel, viewManager, and selection properties are all declared as transient. Each of these are interfaces, so a custom serialization procedure is needed to write these properties to the stream. writeObject() first calls the defaultWriteObject() method of the ObjectOutputStream to write all the fields that aren't marked as transient to the stream. It continues by writing the value of the model, viewManager, and selection properties to a hash table if each of these implement the Serializable interface. Finally, the hash table is written to the stream by calling the writeObject() method of ObjectOutputStream.
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // read object from stream with default deserialization Hashtable hash = (Hashtable)s.readObject(); // call readObject() of s and downcast the data read ... // ... before assigning it to a variable Object data = null; data = hash.get("mo"); // NORES // get model property value if (data != null) model = (VectorModel)data; if (model instanceof WritableVectorModel)model; data = hash.get("vm"); // NORES // get viewManager property value if (data != null) viewManager = (VectorViewManager)data; data = hash.get("se"); // NORES // get selection property value if (data != null) selection = (WritableVectorSelection)data; }
To serialize an object using the Externalizable interface, pass the object to the writeObject() method of an ObjectOutputStream. writeObject() then executes the writeExternal() method. Similarly, to read an object from a stream, call the readObject() method of an ObjectInputStream. readObject() then executes your readExternal() method.
Using the Externalizable interface, you never use the Java serialization mechanism, but always create your own. For more information about externalizing your component, consult the JDK documentation.
In serialization and deserization code that you write, you can often handle such discrepancies if you can convince the serialization mechanism that the two classes are merely two versions of the same class. To do that, you must define a serialVersionUID constant in the class.. serialVersionUID must be defined as static final long. For example, this is the current definition of serialVersionUID in ListCore:
private static final long serialVersionUID = 200L;If you don't add a serialVersionUID variable, a unique version number for your class is created automatically by the ObjectOutputStream. Any change made to the class results in the generation of a new version number. To prevent this from happening, declare the serialVersionUID constant. Even if the serializing class and the deserializing class vary slightly, the version number is the same and therefore, the classes are considered to be the same version.
The JDK includes a serialver program that can compute an initial value for your class. By adding a serialVersionUID constant, you can handle minor changes to your class without destroying the compatibility of the two classes. When you make greater changes to the class, change serialVersionUID constant.