|
Course Notes Table of Contents Exercises | Online Training Index
| ||||||||||||||||||||||||||||||
|
JavaBeans
MageLang Institute
|
||||||||||||||||||||||||||||||
In this section, you will learn about:
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 JavaBeansJavaBeans 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.ExerciseBeans ArchitectureBeans consist of three things:
EventsEvents 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:
PropertiesProperties 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). MethodsBean 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. PersistencePersistence 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
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
Writing Bean ComponentsTo 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.EventsAn event allows your Beans to communicate when something interesting happens. There are three parts to this communication:
EventObjectThe 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;
}
}
EventListenerThe 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 SourceWithout 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.
PropertiesA 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 PropertiesAs 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 PropertiesAn 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 PropertiesRevisit 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 PropertiesConstrained 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); MethodsMethods 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 CustomizationCustomization 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.
BeanInfoThe 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
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.IntrospectionIntrospection 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. EventsThe 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 PropertiesThe 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 MethodsThe 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. BeanInfoAlthough 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 =
SizedTextFieldBeanInfo.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:
CustomizersUsing 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 CustomizersIf 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.
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 CustomizersIf 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).
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:
Using System Property EditorsThe JDK comes with a handful of suitable property editors available for your use. You should expect similar editors available with other tools:
PersistencePersistence 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 SerializationA 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.
|
|
Copyright © 1997 MageLang Institute. All Rights Reserved May-97 Copyright © 1996, 1997 Sun Microsystems Inc. All Rights Reserved |