Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

JavaBeansTM Short Course
Introduction to JavaBeans

By MageLang Institute

[Tutorial Contents] [Exercises]

In this section, you will learn about:

  • The JavaBeans architecture
  • The Beans Event Model
  • Introspection to query Beans about their contents
  • Bean component creation
  • Customization of Beans
  • Persistence to store and retrieve Beans
  • The development of applications comprised of Bean components
  • The creation of simple builder application
  • The BDK BeanBox application

This section takes you through the process of creating and using JavaBeans effectively. First, the course explains the services necessary to use JavaBeans: Introspection/Reflection, Event Handling/Communication, Persistence/Serialization, GUI Merging/Properties, and Customization/GUI Builder support. After the student has the background, this course explores how to customize, connect, and integrate with the Beans environment. Finally, the student will learn how to write new, fully functional Beans.

Introduction to JavaBeans

JavaBeans takes Java's "Write Once, Run AnywhereTM" capability and extends it to include "reuse everywhere". "JavaBeans is a portable, platform-independent component model, written in Java." With it, you create small, reusable, software components. A visual "builder" program combines components from disparate sources to create applications quickly and easily.

What's a Bean?

A Bean is a JavaBeans component. Beans are independent, reusable software modules. Beans may be visible objects, like AWT components, or invisible objects, like queues and stacks. A builder/integration tool manipulates Beans to create applets and applications.

Exercise

  1. Creating the First Bean with Kaidi

Beans Architecture

Beans consist of three things:

  • Events
  • Properties
  • Methods
Also, since Beans rely on their state, they need to be able to be persistent over time.

Events

Events are for notifying others when something is happening. The delegation-event model of AWT, introduced with Java 1.1, demonstrates the Beans event model. It has three parts:

  • EventObjects
    AWTEvent in the AWT world.
  • EventListeners
    ActionListener, ItemListener, ...
  • and Event Sources (Beans)
    The different AWT Components.
Anyone can register an EventListener with a Component, provided the Component understands the event set. (For instance, you cannot register an ActionListener with a TextArea, but you can with a TextField). When something happens within the Component, it notifies any listener(s) by sending each an EventObject, through the appropriate method of the listener.

Properties

Properties define the characteristics of the Bean. For instance, when examining an AWT TextField for its properties, you will see properties for the caret position, current text, and the echo character, among others. In the simplest case, a method or methods in the following design pattern defines a property:

public void setPropertyName(PropertyType value);
public PropertyType getPropertyName();
Where PropertyName is the name of the property, and PropertyType is its datatype. If only one method is present, the property is read-only (set missing) or write-only (get missing).

Methods

Bean methods are available for anyone to call by just making each public. However, you can restrict which methods are visible to the Bean builder/integration tool by providing a getMethodDescriptors method along with your Bean's BeanInfo. Every Bean can provide a supporting BeanInfo class to customize a Bean's appearance to an integration tool. More on BeanInfo later.

Persistence

Persistence is the ability of an object to store its state, for recreation later. Beans use Java's object serialization capabilities for persistence. The ObjectInput and ObjectOutput interfaces are the basis for serialization within Java. The ObjectInputStream and ObjectOutputStream classes, respectively, implement the interfaces. Serialization saves all non-static and non-transient instance variables of an object. If one of those is a reference to another object, that object is recursively saved, too, until everything saved is primitive datatypes. If one object is referred to by multiple references, Java serializes it only once. This ensures the creation of an identical object network when reading the primary object back. To demonstrate, use a fictitious TreeNode class to create the network:

TreeNode top = new TreeNode("top");
top.addChild(new TreeNode("left child"));
top.addChild(new TreeNode("right child"));
and then to save:
FileOutputStream fOut = 
  new FileOutputStream("test.out");
ObjectOutput out = new ObjectOutputStream(fOut);
out.writeObject(top);
out.flush();
out.close();
Now, your program can exit, knowing that when it restarts it can recreate the tree from the test.out file.
FileInputStream fIn = new FileInputStream("test.out");
ObjectInputStream in = new ObjectInputStream(fIn);
TreeNode n = (TreeNode)in.readObject();

Technology Comparison to ActiveX/COM

  • JavaBeans is a framework for building applications out of Java components (Beans).
  • ActiveX is a framework for building compound documents with ActiveX controls.
  • A Bean is very similar to an ActiveX control. However, a Bean is written in Java, so has Java's security and cross platform nature.
  • ActiveX controls written in Java is possible, however they require a port of Microsoft's Common Object Model (COM) to be used outside of Microsoft Windows.
  • More frequently, programmers create ActiveX controls in Visual Basic or Visual C++ and compile them into native libraries.

Recently, JavaWorld contained two articles comparing the technologies. The first article is a strategic analysis of the two. The second article is a head-to-head comparison.

JavaBeans Benefit Analysis

Write Once, Run AnywhereTM java.beans package part of Core API
Component Reusability Reuse Everywhere -- across platforms/tools/solutions
Example: 3D Charting Bean - drop into any container, regardless of platform tool
Interoperability Communicate with other component architectures
Beans-ActiveX Bridge now beta, Beans-OpenDoc under development

Writing Bean Components

To create a Bean, you need to determine what it should do and then define the events, properties, and methods to get it there. Actually, most of the method definitions fall out of the definition of events and properties for the Bean.

Events

An event allows your Beans to communicate when something interesting happens. There are three parts to this communication:

  • EventObject
  • EventListener - (the sink)
  • An Event Source (the Bean)
EventObject

The java.util.EventObject class is the basis of all Beans events.

public class java.util.EventObject
    extends Object implements java.io.Serializable {
  public java.util.EventObject (Object source);
  public Object getSource();
  public String toString();
}
Although you can create EventObject instances directly for your Bean events, design pattern guidelines require you to subclass EventObject so you have a specific event type. For example, to define an event for an employee's hire date, you could create a HireEvent class.
public class HireEvent extends EventObject {
  private long hireDate;
  public HireEvent (Object source) {
    super (source);
    hireDate = System.currentTimeMillis();
  }
  public HireEvent (Object source, long hired) {
    super (source);
    hireDate = hired;
  }
  public long getHireDate () {
    return hireDate;
  }
}
EventListener

The EventListener interface is empty, but serves as a tagging interface that all event listeners must extend; that way, Beans integration tools can work. A listener is something that desires notification when an event happens. The listener receives the specific EventObject subclass as a parameter. The name of the new listener interface is EventTypeListener. For the new HireEvent, the listener name would be HireListener. The actual method names within the interface have no specific design pattern to follow, but, should describe the event that is happening.

public interface HireListener
    extends java.util.EventListener {
  public abstract void hired (HireEvent e);
}
Event Source

Without an event source, the HireEvent and HireListener are virtually useless. The event source defines when and where an event will happen. Classes register themselves as interested in the event, and they receive notification when the event happens. A series of methods patterns represents the registration process:

public synchronized void addListenerType(ListenerType l);
public synchronized void removeListenerType(
  ListenerType l);
The event source needs to maintain the list itself, so the entire code to do this is:
private Vector hireListeners = new Vector();
public synchronized void addHireListener (
  HireListener l) {
  hireListeners.addElement (l);
}
public synchronized void removeHireListener (HireListener l) {
  hireListeners.removeElement (l);
}

If you are using AWT components, AWT events already have this behavior. Maintaining listeners is only necessary for new event types, or adding listeners where they previously were not (ActionEvent within a Canvas).

If you want to permit only one listener (unicast), you have the addListenerType method throw the java.util.TooManyListenersException exception when adding a second listener (and you do not need a Vector). Also, remember to synchronize the add/remove methods to avoid a race condition.

Once the event happens, it is necessary for the event source to notify all the listeners. For the hiring example, this would translate into a method like the following:

protected void notifyHired () {
  Vector l;
  // Create Event
  HireEvent h = new HireEvent (this);
  // Copy listener vector so it won't 
  // change while firing
  synchronized (this) {
    l = (Vector)hireListeners.clone();
  }
  for (int i=0;i<l.size();i++) {
    HireListener hl = (HireListener)l.elementAt (i);
    hl.hired(h);
  }
}
You have to call the method directly when the triggering event happens.

Properties

A property is a public attribute of the Bean, usually represented by a non-public instance variable. It can be read-write, read-only, or write-only. There are four different types of properties:

  • Simple
  • Indexed
  • Bound
  • Constrained
Simple Properties

As the name implies, simple properties represent the simplest of the four. To create a property, define a pair of set/get routines. Whatever name used in the pair of routines, becomes the property name (no matter what instance variable name you use). Normally, the instance variable name matches the property name. However, there is nothing that requires this. For instance, to define a property salary for an Employee Bean:

float salary;
public void setSalary (float newSalary) {
  salary = newSalary;
}
public float getSalary () {
  return salary;
}
If you need a read-only property, only define a getPropertyName routine. If you want a write-only property, only define a setPropertyName routine. Boolean properties can change their get routine to an isPropertyName routine:
boolean trained;
public void setTrained (boolean trained) {
  this.trained = trained;
}
public boolean isTrained () {
  return trained;
}
Indexed Properties

An indexed property is for when a single property can hold an array of values. The design pattern for these properties is:

public void setPropertyName (PropertyType[] list)
public void setPropertyName (
  PropertyType element, int position)
public PropertyType[] getPropertyName ()
public PropertyType getPropertyName (int position)
For instance, if you were to complete the item indexed property for the AWT List component, it might look something like this:
public class ListBean extends List {
  public String[] getItem () {
    return getItems ();
  }
  public synchronized void setItem (String item[]) {
    removeAll();
    for (int i=0;i<item.length;i++)
      addItem (item[i]);
  }
  public void setItem (String item, int position) {
    replaceItem (item, position)
  }
}

The String getItem (int position) routine already exists for List.

The AWT List class is still a Bean without these methods. However, item is not fully defined to be an indexed property.

Bound Properties

Revisit the Employee Bean, and make salary a bound property. That way, if two people were using an employee Bean (say a manager and a spouse), and one (the manager) changes an employee's salary, the other (the spouse) would like to know about said change. In order for the notification to happen, you need to maintain a watch list for PropertyChangeEvents via the PropertyChangeSupport class. First, you have to create a list of listeners to maintain:

private PropertyChangeSupport changes = 
    new PropertyChangeSupport (this);
And then, you have to maintain the list:
public void addPropertyChangeListener (
    PropertyChangeListener p) {
  changes.addPropertyChangeListener (p);
}
public void removePropertyChangeListener (
    PropertyChangeListener p) {
  changes.removePropertyChangeListener (p);
}
Finally, when the change happens (setSalary in this case), you check to see if the property value changed, and if so, notify all the listeners.
public void setSalary (float salary) {
  Float oldSalary = new Float (this.salary);
  this.salary = salary;
  changes.firePropertyChange (
    "salary", oldSalary, new Float (this.salary));
}
On the receiving end, you then need a propertyChange method.
public void propertyChange(PropertyChangeEvent e);
Since Java reports PropertyChangeEvents at the class (Bean) level (versus the property level), you need to check the property name via getPropertyName to see if you received a PropertyChangeEvent you were expecting, or something else. Also, if the property datatype is primitive, you need to wrap it into the appropriate object when firing.
Constrained Properties

Constrained properties are similar to bound properties. In addition to maintaining a list of PropertyChangeListeners, the Bean maintains a list of VetoableChangeListeners. Then, prior to the Bean changing a property value, it asks the VetoableChangeListeners if its okay. If it isn't, the listener throws a PropertyVetoException, which you declare the set routine to throw.

Changing salary to be a constrained property (so a spouse can veto salary decreases), results in the following additions:

private VetoableChangeSupport vetoes = 
    new VetoableChangeSupport (this);
public void addVetoableChangeListener (
    VetoableChangeListener v) {
  vetoes.addVetoableChangeListener (v);
}
public void removeVetoableChangeListener (
    VetoableChangeListener v) {
  vetoes.removeVetoableChangeListener (v);
}
And changes:
public void setSalary (float salary) 
    throws PropertyVetoException {
  Float oldSalary = new Float (this.salary);
  vetoes.fireVetoableChange (
    "salary", oldSalary, new Float (salary));
  this.salary = salary;
  changes.firePropertyChange (
    "salary", oldSalary, new Float (this.salary));
}
On the receiving end, your VetoableChangeListener needs a vetoableChange method.
public void vetoableChange(PropertyChangeEvent e)
  throws PropertyVetoException;
Instead of providing one listener for all property change events, and another for all vetoable change events, you can elect to maintain separate lists of listeners for each property. The design pattern for this is:
public void addPropertyNameListener (
  PropertyChangeListener p);
public void removePropertyNameListener (
  PropertyChangeListener p);
and
public void addPropertyNameListener (
  VetoableChangeListener v);
public void removePropertyNameListener (
  VetoableChangeListener v);

Methods

Methods are operations for others to interact with a Bean. Basically, anything can call any public method of a Bean. Beans receive notification of events by having the appropriate method called on them by the event source.

Besides all the methods required for each property and event of a Bean, you usually create support methods which accept no arguments or an argument of an event you listen for. That way, a builder application can inspect a class quickly for a list of the appropriate methods it can connect to. Non-public support methods may also be available. However, public methods with other parameter patterns are usually the result of a bad design pattern, and not easily connectable from a builder application. For instance, if you always pass along a time-stamp with an event, you should probably subclass the event, add a time-stamp attribute, and pass the subclass around. This results in a simplified design and easier maintainability.

Intro to Customization

Customization of Beans allows you as the Beans developer to control what a Bean-integrator will see when they use a builder tool. By default, a builder tool uses reflection to determine what to display when designing programs with Beans. In most cases, this is sufficient. However, there are times when you want to provide different functionality. For instance, if you want to provide your own customization interface, instead of using the default property sheet, you can implement the Customizer interface and provide a custom Panel. The OurButton Bean in the Beans Development Kit (BDK) provides a Customizer.

On the other hand, if you want to provide an alternative input mechanism for one property (like an enumerated list of valid choices), you can extend the PropertyEditorSupport class. The Molecule Bean in the BDK uses a property editor to provide a list of molecules it can display.

Using either of these mechanisms requires your Bean to provide a supporting class, along with the Bean itself. This second class implements the BeanInfo interface.

BeanInfo

The BeanInfo interface allows you to describe your Bean in greater detail then reflection alone can do. You usually want to do this to provide your Bean integrator with less options, or more restrictive options, so the Bean is easier to work with. In order for the system to locate the BeanInfo for a Bean, you must name it after the Bean, with BeanInfo at the end of the Bean's classname. For instance, if your Bean was called Foo, the BeanInfo for the Foo Bean would be called FooBeanInfo. It isn't necessary to implement the entire BeanInfo yourself. The SimpleBeanInfo class provides a basis, then you just selectively override methods.

Exercises

  1. From Kiva Han to Central Perk
  2. Reflective Programming with Sigmeud Freud
  3. Inspecting Beans with BeanInfo and Juan Valdez
  4. Signing Declarations with John Hancock (optional)

Design-time vs. Run-time Beans "mode"

Beans must be able to operate in a running application as well as inside a builder. At design-time, Beans must provide the design information necessary to edit properties and customize behavior. Also it has to expose methods and events so a builder tool can create code that interacts with the Bean at run-time. The Beans.isDesignTime method allows you to check for the current mode.

Introspection

Introspection is the process of determining the supported properties, methods, and events of a Bean. It can be done with the help of the Introspector class, or directly through the use of the Reflection API. The Introspector provides access to the BeanInfo for the Bean component via its getBeanInfo method, which requires an instance of Class as its parameter:
TextField tf = new TextField ();
BeanInfo bi = 
  Introspector.getBeanInfo (tf.getClass());
If you don't provide BeanInfo for a Bean, the Reflection API is used to determine the different pieces for you.

Events

The getEventSetDescriptors method reports all the events that this Bean fires. For every pair of add/removeListenerTypeListener methods (that return void), an event set is defined for the Bean.
EventSetDescriptor[] esd = 
  bi.getEventSetDescriptors();
for (int i=0;i<esd.length;i++)
  System.out.print (esd[i].getName() + " ");
System.out.println ();
For a TextField, this would print:
text mouse key component action focus mouseMotion

Properties

The getPropertyDescriptors method reports all the properties of a Bean. A property is defined by one or more routines with the following pattern:

public void setPropertyName(PropertyType value);
public PropertyType getPropertyName();
public boolean isPropertyName();
PropertyDescriptor pd[] = 
  bi.getPropertyDescriptors();
for (int i=0;i<pd.length;i++)
  System.out.print (pd[i].getName() + " ");
System.out.println ();
For a TextField, this would print:
selectionStart enabled text preferredSize
foreground visible background selectedText
echoCharacter font columns echoChar name
caretPosition selectionEnd minimumSize editable

Methods

The getMethodDescriptors method reports all the methods of a Bean. This is an all-inclusive list of public methods, which enable you to call any one of them without knowing their name before hand. For each method descriptor, you can discover the parameter types through the getParameterDescriptors method.

MethodDescriptor md[] = bi.getMethodDescriptors();
for (int i=0;i<md.length;i++)
  System.out.print (md[i].getName() + " ");
System.out.println ();
For a TextField, this would print the names of all 155 methods available, most of which are inherited from Component. This includes the add/remove event listener methods, as they are methods like the others.

BeanInfo

Although the examples have been using the Introspector so far, when a custom BeanInfo isn't provided for a Bean, the Introspector uses the Reflection API to determine the events, properties, and methods of the Bean. If you want to restrict a builder tool to display less choices, you can override the default behavior and implement your own BeanInfo. The BeanInfo interface is not implemented by the Bean itself, but by a secondary class. If we've created a SizedTextField Bean, the SizedTextFieldBeanInfo class would provide the Bean's BeanInfo. If you only wanted to display one property, length, you could define that class as such:

import java.beans.*;
public class SizedTextFieldBeanInfo 
    extends SimpleBeanInfo {
  private final static Class beanClass = 
    SizedTextField.class;
  public PropertyDescriptor[] 
      getPropertyDescriptors() {
    try {
      PropertyDescriptor length =
        new PropertyDescriptor("length", beanClass);
      PropertyDescriptor rv[] = {length};
      return rv;
    } catch (IntrospectionException e) {
      throw new Error(e.toString());
    }
  }
}
Then, instead of the 17 properties of TextField, the only property displayed by the tool, would be the new length property. The SizedTextField class still needs to implement the set/getLength methods. Also, creating a custom BeanInfo does not prevent someone from calling the methods of the now hidden properties. The property accessor methods are still public. So, by using the Reflection API (or just knowing the method names), you can still invoke all the public methods.

Also, if you wanted to have the Bean have a space delimited name, instead of being all crunched together, you could add the following to the SizedTextFieldBeanInfo definition:

public BeanDescriptor getBeanDescriptor() {
  BeanDescriptor bd = new BeanDescriptor(beanClass);
  bd.setDisplayName("Sized Text Field");
  return bd;
}
And, to wrap things up, you could even supply an icon for your Bean with the help of an image file and the following added to your BeanInfo definition:
public Image getIcon (int iconKind) {
  if (iconKind == BeanInfo.ICON_COLOR_16x16) {
    Image img = loadImage("sized.gif");
    return img;
  }
  return null;
}

Customizers

Using Customizers allow YOU, the Bean builder, to control how your Bean user is going to configure visually your Bean at design time. Instead of relying on the builder tool's property sheet to setup the properties of a Bean, you can provide either a full-screen customization option or an alternative to the datatype's default property sheet option.

Writing Customizers

If the Employee Bean only had a salary property, you could create a customizer like the following. It would only permit numeric characters in the input field. Customizers are much more powerful than this implies. However, they result in rather lengthy examples and this should provide a sufficient framework to get you started.

package employee;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;

public class EmployeeCustomizer extends Panel 
    implements Customizer, KeyListener {
  private Employee target;
  private TextField salaryField;
  private PropertyChangeSupport support =
    new PropertyChangeSupport(this);
  public void setObject(Object obj) {
    target = (Employee) obj;
    Label t1 = new Label("Salary :");
    add(t1);
    salaryField = new TextField(
      String.valueOf(target.getSalary()), 20);
    add(salaryField);
    salaryField.addKeyListener(this);
  }
  public Dimension getPreferredSize() {
    return new Dimension(225,50);
  }
  public void keyPressed(KeyEvent e) {}
  public void keyTyped(KeyEvent e) {}
  public void keyReleased(KeyEvent e) {
    Object source = e.getSource();
    if (source==salaryField) {
      String txt = salaryField.getText();
      try {
        target.setSalary(
          (new Float(txt)).floatValue());
      } catch (NumberFormatException ex) {
        salaryField.setText(
          String.valueOf(target.getSalary()));
      }
      support.firePropertyChange("", null, null);
    }
  }
  public void addPropertyChangeListener(
      PropertyChangeListener l) {
    support.addPropertyChangeListener(l);
  }
  public void removePropertyChangeListener(
      PropertyChangeListener l) {
    support.removePropertyChangeListener(l);
  }
}
All of this work results in the following screen being displayed when the developer chooses to customize your Bean.

This also assumes that the following EmployeeBeanInfo class is defined:

package employee;
import java.beans.*;
public class EmployeeBeanInfo extends SimpleBeanInfo {
  public BeanDescriptor getBeanDescriptor() {
    return new BeanDescriptor(
      beanClass, customizerClass);
  }
  private final static Class beanClass = 
    Employee.class;
  private final static Class customizerClass = 
    EmployeeCustomizer.class;
}

Writing Property Customizers

If you think providing a custom screen for all the properties is not necessary, but want to have some control over what is displayed in the property sheet for a specific property, implementing PropertyEditor may be in store for you. You can implement the class yourself or use the framework in the PropertyEditorSupport class as your basis.

If you add a position property to the Employee, you can provide a list of positions by creating a EmployeePositionEditor class (that subclasses PropertyEditorSupport).

import java.beans.*;
public class EmployeePositionEditor
    extends PropertyEditorSupport {
  public String[] getTags() {
    String values[] = {
      "President",
      "Technical Support",
      "Window Washer",
      "Janitor"};
    return values;
  }
}

When using a tag list with a property, the property must be initialized to one of the tags. If you forget, when a Bean user goes to use the property editor, a mysterious IllegalArgumentException will be thrown.

Then you add a few things to the EmployeeBeanInfo:

public PropertyDescriptor[] getPropertyDescriptors() {
  try {
    PropertyDescriptor pd = 
      new PropertyDescriptor("position", beanClass);
      pd.setPropertyEditorClass(positionEditorClass);
    PropertyDescriptor result[] = { pd };
    return result;
  } catch (Exception e) {
    System.err.println("Unexpected exception: " + e);
    return null;
  }
}
And, the property sheet for the Employee Bean might look like this:

If there are multiple PropertyDescriptors, the order in the array determines the display order. If you do not call setPropertyEditorClass for a particular PropertyDescriptor, the PropertyEditorManager locates a suitable/default PropertyEditor.

Using System Property Editors

The JDK comes with a handful of suitable property editors available for your use. You should expect similar editors available with other tools:

DatatypeEditor
BooleanBoolEditor
ByteByteEditor
ColorColorEditor
DoubleDoubleEditor
FloatFloatEditor
FontFontEditor
IntIntEditor
LongLongEditor
NumberNumberEditor
ShortShortEditor
StringStringEditor

All of these are part of the sun.beans.editors package. You do not have to do anything special to use them. The BDK's BeanBox tool is smart enough to know that if you have a float or Float property, to use the FloatEditor, similarly for the other datatypes. If you are not using the BeanBox, you can try to use them in your own environment.

Persistence

Persistence is the ability of an object to store its state. Beans use Java's object Serialization API to provide a great medium-weight solution for persistence. In the simplest case, the way to enable the serialization of any object is to implement the Serializable interface. The Serializable interface has no methods that must be implemented; however, any data fields of a class that implements this interface must also be serializable.

Bean Serialization

A Serializable object is serialized by calling the ObjectOutput.writeObject method, with the object as its parameter. To deserialize, the ObjectInput.readObject method is called. You, as a Bean creator, do NOT call these methods. What you do need to do is ensure your Bean is serializable.

  1. Is your class Serializable?
  2. Are all instance variables Serializable?
  3. Do you want everything that isn't transient or static to be saved?
  4. Should you serialize the object in its current structure?
  5. How do I initialize transient and static variables upon deserialization?
  6. Do you want to add a validator to the serialization process?

If you're Serializable and want the default behavior for everything else, you're set and don't have to do anything extra. However, if not, you have something to do.

The first is easy, does your class implement the Serializable interface (directly or indirectly)? If it doesn't, make it. (Note, there is also an Externalizable interface for when you need to control all aspect of Serialization, usually for security purposes.)

For the second, check to see if all each instance variable is Serializable. Primitive datatypes are, by default. The rest you have to check manually, until you learn which aren't. (Image isn't). If one isn't, you need to figure out what to do about it. (For instance, image files can be kept separate and re-read when a Bean is deserialized.) Do you initialize it to a default value, make it transient, or provide some other means to initialize?

The third is easy. Is it okay to save everything?

Fourth, is it more efficient to save the data in a different format? For instance, it is more efficient to serialize a Hashtable's pieces, and recreate the structure, then to save the mostly empty hashtable. Also, should you encrypt the information?

Fifth, is it okay to use default values for non-persistent variables? If not, you have to add code to handle this.

Finally, do you want to validate the deserialized object network? Like, is your graph consistent? If yes, you need to register a validation method.

For instance, if you wanted to have a Date instance variable of a class, but wanted it to be the 'last-created' date vs. a 'first-created' date, you could have a transient variable that is reset when deserialized:

public class TreeNode implements Serializable {
  Vector children;
  TreeNode parent;
  String name;
  transient Date date;
  public TreeNode(String s) {
    children = new Vector(5);
    name = s;
    initClass();
  }
  private void initClass () {
    date = new Date();
  }
  ...
  private void writeObject(ObjectOutputStream s)
      throws IOException {
    s.defaultWriteObject();
  }
  private void readObject(ObjectInputStream s)
      throws ClassNotFoundException, IOException {
    s.defaultReadObject();
    initClass();
  }
}
Although the writeObject does the default functionality, whenever a readObject method is provided, a writeObject method is necessary. [complete source]

Bean Reconstitution

Normally, you call new when you want to create an instance of a class. With Beans, you can still do that, however, there is another way. The Beans.instantiate method is normally used by builder tools to recreate a Bean from a serialized Bean source. The reason being, tool vendors do not want to restrict what Beans you could use to only those it knew about when the tool was created. For instance, to create a TextField, without new, just call:

Component c = 
  (Component)Beans.instantiate(
  null, "java.awt.TextField");

By itself, this is identical to Component c = new TextField(); However, before you get too excited, build on this a bit. First, create your own TextField class, by subclassing TextField.

public class MyTextField extends java.awt.TextField {
}
Nothing fancy yet. But, create a little program to save a serialized MyTextField, with a couple of properties set:
import java.awt.*;
import java.io.*;

public class TfSaver {
  public static void main(String args[]) {
    MyTextField mtf = new MyTextField();

    // set the properties
    Font ft = new Font("Serif", Font.ITALIC, 36);
    mtf.setFont(ft);
    mtf.setText("Hello World");
    
    // serialize
    try {
      FileOutputStream f = new FileOutputStream(
        "MyTextField.ser");
      ObjectOutputStream s = 
        new ObjectOutputStream(f);
      s.writeObject(mtf);
      s.flush();
    } catch (Exception e) {
      System.out.println(e);
    }
    System.exit(0);
   }
}
Now, if you create an application that uses a MyTextField instance, it can start with the font and text properties already set (to 36-point italic Serif font and "Hello World" text).
Component c =
  (Component)Beans.instantiate(null, "MyTextField");
If a filename of the form Classname.ser exists (MyTextField.ser in this case), then the class is deserialized. Otherwise, the Bean will be created by using the default no-arg constructor of the class.

With MyTextField.ser


Without MyTextField.ser

Bean Versioning

Changes made to a Bean that change the persistence structure flag the new version as incompatible with the old, and prevents reconstitution. While this sounds good in theory, in practice it has its shortcomings. Adding any new method to a Bean makes the new class definition incompatible (for deserialization), resulting in a java.io.InvalidClassException being thrown. However, since a method does not add (or remove) any state information (behavior is not serialized), the two classes should be compatible. In order to ensure the new version can be reconstituted from the original, you need to add a Stream Unique Identifier (SUID) variable to your class definition.

private static final long serialVersionUID =
    -2966288784432217853L;

Then, when the versions are truly incompatible, you change the SUID variable. When this does happen, you should keep track of all the old values so you can properly handle the differences (e.g., default setting for new instance variable). If you forget to save the old setting, it is necessary to revert back to an old version, regenerating the value, and moving forward again.

The values are generated from a hash algorithm, not randomly picked out of the air nor even a one-up numbering scheme. Since static variables are not serialized, the value is regenerated during deserialization and compared against the current class definition. The program to generate the values is serialver and has one argument of the classname. If you run the program with the -show argument instead, serialver displays a graphical interface, which allows you to cut and paste the results into your editor.

Exercises

  1. BDK Introduction with John Wayne
  2. Packaging Central Perk with Marcel
  3. Getting Resources with Clara Barton
  4. Saving Billboards with Casey Kasem

Resources

Some web-based resources:

and books:
  • The JavaBeans Specification and Tutorial (Addison-Wesley)
  • Developing Java Beans (O'Reilly)
  • Design Patterns - Elements of Reusable Object-Oriented Software (Addison-Wesley) Gamma, Helm, Johnson, Vlissides (ISBN 0-201-63361-2).
Copyright © 1997 MageLang Institute. All Rights Reserved.

[ This page was updated: 9-Nov-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.