Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

How Do I Create a JavaBean?


Dr. Dobb's Journal September 1997: How Do I Create a JavaBean?

Cliff, vice president of technology of Digital Focus, can be contacted at [email protected]. To submit questions, check out the Java Developer FAQ web site at http://www.digitalfocus.com/faq/.


The JavaBeans spec (http://splash.javasoft.com/beans/spec.html) states, "A JavaBean is a reusable software component that can be manipulated visually in a builder tool." To this end, the Bean spec defines a set of standards that IDEs can use to create components that can be used across different development tools. It also defines a set of classes and methods for obtaining information about Beans; IDEs use these.

Nonreusable components are tightly coupled. Components invoke each other's methods by passing references. Beans, in contrast, are loosely coupled. Events are used as the primary mechanism to achieve coordination between components. Beans are also customizable. Beans have properties with accessor methods, which can be called by an IDE. Properties may optionally be "bound," or "constrained," in such a way that changes to properties automatically propagate under the control of the programmer.

Beans don't implement a "Bean interface." Therefore, an IDE must have a mechanism for finding out what a Bean's methods are, and which of those methods can be used to achieve the things the IDE needs to do (set property values, for instance). Java 1.1 provides a foundation set of methods (called "reflections") in java.lang.Class for obtaining anything you might want to know about a specified class. A Bean can also have a BeanInfo object to provide additional information.

Knowing that a class has a method called abc() does not tell you what that method does, so Beans define a set of "design patterns" -- naming conventions for the methods a Bean is expected to have. If the reflection methods find methods that adhere to these conventions, the IDE can make assumptions about what those methods do. For example, a method called getAbc() is expected to retrieve the value of a property called abc.

Sometimes an IDE will need to call one of these methods. But if the method is only discovered at run time (that is, when users drag a new Bean into an application being built), how can the IDE call any of these methods? When you retrieve a list of a class's methods, the list is in the form of an array of Method objects. The Method class has a method called invoke(), which can be used to call that method, given an instance of the method's class as the first argument, and an argument list (as an array of objects) as the second.

Designing Beans

To design a Bean, you first must choose an existing (or create a new) event model. If you use an existing event model, your Bean will be able to interact with other Beans that already use that event model. An event model consists of a set of event classes, derived from java .util.EventObject, and associated event listener interfaces.

The AWT 1.1 event model conforms to the Bean specification, and provides many event classes and event handler (listener) interfaces (defined in java.awt .event). If your Beans use these event types, they will be able to interact with AWT components. In the AWT, the event objects are generated by the components in response to system events. The AWT queues the generated event objects, and a separate queue dispatcher thread removes events from the queue and calls the originating component's listeners. The AWT defines 11 types of listeners, each with a set of callback methods that the event dispatcher will call depending on the event. For example, if a window's Close icon is clicked, and the window has an event listener, the AWT event dispatcher will call the listener's windowClosing() method. The set of AWT event types and listener types defines the event space and event-handler signatures for the AWT event model. Components that use these types (Beans or otherwise) can communicate using these events.

For Beans, an important type of event is the PropertyChangeEvent, defined in the java.beans package. This is the type of event generated when a Bean property editor modifies the value of a Bean property. Property editors are typically IDE-based objects specifically designed to control the modification of a particular Bean property. All property editors implement the java.beans.PropertyEditor interface. A property sheet is a visual component in that an IDE lets users read and modify Bean properties to customize a Bean. Property sheets generally make use of property editor objects for updating property values. Once the Bean has been customized, the IDE may save the Bean's state by serializing the Bean, using Java serialization, and writing this state to a file. For this column, I will ignore the whole issue of properties and property editors, and concentrate on the event aspects of a Bean.

You may be wondering, "What makes a Java component a Bean?" Most Java classes could be considered Beans if appropriately packaged. Some classes, for example, define properties, according to Bean conventions, and are therefore Beans. Any JDK 1.1 AWT-derived component falls into this category. On the other hand, defining an event-listener interface, and methods for registering those listeners with the Bean, makes a component a Bean.

Yet, what really makes a component a Bean is the way it is transported and used. A Bean must be implemented as a JAR file (see "How Do I Create a Signed Applet?" DDJ, August 1997). Within that JAR file, there is normally a manifest file listing its contents. The JAR file's manifest includes an entry for each item in the JAR file, and if an entry for a class specifies the attribute Java-Bean: True, the class is understood to be a Bean. If there is no manifest file, all classes are assumed to be Beans.

For reusable components to communicate, they should use events, in accordance with the Bean specification for how those events and event-handler interfaces should be defined. Similarly, if a Bean is to have attributes that users can modify using an IDE, then those attributes should have accessor methods, in accordance with the Bean spec. The way an IDE discovers if a Bean has these things is by using any BeanInfo objects provided with the Bean. If no BeanInfo objects are provided, the Java reflection methods are used; for example, java.lang.Class.getMethods() returns an array of method descriptor objects, revealing the methods that the class has. The IDE can even invoke one of these methods, by calling the method Method.invoke() and passing the instance object as the first argument. The invoke() method is, of course, a native method, and interacts closely with the virtual machine. Using this technique, you can at run time dynamically invoke methods that are not explicitly named in the compile-time code.

PasswordDialogBean

I have constructed a simple example of a Bean, called "PasswordDialogBean," which interactively gets a user ID and password, then requests verification of that password; see Figure 1. By defining this class as a Bean, it can be connected dynamically in an IDE to any other component designed to use the same event model. This is a reusable component, and the actual application that connects it to other components could be constructed using drag-and-drop. Any unique event-handling code would, of course, have to be compiled and inserted by the IDE, but the number of PasswordDialogBean instances could be selected without programming, and the objects listening to its events could be designated again without programming.

The first step in defining a Bean is to decide on its event model. For GUI-based components, a choice of GUI event models is required, normally the AWT. The set of AWT events may not be sufficient for all the Bean needs to do, however, and so additional events (and corresponding event-listener interfaces) may need to be defined. In general, each kind of application will require an event model, which defines the events unique to that application. In this example, I define an event called PasswordEvent: I will use it for communication between my Bean and other Beans that use it. I have also created another Bean, which makes use of my PasswordDialogBean; I call it DemoBean. It must adhere to the same event model as my PasswordDialogBean.

Listing One defines the PasswordEventObject. Notice that it defines an id and other variables, which let me communicate information to listeners of this type of event. For example, when users enter a user ID and password and click the Submit button, a PasswordEventObject is generated, with id=PASSWORD_PROFFERED and user ID and password set to the values entered by users. This event object is created by the Submit button event handler.

Any object that wishes to respond to password events must implement the PasswordListener interface (Listing Two). A listener of password events must also register itself with the object that generates the event -- the event source. The event source must therefore have methods to provide for this registration (and deregistration). In this case, these methods are the addPasswordListener() and removePasswordListener() methods.

All AWT components provide methods like these for the registration of listeners. For registering an interest in an AWT event, you implement one of the AWT listener interfaces and call the source component's addXXXListener() method. Since you have defined your own event type, PasswordEventObject, you need to implement the addPasswordListener() and removePasswordListener() methods.

You probably have detected a pattern in the naming of these methods and types. For example, if you have defined an event-listener interface of type <listenerInterfaceType>, the add... and remove... methods must be called; see Listing Three.

An IDE will use the introspection (BeanInfo object) and reflection facilities to find and identify these methods for any given listener, based on the name.

The AWT components have these kinds of methods for the listener types they can send events to. If you have defined your own kinds of events, you must keep track of your own listeners. This should be a transient list. For example, in PasswordDialogBean I define private transient java.util.Vector passwordListeners = new java.util.Vector();. The addPasswordListener() and removePasswordListener() methods add and remove listeners from this list.

Any listener object that receives events must implement the listener interface for that event type. Beans frequently generate events and respond to them as well, so it is natural for a Bean to itself implement one or more listener interfaces. PasswordDialogBean implements my PasswordListener interface, with the method in Listing Four.

If another Bean registers this Bean as a listener for password events, this method will be called in response to each such event. In this case, it checks if the event is signaling a password approval; if so, it disposes of this dialog. An IDE might allow users to edit event-handler code, so that users can customize behavior in arbitrarily complex ways. The IDE would then have to dynamically compile the user's code, and incorporate it into the Bean instance, probably by extending the base Bean and creating a new class.

A Bean must have a null constructor. Only then can an IDE instantiate a Bean without having to obtain constructor arguments at the moment of instantiation. The impact for the Bean developer is that not all kinds of classes can be Beans. An AWT Dialog, for instance, has no null constructor. You can simulate a non-modal dialog with a Frame, and a Frame has a null constructor. This is what I did in the example here. Still, it would have been better if I could have used the Dialog class. Javasoft's Bean Box, for instance, cannot instantiate Beans that cannot be put into an AWT Window. Thus, my PasswordDialogBean cannot be instantiated into the Bean Box, because PasswordDialogBean extends Frame, and a Frame cannot be added to a Window. A real IDE should be able to handle this kind of situation. In fact, a Frame does not belong to a window, and so must be dynamically instantiated (and hooked up to listeners) at run time. This is also a situation the Bean Box cannot handle, but a real IDE would have to.

PasswordDialogBean generates events itself, as most Beans do. It generates a PasswordEvent, with id=PASSWORD_ PROFFERED whenever users click on the Submit button. Since the Submit-button click event is caught by ActionListener, the ActionListener object generates the PasswordEvent for this Bean. I have made the Bean the ActionListener for the Submit button, and so PasswordDialogBean has the handler method for this event type; see Listing Five.

The listener list is cloned before it is traversed. This is, in effect, the event-delivery mechanism: You are, in a synchronized and uninterruptible manner, retrieving a list of the listeners for the generated event. You do not need to do this if you don't care if the list is updated by another thread while you are traversing it. Immediately afterwards, you traverse the cloned list, and call each listener's handler in turn. (Alternatively, you could add the event to an event queue, and dispatch events from the queue in a dispatcher thread.) The Bean spec recommends that listeners be called from unsynchronized code, to avoid a deadlock if a different thread is at that moment executing a synchronized block used to either post new events or retrieve listeners from the listener list.

DemoBean: The Client Bean

To demonstrate a component that uses the Bean presented here, I have created DemoBean. Like PasswordDialogBean, DemoBean has the normal addPasswordListener() and removePasswordListener() methods, and a transient-listener list. It also has an event-handler method, for responding to incoming password events. This event handler also generates events. However, if the handler receives a PASSWORD_PROFFERED password event, it implements a check for the validity of the password (I just use a hard-coded test to see if the user ID and password match the strings "Albert" and "Einstein", respectively), and if the test succeeds, the handler generates a new password event, with id=PASSWORD_APPROVED; otherwise, with id=PASSWORD_REJECTED. Any listeners for these events (like the PasswordDialogBean) will respond in whatever way they prefer; for example, PasswordDialogBean responds to PASSWORD_APPROVED by closing itself.

When you hook two Beans up in Javasoft's Bean Box, the Bean Box dynamically generates and compiles a hookup adapter (see Listing Six), and generates code similar to Listing Seven in the container object. These pieces of code become part of the generated application, and are used to connect the two Beans when the application is started. In DemoBean, I have included this code only for illustration, and commented it out. Because I have the "advantage" of coding this by hand, I don't need an auxiliary class to implement my connections and instead use Listing Eight to connect the Beans.

For comparison, I have implemented the same functionality of PasswordDialogBean and DemoBean, as closely as possible, using "conventional" (nonevent) techniques. That implementation is not reusable, and cannot be instantiated in a Bean IDE, or connected to other Beans. It is shorter, and I was able to put all the code in one file, instead of multiple files. It was also much easier to write. However, it is less useful, because of its lack of reusability. The complete source code for Demo.java and other files is available electronically from DDJ (see "Availability," page 3) and Digital Focus (http://www .digitalfocus.com /ddj/code/).

Saving and Delivering Beans

Beans are distributed in JAR files. A JAR file may contain zero or more Beans. A Bean may be represented either by a class file, or by a serialized object (.ser) file created using Java serialization (see the ObjectInputStream and ObjectOutputStream classes in package java.io). If the serialized object file is marked as a Bean (the manifest Java-Bean attribute is True), then a Bean IDE is required to load the file and reconstruct the object from the serialized object file, retrieving any classes it needs from the JAR file. Thus, you can save a Bean as a persistent object file, and count on that state being restored when the Bean is used.

When an IDE is used to build an application from Beans, the resulting application is not necessarily a Bean. In fact, Beans are not required to retain their identity as Beans when instantiated into an application -- that's up to the IDE. If the application that uses Beans is itself constructed as a Bean, the entire application can be serialized and saved in a JAR file.

The Bean specification states that inter-Bean event connections (references to listeners) should be given a transient modifier. This ensures that if an IDE serializes a Bean, its interconnections do not result in the Bean pulling in all connected objects with it. Rather, an IDE that uses Beans to build applications should construct container objects in such a way that they rebuild the connections automatically when they are constructed. For example, my main method in the DemoBean class shows code that would be generated by the Javasoft Bean Box for this example. The "hookup" code is confined to a set of classes that are outside of the Bean itself. In my example, I put the hookup class definitions in the main() method, for your benefit, so they would be side by side with my own code for comparison. The Bean Box, and a real IDE, would generate this code in an outer class, completely external to the Bean. The hookups are therefore not part of the Bean.

Conclusion

Beans are an important part of Java development. It may even come to pass that Beans will become Java, much as Visual Basic is now defined by the COM objects (and their derivatives). However, writing Beans is not trivial, and having to adhere to complex programming paradigms brings back memories of C++, which the Java community should be looking beyond. If Beans are so important, they should be incorporated into the language. The Java language compiler should enforce adherence to design patterns for components defined to be Beans; you should not have to do bookkeeping, or worry about naming. Bean-development tools provide this in varying degrees, but interactive development tools are not always the answer, especially in sophisticated applications. Further, it should be possible to write and compile a specification for a Bean. Perhaps someone will define such a specification, and write a Bean compiler. If anyone has or is planning to, I'd be interested in hearing from you.

That said, the Bean model is powerful and will provide a model of unprecedented power for the creation of full-featured application-development environments. Imagine drag-and-drop IDE components that implement functionality such as CORBA dynamic invocation bridges and browsers, JDBC drivers, PBX applications, and paging and GPS system interfaces. The long-promised productivity gains of computers, delayed by overly complex approaches and lack of standardization, are about to be realized.


Listing One

public class PasswordEventObject extends java.util.EventObject{
    public PasswordEventObject(Object source, int id)
    {
        super(source);
        this.id = id;
    }
    public static final int PASSWORD_PROFFERED = 1;
    public static final int PASSWORD_APPROVED = 2;
    public static final int PASSWORD_REJECTED = 3;
    public int id;
    public String userid;
    public String password;
}

Back to Article

Listing Two

public interface PasswordListener{
    public void handlePasswordEvent(PasswordEventObject peo);
}

Back to Article

Listing Three

public void add<listenerInterfaceType>(<listenerInterfaceType> arg)public void remove<listenerInterfaceType>(<listenerInterfaceType> arg)

Back to Article

Listing Four

public void handlePasswordEvent(PasswordEventObject peo){
    if (peo.id != PasswordEventObject.PASSWORD_APPROVED) return;
    // Close this dialog
    System.out.println("Password verified; banana mochas all around!");
   dispose();
}


</p>

Back to Article

Listing Five

public void actionPerformed(java.awt.event.ActionEvent e){
    if (e.getSource() == okButton)
    {
        // Generate a PASSWORD_PROFFERED event
        PasswordEventObject peo = new PasswordEventObject(this, 
            PasswordEventObject.PASSWORD_PROFFERED);
        peo.userid = useridField.getText();
        peo.password = passwordField.getText();
        // Notify each listener
        java.util.Vector listeners;
        synchronized (this)
        {
            listeners = (java.util.Vector)(passwordListeners.clone());
        }
        for (int i = 0; i < listeners.size(); i++)
        {
            // Invoke the listener's handler
            PasswordListener pl = (PasswordListener)(listeners.elementAt(i));
            pl.handlePasswordEvent(peo);
        }
    }
    else if (e.getSource() == cancelButton)
    {
        dispose();
    }
}

Back to Article

Listing Six

class PasswordBeanAdapter // BeanBox generates and compiles this dynamically{
    public void setTarget(PasswordListener t) { target = t; }
    public void addPasswordListener(...) ...
    public void removePasswordListener(...) ...etc.
}

Back to Article

Listing Seven

PasswordBeanAdapter pbAdapter = new PasswordBeanAdapter();pbAdapter.setTarget(demoBean);      // link the adapter to the listener
pb.addPasswordListener(pbAdapter);  // and the source to the adapter 

Back to Article

Listing Eight

pb.addPasswordListener(demoBean);demoBean.addPasswordListener(pb);

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.