Online Training Index
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 Anywhere" 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
- 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 Anywhere |
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
- From Kiva Han to Central Perk
- Reflective Programming with Sigmeud Freud
- Inspecting Beans with BeanInfo and Juan Valdez
- 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:
| Datatype | Editor |
| Boolean | BoolEditor |
| Byte | ByteEditor |
| Color | ColorEditor |
| Double | DoubleEditor |
| Float | FloatEditor |
| Font | FontEditor |
| Int | IntEditor |
| Long | LongEditor |
| Number | NumberEditor |
| Short | ShortEditor |
| String | StringEditor |
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.
- Is your class
Serializable?
- Are all instance variables
Serializable?
- Do you want everything that isn't
transient or static to be saved?
- Should you serialize the object in its current structure?
- How do I initialize
transient and static variables upon deserialization?
- 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
- BDK Introduction with John Wayne
- Packaging Central Perk with Marcel
- Getting Resources with Clara Barton
- 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.
|