Java Beans and the New Event Model

Dr. Dobb's Journal April 1997

Gluing Java components together

By Eric Giguère

Eric is a software engineer with the Powersoft Languages Division of Sybase and one of the developers of Powersoft Optima++. He can be reached at giguere@ watcom.com.

The defining software trend of the '90s is the use of software components and rapid application development (RAD) tools to quickly prototype and flesh out interactive applications. In Java, components that follow the Java Beans specification can be used in a number of Java-based RAD tools. In this article, I'll examine the Java Beans specification and describe the event model that lets you glue components together.

Component Models

A component is a software object that has the following elements:

Components are also opaque (their internal implementation is hidden, and they may only be available in binary form), and they encapsulate important functionality. Many components represent on-screen windows or controls that interact with the user. These components are usually referred to as "visual components." In a RAD tool, the emphasis is on the use of visual components, but nonvisual components can also be used, in which case the RAD tool creates a special design-time representation for the nonvisual component that can be manipulated directly by the user.

Applications are built by combining a set of independent components with developer-written code. The developer's code acts as the "glue" between components, in most cases responding directly to component events by setting component properties or invoking component methods.

Actually implementing a component requires component writers and the developers of RAD tools to agree on a component model -- a set of conventions for identifying and invoking properties, methods, and events. The component model can also describe design-time information that the RAD tool might find useful -- the icon to display in a toolbar or instructions on how to bring up a set of property sheets -- independent of a particular programming language or development system.

There are many different component models in use today, and some tools can use more than one. The ActiveX Control (formerly known as OCX) model is probably the best known on Microsoft Windows platforms.

Java Beans

Java Beans is a component model for building and using Java-based components. Beans is a public specification developed by Sun in consultation with the Java community at large. Version 1.0 of the spec is available for download from http://splash.javasoft.com/beans.

A "bean" is just a Java class with some extra descriptive information. The descriptive information is similar to the concept of an OLE type library. Unlike OLE type libraries, however, a bean is usually self-describing. Because of Java's late-binding features, a Java .class file (the equivalent of a .OBJ file in C or C++) contains the class's symbol information and method signatures, and can be scanned by a development tool to gather information about the bean. This gathering process is referred to as "introspection" and is usually done by applying heuristics to the names of public methods in a Java class. (The Beans specification refers to these heuristics as design patterns, not to be confused with the high-level design patterns common in object-oriented software design.) Introspection can also be performed by using a BeanInfo companion class provided by the developer of the bean, which allows fine control for limiting the object attributes that are exposed to the user. A typical use for a BeanInfo would be to retrieve localized names for a bean's properties, methods, and events.

Any Java class with public methods can be considered a bean, but a bean typically has properties and events as well as methods. Listing One s an example of a simple bean with a single property.

Properties are sets of methods that follow special naming conventions. In the case of read/write properties, the convention is that if the property name is XYZ, then the class has the methods setXYZ and getXYZ, respectively. The return type of the getter method must match the single argument of the setter method. Read-only or write-only properties have only one of these methods. Boolean properties can also have a getter method of the form isXYZ in addition to or instead of the getXYZ method. Java Beans also provides support for indexed properties, which are properties that set or get an indexed value. Indexed properties take an additional integer parameter in their setter and getter methods.

The Beans definition of a property isn't revolutionary. It essentially reflects and standardizes what is common practice both in Java and in other object-oriented languages. The most interesting feature of Java Beans is really the Java event model.

Note that the Beans specification does not define things such as visual components and how they paint themselves. These are determined by the application framework. Usually, this consists of the AWT UI classes, so most visual beans will end up deriving from one of the existing AWT classes such as java.awt.Canvas.

The Old Event Model

Events are a convention for notifying a program of things that might interest it, but they are not really part of the Java language. Events are typically associated with UI components and are often triggered in response to what the user does to or with the component.

The Java event model was defined by the AWT 1.0 UI classes that ship as a standard part of the Java environment. The model is very simple. When an event occurs, AWT calls the handleEvent function on the appropriate component, passing an Event object that describes the event (which event occurred, the key and mouse states, and so on). If false is returned from the handleEvent function, AWT calls the handleEvent function of the component's container. This process continues until a component returns True from handleEvent or the top-most container is reached.

There are three problems with this event model:

These problems are all addressed by the event model in the Java Beans specification. The Beans event model can be used with the AWT 1.1 UI classes.

The Beans Event Model

The Beans event model is a callback model: Anyone interested in an event registers itself as a listener with the component. When the event occurs, the component calls the listener back. This model is used with great success in other systems (like Motif and Optima++) and avoids the problems just listed because

In languages like C and C++, the callback mechanism is simple to implement: Event handlers are invoked indirectly through function pointers (or member function pointers). The registration process is merely a matter of passing a function pointer to the event source, which stores it in a list. So how do you do this in a language like Java that doesn't support pointers of any kind?

The answer is that you define a callback interface for each event, a class with a method whose signature (name and parameters) is known by the event source. An object of that class is created and the object itself is registered with the event source. When an event occurs, the event source directly invokes the method on each registered object. In C++, you would define the callback method as a virtual function on an abstract base class. In Java, the preference is to define a Java interface.

Listing Two(a) is an example of a listener interface that is close to what Beans defines. Recall that in Java, an interface is really just a set of methods that are to be implemented by one or more classes. Listing Two(b) is a class implementing a listener.

The nice thing about interfaces in Java is that you can treat them like classes in many cases, so that a component can easily define a registration interface, as in Listing Two(c). Here I've defined a registration method called addClickListener which takes a ClickListener as input and adds it to an internal list. When the component wants to notify the listeners that a Click event has occurred, it simply calls the click method on each ClickListener in the list.

The Java Beans event model is a bit more complicated than the simple listener model in Listing Two. All event-listener interfaces must extend the java.util.EventListener interface -- this is what identifies it as an event interface during the introspection process. The name of the interface must end in the Listener suffix. The methods (there may be more than one) defined by the interface must take a single parameter whose type is a subclass of java.util.EventObject (exceptions to this rule are permitted but should be avoided). Listings Three(a) and (b) show the Beans version of ClickListener and the ClickEvent class.

ClickEvent is not a particularly interesting class, but the idea is that each event would add methods and data members to its subclass of java.util.EventObject as appropriate.

The only thing left to do is to define the registration methods in the event source. The Beans specification requires methods to add and remove listeners, as in Listing Three(c).

The addClickListener and removeClickListener methods are synchronized to make them thread safe. Notice how the handleClick method first clones the existing list of listeners before invoking the click method on each element in the list. This means that once the event is triggered, all the event listeners that were registered with the event source at that time are guaranteed to be called. (Technically, the handleClick method is not required by the Beans spec but it makes good sense to have such a function. If you make it protected, it can only be called by the component or one of its subclasses.)

If you are implementing an event source, you might consider some simple optimizations. The first that comes to mind is to not allocate the Vector object until the first listener is added. Another optimization is to delay the cloning operation until absolutely necessary by moving it into the add and remove operations. Both optimizations require more code but can yield much better performance and memory usage. (After implementing a few events, though, you'll probably find you miss not having C++ templates and a preprocessor! The only real disadvantage to this event model is the constant code duplication that's required.)

Event names should be as simple as possible. As long as you place all your classes in unique Java packages, there won't be any conflict with events in other packages. The names of the listener interface and the event data class are derived from the name of the event.

I've provided sample source code that demonstrates how to define and use the Java Beans event model (see "Availability," page 3). Files of particular interest include:

The MyDialog examples can be run directly from the command line.

Using the Beans Model

Assume you have a PushButton component with a Click event. If the button is on a dialog, there is a simple way to trap the event, as shown in Listing Four(a). Problems arise, of course, if you have two or more PushButton objects on the same dialog. You could use an if statement inside the click function to distinguish the buttons based on the source of the event (available as a parameter from the event data). The class would look something like Listing Four(b).

Another approach is to define a relay class (the Beans specification refers to this as an "adaptor") whose job is to implement a particular listener interface and then relay the event to another object. You can modify Listing Four(b) to use relays quite simply, as in Listing Four(c).

Which approach is better? As usual, there are trade-offs. Using relays increases the number of classes (each class gets compiled into a separate .class file) and the amount of memory your project uses increases. Relays are very efficient, however, since there are no if statements. Not using relays keeps the number of class files to a minimum but adds a small performance penalty that increases with the more components you use. With luck, your development tool gives you a choice of which approach to use.

The Future of Beans

Although the Beans specification was completed ahead of schedule, it will take a while for vendors to update their components to work as beans, and for web browsers and other applications that embed the Java virtual machine to update themselves to the new Java event model. An SDK for Beans is in the works and a number of software vendors have announced support for using and creating beans in their Java development tools. At this point, however, nothing is stopping you from writing your own beans, so just grab the specification and start writing. It's a lot less complicated than creating an OCX ever was.

DDJ

Listing One

public class SimpleBean {

// Define the 'Visible' property public boolean getVisible() { return _visible; } public void setVisible( boolean visible ) { _visible = visible; } private boolean _visible; }

Back to Article

Listing Two

(a)

// A simple (but not a Beans) listenerinterface ClickListener {
    void click();
}

(b)
// An implementation of a simple listener

public class MyClickListener implements ClickListener { public void click() { System.out.println( "You clicked!" ); } }

(c)
public class MyComponent {    private Vector _clickListeners = new Vector();

public void addClickListener( ClickListener listener ) { _clickListeners.addElement( listener ); } protected void handleClick() { for( int i = 0; i < _clickListeners.size(); ++i ){ ((ClickListener) _clickListeners.elementAt(i)).click(); } } }

Back to Article

Listing Three

(a)

// A Beans listener interfaceinterface ClickListener extends java.util.EventListener {
    void click( ClickEvent event );
}

(b)
// A Beans event data classpublic class ClickEvent extends java.util.EventObject {
    public ClickEvent( Object source ) {
        super( source );
    }
}

(c)
// A Beans event sourcepublic class MyComponent {
    private Vector _clickListeners = new Vector();
    public synchronized void addClickListener( ClickListener listener ) {
        _clickListeners.addElement( listener );        
    }
    public synchronized void removeClickListener( ClickListener listener ) {
        _clickListeners.removeElement( listener );
    }
    protected void handleClick( ClickEvent event ) {
        Vector l;
        synchronized( this ) {
            l = (Vector) _clickListeners.clone();
        }
        for( int i = 0; i < l.size(); ++i ){
            ((ClickListener) l.elementAt(i)).click( event );
    }
}

Back to Article

Listing Four

(a)

public class MyDialog extends java.awt.Dialog implements ClickListener {    public MyDialog() {
        super( null, true );
        pb = new PushButton( "pb_1" );
        pb.addClickListener( this );

add( "Center", pb ); pack(); } public void click( ClickEvent event ) { System.out.println( "You clicked!" ); } private PushButton pb; public static void main( String args[] ) { MyDialog app = new MyDialog(); app.show(); } }

(b)
public class MyDialog2 extends java.awt.Dialog implements ClickListener {    public MyDialog2() {
        super( null, true );
        pb_1 = new PushButton( "pb_1" );
        pb_1.addClickListener( this );
        pb_2 = new PushButton( "pb_2" );
        pb_2.addClickListener( this );

add( "Center", pb_1 ); add( "Center", pb_2 ); pack(); } public void click( ClickEvent event ) { Object source = event.getSource(); if( source == pb_1 ){ System.out.println( "You clicked pb_1!" ); } else if( source == pb_2 ){ System.out.println( "You clicked pb_2!" ); } } private PushButton pb_1; private PushButton pb_2;

public static void main( String args[] ) { MyDialog2 app = new MyDialog2(); app.show(); } }

(c)
class pb_1_Relay implements ClickListener {    public pb_1_Relay( MyDialog3 dlg ) { _target = dlg; }
    public void click( ClickEvent event ) {
        _target.pb_1_Click( event );
    }
    private MyDialog3 _target;
}
class pb_2_Relay implements ClickListener {
    public pb_2_Relay( MyDialog3 dlg ) { _target = dlg; }
    public void click( ClickEvent event ) {
        _target.pb_2_Click( event );
    }
    private MyDialog3 _target;
}
public class MyDialog3 extends java.awt.Dialog {
    public MyDialog3() {
        super( null, true );
        pb_1 = new PushButton( "pb_1" );
        pb_1.addClickListener( new pb_1_Relay( this ) );
        pb_2 = new PushButton( "pb_2" );
        pb_2.addClickListener( new pb_2_Relay( this ) );

add( "Center", pb_1 ); add( "South", pb_2 ); pack(); } public void pb_1_Click( ClickEvent event ) { System.out.println( "You clicked pb_1!" ); } public void pb_2_Click( ClickEvent event ) { System.out.println( "You clicked pb_2!" ); } private PushButton pb_1; private PushButton pb_2; public static void main( String args[] ) { MyDialog3 app = new MyDialog3(); app.show(); } }

Back to Article


Copyright © 1997, Dr. Dobb's Journal