You are on page 1of 18

Java Beans Self-Contained Components The specification of JavaBeans adds to the Java platform the capability to create a complete

application by linking together a set of self-contained components. Microsoft's Visual Basic and Borland's Delphi are both examples of applications that allow users to build full-blown applications by combining independent components. The success and popularity of these two applications alone speak volumes to the success of this style of application building. Just as with other models, there is no restriction on the size or complexity of a JavaBeans component. In principle, JavaBeans components (or just Beans) can range from widgets and controls to containers and applications. The philosophy behind JavaBeans is to provide easy-to-implement functionality for the former, while allowing enough flexibility for the latter. In the spirit of this philosophy, you'll see how to create and use fairly simple Beans. However, after you finish reading this chapter, you'll have learned enough to create larger and more complex Beans, if you choose to do so. Important Concepts in Component Models JavaBeans provides a platform-neutral component architecture. This is in contrast to platform-specific component architectures such as COM for the Windows platform. A COM component written to be placed inside a corresponding container such as Microsoft Word cannot be used in containers based on other component architectures. Because JavaBeans is an architecture-neutral specification, Beans can be placed into any container for which a bridge exists between JavaBeans and the container's component architecture. This means that a JavaBean can be used in containers as diverse as Microsoft Word, Internet Explorer, and Lotus Notes, among others. To accomplish this seemingly impossible feat, the JavaBeans specification adopts features common with the other popular component models. In particular, these features include the following: Component fields or properties Component methods or functions Events and intercommunication State persistence and storage Note If you are familiar with component models already, you don't necessarily need to read this section. You can jump right into the next section, "The Basics of Designing a JavaBean."

Component Fields or Properties For a component to be useful, it has to have a set of properties that defines its state. For example, if you were to design a component that displayed some text, one of the properties of that component might be the foreground color of the font. Another property might be the type and size of the font. Taken as a whole, the set of properties that make up a component also defines its state. For example, if the properties of one component com-pletely match that of another, they are in the same state. Properties are often used to define not only the appearance but also the behavior of components. This is because a component need not have any appearance at all. For example, a component in a spreadsheet might calculate the interest earned on some earnings column. If that component is not capable of displaying the data, it probably shouldn't have any properties associated with appearance. It is likely, however, that it will have a property that defines the current interest rate. Properties can range from Boolean values, to strings, to arrays, to other components. They can also be interdependent. Following the same example given previously, a component that displays the earnings column might want to be notified if the interest rate property of the other component changes.

Component Methods or Functions The API, so to speak, of a component is the collection of methods or functions that it contains that other components and containers can call. There has to be some way for a container to modify a component's properties, notify it of an event, or execute some functionality. The various component models differ in how they make the properties and methods of their components available to other components. Because entire books have been written on how this is implemented for different models, it's clear that this is a common feature of component models. This topic will be discussed as it relates to JavaBeans in the section on introspection later in the chapter. Events and Intercommunication A component by itself is a lonely component. Although some components might have extensive functionality and many properties, in the true spirit of a component, it should be the most useful when exercised in conjunction with other components. So if two components are sitting together in a container, how do they talk? How does one let the other know when it has done something the other really ought to know about? The method by which most components communicate is through event transmission. One component (or the container) undergoes some action causing it to generate an event. For example, an event is generated when you click a button. Depending on the model, the component will notify the container, the interested components, or both of the events. At the same time, the objects in the environment also act on events delivered to them. For example, the File dialog box displays itself when it hears that you just clicked a Browse button. State Persistence and Storage It is important for components to remember their state. This is so common that you might not even recognize it. When you open an application and it remembers the size and position of its window when it was last closed, it is maintaining (to some degree) a persistent state. Also important is the capability to store and retrieve components. Sun Microsystems, Inc., likes to call this packaging. This is especially important in a distributed environment where the components are likely to be served up over a network. The Basics of Designing a JavaBean All good programmers recognize the importance of the design phase in programming. Thus, you'll start out by addressing how to design a Bean. As you will learn later, the way in which you design your Bean directly affects the way it behaves in containers. For example, the names you choose for the methods should follow specific design specifications. If you start from the beginning with these rules in mind, allowing your Bean to participate in introspection does not require any additional programming on your part. The concept of introspection was introduced in Chapter 28, "Reflection," but you'll see more of how it relates to JavaBeans a little later. Designing a Bean consists of the following steps: 1. 2. 3. 4. Specifying the Bean's properties Specifying the events the Bean generates or responds to Defining which properties, methods, and events the Bean exposes to other Beans or to its container Deciding miscellaneous issues, such as whether the Bean has its own Customization dialog box or whether it requires some prototypical state information

You'll start by designing some Beans. For the sake of a simple example, assume that you are developing two Beans; one Bean allows text to be entered into it, and the other displays some text. You can imagine how these Beans might be useful. By placing these two Beans into a container, you can use one to enter text that the other will then display. What types of properties do you think these Beans need to have? What events are these Beans interested in hearing about? What events do these Beans generate? Do these Beans expose all their properties and events, or just some? At this point, you might not know the answers to these questions. The process is the important concept here; the details will become clearer as you progress through the

chapter. Regardless, the first thing any Bean needs is a name. In this chapter, the sample Beans will be called TextDisplayer and TextReader. Specifying the Bean's Properties The TextDisplayer and TextReader Beans definitely need to have a property defining the text they hold. For example, say that the TextDisplayer Bean also contains properties defining the background color, font, and font color. TheTextReader Bean also contains a pro-perty that defines how many columns of characters it can display. Table 29.1 lists the TextDisplayer Bean's properties and the Java types that will be used to implement them. Table 29.2 lists theTextReader Bean's properties and the Java types that will be used for them.

Table 29.1. The TextDisplayer Bean's Properties and Java Types Property Name OutputText BGColor TextFont FontColor Java Type java.lang.String java.awt.Color java.awt.Font java.awt.Color

Table 29.2. The TextReader Bean's Properties and Java Types Property Name InputText Width Java Type java.lang.String int

Specifying the Events the Bean Generates or Responds To The TextDisplayer Bean must respond to an event specifying that its text should change. Specifically, it must update its OutputText property and redraw itself. The TextReader Bean doesn't need to respond to any events, but it must generate (or fire) an event when the user changes its InputText property. This type of event is called aPropertyChangeEvent, for obvious reasons. Properties, Methods, and Event Exposure Because these Beans are particularly simple, you don't need to hide anything from the Beans'container or the other Beans interested in them. JavaBeans provides a mechanism for you that will use the names of your methods to extract the names and types of your properties and events. Rest assured that you will learn how this works as you go along. Later in the chapter, you'll learn how to explicitly define what information in a Bean is exposed to its environment. Initial Property Values and Bean Customizers To keep this example simple, assume that your Beans do not need any prototypical information (you'll define default values for all their properties) and that they do not have their own Customization dialog box. This means that your Beans have a predefined state when they're instantiated and that they use the standard PropertyEditors for their properties. If you were designing a Bean that displays an HTML page, for example, specifying default values might not be possible. You would need to know what file to display when the Bean is instantiated. Table 29.3 shows yourTextDisplayer Bean's properties and the default values it will hold. Likewise, Table 29.4 is for the TextReader Bean.

Table 29.3. Default Property Values for the TextDisplayer Bean Property Name Default Value

OutputText BGColor TextFont FontColor

"TextDisplayer" java.awt.Color.white Courier, normal, 12 java.awt.Color.black

Table 29.4. Default Property Values for the TextReader Bean Property Name InputText Width Default Value "" (an empty string) 40

At this point, you've designed your Beans enough to begin coding. This will be an additive process because you haven't learned how to make the Beans do anything yet. You'll see the relevant pieces of code as you build the example. InFigure 29.1, you can see your Beans hard at work inside the BeanBox. The BeanBox is a JavaBeans container that you can download from Sun's Web site; it's included in the BDK, or Beans Development Kit. Figure 29.1. Sun's BeanBox shows the TextDisplayer and TextReader Beans.

Right now, the Beans are completely isolated. Because you haven't given the Beans any functionality yet, this is about as good as it gets. The preliminary code needed to instantiate and draw the TextDisplayer Bean is shown in Listing 29.1. Notice that there is no class hierarchy required for a Bean. It is not necessary for a Bean to extend any particular superclass, although it must implement the Serializable interface (or extend a class such as Canvas that does). Listing 29.1 TextDisplayer.javaPreliminary Code for the TextDisplayer Bean import java.awt.*; import java.beans.*; public class TextDisplayer extends Canvas implements PropertyChangeListener { // default constructor for this Bean. This is the constructor that an // application builder (such as Visual Basic) would use. public TextDisplayer() { this( "TextDisplayer", Color.white, new Font( "Courier", Font.PLAIN, 12 ), Color.black ); } // custom constructor for this Bean. This is the constructor you would // likely use if you were going to do all your coding from scratch. public TextDisplayer( String OutputText, Color BGColor, Font TextFont, Color FontColor ) { this.OutputText = OutputText; this.BGColor = BGColor; this.TextFont = TextFont; this.FontColor = FontColor; setFont( TextFont ); // set the Canvas's font. setBackground( BGColor ); // set the Canvas's background color. setForeground( FontColor ); // set the Canvas's foreground color. } // this Bean's properties. protected String OutputText; protected Color BGColor, FontColor; protected Font TextFont; // override the Canvas's paint method to display the text public void paint( Graphics g ) { // simplistic implementation to display a string

g.drawString( OutputText, 10, 20 ); } // implement the PropertyChangeListener interface public void propertyChange( PropertyChangeEvent evt ) { // add code here to respond to a change in a bound property } }

The draw method implementation in Listing 29.1 is obviously not as robust as you would want for a reusable component, but it's adequate for demonstrating how the Bean works. You might have also noticed that you have specified that your Bean implement an interface called PropertyChangeListener. This is so that the TextDisplayer Bean can update itsOutputText property by receiving an event through its propertyChange method. How that works will be discussed in more detail later in the chapter. The preliminary code needed to instantiate your TextReader Bean is shown in Listing 29.2. Listing 29.2 TextReader.javaPreliminary Code for the TextReader Bean import java.awt.*; import java.awt.event*; import java.beans.*; public class TextReader extends TextField { // default constructor for this Bean. This is the constructor that an // application builder (such as Visual Basic) would use. public TextReader() { this( "", 40 ); } // custom constructor for this Bean. This is the constructor that you // would likely use if you were doing your coding from scratch. public TextReader( String InputText, int Width ) { super( InputText, Width ); this.InputText = InputText; this.Width = Width; setEditable( true ); // update the InputText property when the enter key is // pressed within the TextField this.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { setInputText(getText()); } } ); } // this Bean's properties. protected String InputText; protected int Width; // getter method for the InputText property. public synchronized String getInputText() { return InputText; } // setter method for the InputText property. public synchronized void setInputText( String newText ) { String oldText = InputText; InputText = newText; setText( InputText ); // uncomment this statement after the Listing 29.4 additions are made // changeAgent.firePropertyChange( "inputText", new String( oldText ), // new String( newText ) ); }

Creating and Using Properties Previously in Figure 29.1, notice that the TextDisplayer Bean displayed itself with a white background and black text. It did so because that's how you set its properties in the default constructor. If you had set the FontColor property to red, it would have displayed the text in red. If the properties of a component cannot be changed by another Bean (or any other class that uses it), the usefulness of the Bean is reduced, as well as the reusability. For example, if you used theTextDisplayer Bean in an accounting package, you would need to change the Bean's FontColor property to red to indicate a negative value. So how do you let other Beans know that they can set (or read) this property? If you're coding from scratch, you can look at the documentation for the Bean. But what if you're in an application builder? Luckily, there's a way to do this without incurring any extra coding on your part. You'll see how that works a little later. Two types of properties are supported by JavaBeans: single-value and indexed. In addition, properties can also be bound or constrained. A single-value property is a property for which there is only one assigned value at a time. As the name suggests, an indexed property has several values, each of which has a unique index. If a property is bound, it means that some other Bean is dependent on that property. In the continuing example, the TextReader Bean'sInputText property is bound to the TextDisplayer Bean; the TextReader must notify the TextDisplayer whenever its InputText field changes. A property is constrained if it must check with other components before it can change. Note Constrained properties cannot change arbitrarily. One or more components might not allow the updated value.

Single-Value Properties All properties are accessed by calling methods on the owning Bean instance. Readable properties have a getter method used to read the value of the property. Writable properties have a setter method used to change the value of a property. These methods are not constrained to returning the value of a field declared within a Bean; they can also perform calculations and return some other value. All the properties the Beans have in the examples here are single-value. At this point, you're ready to start talking about introspection. The method by which other components learn of your Bean's properties depends on a few things. In general, however, this process is called introspection. In fact, the classjava.beans.Introspector is the class that provides this information for other components. The Introspector class traverses the class hierarchy of a particular Bean. If it finds explicit information provided by the Bean, it uses that. However, it uses design patterns to implicitly extract information from those Beans that do not provide information. Note that this is what happens to your Beans in the example. Specific design rules should be applied when defining accessor methods so that the Introspector class can do its job. If you choose to use other names, you can still expose a Bean's properties, but it requires you to supply a BeanInfo class. For more about what a BeanInfo class is, see the"Introspection: Creating and Using BeanInfo Classes" section later in this chapter. Here are the design patterns you should use: public void set<PropertyName>( <PropertyType> value ); public <PropertyType> get<PropertyName>(); public boolean is<PropertyName>(); Note that the last pattern is an alternative getter method for Boolean properties only. setter methods are allowed to throw exceptions if they so choose. The accessor methods for the TextDisplayer Bean are shown in Listing 29.3. Notice that all the accessor methods have been declared as synchronized. Even though nothing serious could happen in this Bean, you should always assume that your Beans are running in multithreaded environments. Using synchronized accessor methods helps prevent race conditions from forming. TextReader requires a similar set of accessor methods for its properties. Listing 29.3 TextDisplayer.javaThe the TextDisplayer Bean Accessor Methods for the Properties in

public synchronized String getOutputText() { return( OutputText ); }

public synchronized void setOutputText( String text ) { OutputText = text; // force a paint Graphics g = getGraphics(); if ( g != null ) { update(g); } } public synchronized Color getBGColor() { return( BGColor ); } public synchronized void setBGColor( Color color ) { BGColor = color; setBackground( BGColor ); // set the Canvas's background color. repaint(); } public synchronized Font getTextFont() { return( TextFont ); } public synchronized void setTextFont( Font font ) { TextFont = font; setFont( TextFont ); // set the Canvas's font. } public synchronized Color getFontColor() { return( FontColor ); } public synchronized void setFontColor( Color color ) { FontColor = color; setForeground( FontColor ); // set the Canvas's foreground color. repaint(); }

Figure 29.2 shows you what the property sheet of Sun's BeanBox shows for your TextDisplayer Bean. Notice that you can see the properties of the parent class, too. Your Bean inherits from java.awt.Canvas, which inherits fromjava.awt.Component, which inherits from java.lang.Object. The additional properties that you see are from thejava.awt.Component class. This illustrates the principal drawback of using the automatic JavaBeans introspection methods. In your own Beans, this might be the motivation for providing a BeanInfo class. Again, more on that is in the"Introspection: Creating and Using BeanInfo Classes" section later in this chapter.

Indexed Properties An indexed property takes on multiple values simultaneously and can be treated as an array. You read or write the values associated with an indexed property by specifying an integer index or working with the entire array at once. The design patterns for indexed properties are as follows: public public public public <PropertyType> get<PropertyName>( int index ); void set<PropertyName>( int index, <PropertyType> value ); <PropertyType>[] get<PropertyName>(); void set<PropertyName>( <PropertyType>[] value );

To illustrate, assume there is a Meal property that consists of an array of Courses: public public public public Course getMeal( int course ); void setMeal( int course, Course dish ); Course[] getMeal(); void setMeal( Course[] dishes );

Bound Properties As the programmer, you can decide which of your Bean's properties can be bound to other components. To provide bound properties in your Beans, you must define the following methods: public void addPropertyChangeListener( PropertyChangeListener l ); public void removePropertyChangeListener( PropertyChangeListener l ); To provide this functionality on a per-property basis, the following design pattern should be used: public void add<PropertyName>Listener( PropertyChangeListener l ); public void remove<PropertyName>Listener( PropertyChangeListener l ); Beans wanting to bind to other components'properties the PropertyChangeListener interface, which consists of the following method: public void propertyChange( PropertyChangeEvent evt ); Whenever a bound property in a Bean is updated, it must call the propertyChange() method in all the components that have registered with it. The class java.beans.PropertyChangeSupport is provided to help you with this process. The code in Listing 29.4 shows you what is required in the TextReader Bean to allow its InputText property to be bound. The setInputText method shown here was included previously in Listing 29.2, but the call to firePropertyChangecan now be uncommented. Listing 29.4 TextReader.javaCode the TextReader Bean a Bound Property Required to Make the InputText Property of should implement

// setter method for the InputText property. public synchronized void setInputText( String newText ) { String oldText = InputText; InputText = newText; setText( InputText ); changeAgent.firePropertyChange( "inputText", new String( oldText ), new String( newText ) ); } // these two methods allow this Bean to have bound properties. public void addPropertyChangeListener( PropertyChangeListener l ) { changeAgent.addPropertyChangeListener( l ); } public void removePropertyChangeListener( PropertyChangeListener l ) { changeAgent.removePropertyChangeListener( l ); } protected PropertyChangeSupport changeAgent = new PropertyChangeSupport( this );

After you make the Listing 29.4 changes to TextReader, you can implement the propertyChange method ofTextDisplayer that was declared previously in Listing 29.1. You should implement this method to respond to changes in the TextReader InputText property as follows: // implement the PropertyChangeListener interface public void propertyChange( PropertyChangeEvent evt ) {

// only interested in the inputText property of TextReader if ( evt.getPropertyName().equals("inputText") ) { // display the new value of the TextReader property setOutputText( (String)evt.getNewValue() ); } } Notice that this method refers to the property name as "inputText" even though we declared the TextReaderproperty using the identifier InputText. In general, the rules used to extract property names based on getter andsetter method declarations produce names that begin with a lowercase letter. The exception to this rule occurs when an accessor method contains a property name that begins with two or more uppercase letters. For example,getToolKitName corresponds to a property named toolKitName, but getGUIToolKitName corresponds to a property named GUIToolKitName. Troubleshooting Tip If you have trouble configuring a bound property, the "Troubleshooting"section at the end of this chapter. see "Bean Events Are Not Propagated" in

Constrained Properties The process for providing constrained properties in your code is also fairly straightforward. You must define the following methods in your Bean: public void addVetoableChangeListener( VetoableChangeListener l ); public void removeVetoableChangeListener( VetoableChangeListener l ); Just as with bound properties, you can make individual properties constrained using the following design pattern: public void add<PropertyName>Listener( VetoableChangeListener l ); public void remove<PropertyName>Listener( VetoableChangeListener l ); Beans intended to constrain other components'properties the VetoableChangeListener interface, which consists of the following method: should implement

public void vetoableChange( PropertyChangeEvent evt ) throws PropertyVetoException; Whenever a constrained property in a Bean is updated, it must call the vetoableChange() method in all the components that have registered with it. If a registered component disapproves of the property change, it can throw aPropertyVetoException to request that the previous value be retained instead. There is also a support class to help make this process easier. Use the class java.beans.VetoableChangeSupport to help manage your vetoable properties. The code in Listing 29.5 shows you what is required in the TextReader Bean to allow its Width property to be constrained. Listing 29.5 TextReader.javaCode Required to Make the Width Property of the TextReader Bean a Constrained Property // setter method for the Columns property. public synchronized void setWidth( int newWidth ) throws PropertyVetoException { int oldWidth = Width; vetoAgent.fireVetoableChange( "width", new Integer( oldWidth ), new Integer( newWidth ) ); // no one vetoed, so change the property. Width = newWidth; setColumns( Width ); Component p = getParent(); if ( p != null ) { p.invalidate(); p.doLayout();

} changeAgent.firePropertyChange( "width", new Integer( oldWidth ), new Integer( newWidth ) ); } // getter method for the Columns property. public synchronized int getWidth() { return Width; } // these two methods allow this Bean to have constrained properties. public void addVetoableChangeListener( VetoableChangeListener l ) { vetoAgent.addVetoableChangeListener( l ); } public void removeVetoableChangeListener( VetoableChangeListener l ) { vetoAgent.removeVetoableChangeListener( l ); } protected VetoableChangeSupport vetoAgent = new VetoableChangeSupport( this );

In this particular example, the Width property is bound and constrained. A property does not have to be bound to be constrained. For example, to make the Width property constrained but not bound, you would remove the following line from Listing 29.5: changeAgent.firePropertyChange( "width", new Integer( oldWidth ), new Integer( newWidth ) );

Using Events to Communicate with Other Components The whole idea behind the JavaBeans component model is to provide a way to create reusable components. To do this, Beans must be able to communicate with the other Beans in their environment and with their container. This is accomplished by means of Listener interfaces. You've already seen some of this with the PropertyChangeEvent from the last section. This section goes into more detail about how Listeners work. Beans use the same event-handling scheme as AWT and Swing. This means that if your Bean needs to hear about events coming from another Bean, it must register itself with that Bean. To do this, it must implement the Listenerinterface for the event of interest. At the same time, if your Bean is no longer interested in hearing about some other Bean's event, it must unregister itself with that Bean. Any event that a Bean wants to fire must inherit from thejava.util.EventObject class. For simple events, the java.util.EventObject class itself could be used; however, as with java.lang.Exception, using child classes provides clarity and is preferred. All Listener interfaces must inherit from the java.util.EventListener interface, and the same subclassing convention applies. The event handling method of a Listener interface should follow the design pattern for introspection as shown here: void <EventOccuranceName>( <EventObjectType evt ); Note that <EventObjectType> must inherit from java.util.EventObject. Here is an example of an event handler for a DinnerServedListener interface: // DinnerServedEvent inherits from // java.util.EventObject. void dinnerServed( DinnerServedEvent evt );

There is no restriction preventing an event-handler method from throwing an exception. In addition, any one Listenerinterface can have any number of related event handlers. There are two types of events that components can listen for: multicast events and unicast events.

Multicast Events Multicast events are the most common types of events. The PropertyChangeEvent, which you have already been exposed to, is a multicast event because there can be any number of listeners. In that example, you hadaddPropertyChangeListener() and removePropertyChangeListener() methods, which allowed other components to register with the Bean as being interested in hearing when a bound property changed. The process is the same for any other type of multicast event, and the registration methods should follow the design pattern for introspection as shown here: public synchronized void add<ListenerType>( <ListenerType> listener ); public synchronized void remove<ListenerType>( <ListenerType> listener ); The keyword synchronized is not actually part of the design pattern. It is included as a reminder that race conditions can occur, especially with the event model, and precautions must be taken.

Unicast Events Unicast events don't occur nearly as often as their counterpart, but they're just as useful. Unicast events can have only one listener. If additional components attempt to listen to the unicast event, ajava.util.TooManyListenersException will be thrown. The following design pattern should be used when declaring unicast events: public synchronized void add<ListenerType>( <ListenerType> listener ) throws java.util.TooManyListenersException; public synchronized void remove<ListenerType>( <ListenerType> listener );

Event Adapters In some cases, it might be necessary to build an event adapter class that can transfer an event to a component. This comes into play especially for an application builder because the application doesn't know until runtime how the components will be linked together or how they will interact with each other's events. An event adapter intervenes in the normal event-handling scheme by intercepting the events normally meant for another component. For example, assume that a user places a button and a text box in an application builder. If the user wants the text box to fill with the word "Pressed" when the button is pressed, the application builder can use an event adapter to call a method containing the user-generated code needed to do it. Here's how it will eventually work:

1. 2.

The event adapter registers with the event source. In an addSomeEventListener() method on the event source component.

other

words,

it

calls

The event source component fires an event by calling the event adapter's event-handler method, someEvent().Keep in mind that the event source component doesn't care whether it's calling an event adapter or a true event listener. At this point, with the event fired, it can continue with its business. The event adapter calls the specific user-designed method on the final target component.

3.

4.

The code in the user-designed method fills in the text box component with the "Pressed" text.

Sometimes it helps to see some code. Listing 29.6 contains some pseudocode you can examine to see how an event adapter is written. The code in the example builds off the procedure listed previously. You won't be able to compile this code (notice the class keywords have been changed to pseudoclass), but it serves as an example you can build off of in your own Beans.

Listing 29.6 AdaptorExample.javaPseudocode Showing How to Implement an Adapter Class; This Code Might Be Generated by an Application Builder // this pseudoclass example uses a unicast mechanism to keep things simple. public interface SomeEventListener extends java.util.EventListener { public someEvent( java.util.EventObject e ); } public pseudoclass button extends java.awt.Button { public void synchronized addSomeEventListener( SomeEventListener l ) throws java.util.TooManyListenersException { if ( listener != null ) { listener = l; } else throw new java.util.TooManyListenersException; } private void fireSomeEvent() { listener.someEvent( new java.util.EventObject( this ) ); } private SomeEventListener listener = null; } public pseudoclass eventAdaptor implements SomeEventListener { public eventAdaptor( TargetObject target ) { this.target = target; } someEvent( java.util.EventObject e ) { // transfer the event to the user generated method. target.userDefinedMethod(); } private TargetObject target; } public pseudoclass TargetObject { public TargetObject() { adaptor = new eventAdaptor( this ); } public userDefinedMethod() { // user generated code goes here. } private eventAdaptor adaptor; }

Introspection: Creating and Using BeanInfo Classes You've already seen in the preceding sections and in the two Beans you designed how to use design patterns to facilitate automatic introspection. You also saw that the automatic introspection mechanism isn't perfect. If you look back at Figure 29.2, you'll see an example of this. Introspection is probably the most important aspect of JavaBeans because without it a container can't do anything with a Bean other than display it. As

you become proficient at designing your own Beans, you'll find that you sometimes need to provide additional introspection information for the users of your Beans. In the case of your Beans, this is to hide the parent class's properties to clear up ambiguities. The java.beans.Introspector class, as discussed earlier in the chapter, does all the pattern analysis to expose the properties, methods, and events that a component has. As a first step, however, this class looks to see whether aBeanInfo class is defined for the Bean it's inspecting. If it finds one, it doesn't do any pattern analysis on the areas of the Bean for which the BeanInfo class supplies information. This means that you can selectively choose which information you want to provide and which information you want to be derived from analysis. To show how this is done, you'll design a BeanInfo class for our TextDisplayer Bean. The first thing you need to do is define what information you'll provide and what you'll leave up to the Introspectorclass to analyze. For the sake of example, say that you'll choose to provide the properties of your Bean, and you'll let theIntrospector class use analysis to expose the events and methods. Table 29.5 shows the names of theTextDisplayer Bean's properties and the user-friendly names you want to display. With that information defined, you can start working on your BeanInfo class, TextDisplayerBeanInfo.class. Notice how you appended "BeanInfo" to the class name. That's an introspection design pattern; the Introspector class looks for BeanInfo information by appending "BeanInfo" to the class name of the Bean it's currently analyzing.

Table 29.5. The TextDisplayer Bean's Properties and User-Friendly Names Property Name OutputText BGColor TextFont FontColor User-Friendly Name "Text String" "Background Color" "Text Font" "Text Color"

All BeanInfo classes must implement the java.beans.BeanInfo interface. At first glance, that seems difficult; there are eight methods in the java.beans.BeanInfo interface! But remember the Introspector class has a set procedure for the way it looks for information. For the sake of clarity, that procedure is shown in the following list:

1. 2.

The Introspector class looks for a BeanInfo class for the Bean it's analyzing. If a BeanInfo class is present, each method in the BeanInfo class is called to find out whether it can provide any information. The Introspector class will use implicit analysis to expose information for which the BeanInfo class denies any knowledge (returns a null value). If no BeanInfo class is found, the Introspector class will use implicit analysis for all the methods in the java.beans.BeanInfo interface. The Introspector class then checks to see whether it has obtained explicit information for each of the methods in the BeanInfo interface. If it hasn't, it steps into the parent class (if one exists) and starts the process over for only those methods that it had to use analysis on. When the Introspector class has information from a BeanInfo class for all the methods in thejava.beans.BeanInfo interface, or when there are no more parent classes to explore, the Introspector class returns its results.

3. 4.

To make your life easier as a programmer, Sun has provided a prebuilt class, java.beans.SimpleBeanInfo, which returns a null value for all the BeanInfo methods. That way, you can inherit from that class and only override the methods you choose. Listing 29.7 shows the BeanInfo class for the TextDisplayer Bean. Notice how you only override the getPropertyDescriptors() method. The parent class returns null for all the other methods in thejava.beans.BeanInfo interface. Listing 29.7 TextDisplayerBeanInfo.javaThe Entire BeanInfo Class Showing How to Provide Property Information import java.beans.*; for the TextDisplayer Bean

public class TextDisplayerBeanInfo extends SimpleBeanInfo { // override the getPropertyDescriptors method to provide that info. public PropertyDescriptor[] getPropertyDescriptors() { PropertyDescriptor[] properties = new PropertyDescriptor[4]; try { properties[0] = new PropertyDescriptor( "Text String", BeanClass, "getOutputText", "setOutputText" ); properties[1] = new PropertyDescriptor( "Text Color", BeanClass, "getFontColor", "setFontColor" ); properties[2] = new PropertyDescriptor( "Text Font", BeanClass, "getTextFont", "setTextFont" ); properties[3] = new PropertyDescriptor( "Background Color", BeanClass, "getBGColor", "setBGColor" ); } catch( IntrospectionException e ) { return( null ); // exit gracefully if you get an exception. } return( properties ); } private Class BeanClass = TextDisplayer.class; }

Take a second to look at the try-catch clause in Listing 29.7. Notice how you return a null value if you catch ajava.beans.IntrospectionException. If you catch this exception, it usually means that you've provided an incorrectgetter or setter method name. You should always return a null value if you catch this exception so that theIntrospector class can still analyze your Bean. You should be able to extend this example to override the other methods in the java.beans.BeanInfo interface. Figure 29.3 shows the PropertySheet window of Sun's BeanBox for your TextDisplayer Bean. Notice how the user-friendly names for the properties have been used, and the parent class's properties are gone. Sweet success! Customization: Providing Custom PropertyEditors and GUI Interfaces So far, you have seen how to create a Bean; how to expose its properties, methods, and events; and how to tweak the introspection process. You might have noticed from the figures that the properties of a Bean have a PropertyEditor.For example, look back at Figure 29.3. In the PropertySheet window, next to the "Text String" label, there's aTextField component already filled with the value of the OutputText property. You didn't supply any code for this component, so how did Sun's BeanBox know to provide it? The answer is that the BeanBox application asked thejava.beans.PropertyEditorManager what the default PropertyEditor was for an object of typejava.lang.String and displayed it. Just because PropertyEditors and Customizers require a GUI environment doesn't mean a Bean can't function without one. For example, a Bean designed to run on a server might not use (or need) a GUI environment at all. Thejava.beans.Beans class and the java.beans.Visibility interface allow Beans to exhibit different behavior in GUI and non-GUI environments. PropertyEditors and the PropertyEditorManager The class java.beans.PropertyEditorManager provides default PropertyEditors for the majority of the Java class types. So, if you use only native Java data types and objects, you're all set. But what if you design a Bean that has a property for which there's no default PropertyEditor? You'll run into this problem any time you design a custom property type. For those cases where there is no default PropertyEditor, you have to provide your own. Actually, you could redesign all the default PropertyEditors, too, if you choose, but you would only do this in rare cases, so this won't be discussed here. This means that you have to provide an additional class, by appending Editor to the class name, that the PropertyEditorManager can use. In most cases, you provide a subclass of java.awt.Component. The property sheet for your component will then pop up your custom PropertyEditor to allow your custom property to be edited. You won't actually

design a custom PropertyEditor here because the majority of Beans won't require it, but an explanation of how to do it will be included. The requirements of a PropertyEditor are as follows:

1. 2.

Custom PropertyEditors must inherit from java.awt.Component so that they can be displayed in a property sheet. Note that this could mean inheriting from an AWT component such as java.awt.TextField. Custom PropertyEditors must derive their class name by postfixing Editor to the property class name unless they register themselves with the PropertyEditorManager for their container (see step 3). For example, thePropertyEditor for a custom property type CustomProperty.class must be namedCustomPropertyEditor.class. For custom PropertyEditors that do not follow the standard naming convention in step 2, the custom property type must register itself with the container's PropertyEditorManager by calling the registerEditor() method. Custom PropertyEditors always must fire a PropertyChange event to update the custom property. This is a must! Otherwise, the container has no way of knowing to update the component.

3.

4.

You can provide your own property sheet; in fact, for complex Beans, this is absolutely imperative. Property sheets by nature are simple and relatively not user-friendly. The following section discusses how to override the property sheet mechanism to provide your own customization dialog boxes. Customization Editor All application builders have to implement some method of customizing the Beans placed into their containers. Thus, thePropertyEditor mechanism and the idea of a property sheet were born. But what about the special cases where a Bean can be customized several different ways, or there are dozens of properties? The solution to this problem is calledcustomizers. Bean developers can optionally supply customizer classes with their Beans to be used in place of standard property sheets. Even though the property sheet mechanism works just fine for the TextReader Bean, you'll create a customizer class anyway, to learn how it's done. To implement a customizer class, a Bean must also provide a BeanInfo class. The class name of a Bean's customizer class is determined from a call to the getBeanDescriptor() method of the java.beans.BeanInfo interface. This is a little bit different from what you've encountered so far. There is no default introspection design pattern for customizers; you must provide a BeanInfo class, even if the only information it provides is a BeanDescriptor. In fact, this is what you do for the TextReaderBeanInfo.class shown in Listing 29.8. Notice how the class inherits fromjava.beans.SimpleBeanInfo; the parent class implements the java.beans.BeanInfo class, and you override thegetBeanDescriptor() method so that it returns something meaningful. Listing 29.8 TextReaderBeanInfo.javaThe BeanInfo Class for the TextReader Bean Showing How to Provide Customizer Class Information import java.beans.*; public class TextReaderBeanInfo extends SimpleBeanInfo { // override the getBeanDescriptor method to provide a customizer. public BeanDescriptor getBeanDescriptor() { return( new BeanDescriptor( BeanClass, CustomizerClass ) ); } private Class BeanClass = TextReader.class; private Class CustomizerClass = TextReaderCustomizer.class; } Although there isn't a design pattern for it, it's customary to name a customizer postfixing Customizer to the class name. Notice that you the TextReader customizer TextReaderCustomizer.class. This is a good habit to get into. class by named

The programmer has a tremendous amount of freedom when designing customizer classes. There are only two restrictions: The class must inherit from java.awt.Component, so that it can be placed in a Panel or Dialog, and it must implement the java.beans.Customizer interface. The customizer class is given a reference to the target component through a call to the setObject() method. After this point, what the customizer class does is its business, for the most part. Remember, however, that you'll be required (by the compiler) to acknowledge constrained properties because their accessor methods might throw PropertyVetoExceptions. Finally, the java.beans.Customizerinterface includes functionality for PropertyChangeListeners. Because the Bean's container might register itself as a listener with the customizer class, any property updates should be followed by a call to firePropertyChange(). The easiest way to do this is by using a java.beans.PropertyChangeSupport class as was done when discussing bound properties earlier. Listing 29.9 shows most of the code for the TextReaderCustomizer class. Some of the AWT-specific code was removed for clarity. Take a look at the handleEvent() method. This method is called by AWT when the user enters data. Notice how you were forced to catch ProperyVetoExceptions for the setWidth() method? You can also see how thePropertyChangeListener methods are used appropriately. Figure 29.4 shows what the customizer looks like when called up from within Sun's BeanBox.

Listing 29.9 TextReaderCustomizer.javaThe Code from TextReaderCustomizer.java Showing How to Implement a Customizer Class import java.awt.*; import java.beans.*; public class TextReaderCustomizer extends Panel implements Customizer { public TextReaderCustomizer() { setLayout( new BorderLayout() ); } public void setObject( Object target ) { component = (TextReader)target; // generate the User Interface (code removed for clarity) } public void processEvent( AWTEvent event ) { if ( event.getID() == Event.KEY_RELEASE && event.getSource() == InputText ) { String old_text = component.getInputText(); String text = InputText.getText(); component.setInputText( text ); changeAgent.firePropertyChange( "inputText", old_text, text ); } else if ( event.getID() == Event.KEY_RELEASE && event.getSource() == Width ) { int old_width, width; old_width = component.getWidth(); try { width = Integer.parseInt( Width.getText() ); try { component.setWidth( width ); changeAgent.firePropertyChange( "width", new Integer( old_width ), new Integer( width ) ); } catch( PropertyVetoException e ) { // do nothing wait for acceptable data. } } catch( NumberFormatException e ) { // do nothing wait for better data. } } super.processEvent( event ); }

public void addPropertyChangeListener( PropertyChangeListener l ) { changeAgent.addPropertyChangeListener( l ); } public void removePropertyChangeListener(PropertyChangeListener l) { changeAgent.removePropertyChangeListener( l ); } private TextReader component; private TextField InputText, Width; private PropertyChangeSupport changeAgent = new PropertyChangeSupport( this ); }

Providing Alternative Behavior in Non-GUI Environments Unfortunately, a GUI interface is not always available to a Bean. The most likely reason for this is that the Bean is being run in the background or on a server. Whatever the case, Beans that need to provide alternative or additional behavior in non-GUI environments can do so by using the java.beans.Beans class and the java.beans.Visibility interface. The static methods isDesignTime() and isGuiAvailable() in the java.beans.Beans class can be used to check whether the Bean is being used in an application builder and a GUI environment is available. The methodisDesignTime() returns true if the Bean is in an application builder and false if not. The method isGuiAvailable()returns true if a GUI environment is available to the Bean, and false if not. Just because a GUI environment is available doesn't necessarily mean a container wants a Bean to use it. Similarly, a container might want to know whether a Bean isn't using the GUI environment, or even whether it needs one. A Bean and its container can communicate these things by implementing the java.beans.Visibility interface. The vast majority of Beans have no need for this interface, and it isn't necessary to implement it unless a Bean plans to use it. There are four methods in the interface: public abstract boolean avoidingGui() This method is called by a container to ask whether a Bean is currently avoiding the GUI environment. A Bean should return true for this method if it is actively avoiding the GUI environment. Notice that this is not the same as indicating that it doesn't need the GUI environment. For example, a container might use this information to free up resources being used by the GUI environment if a call to this method returns true. public abstract void dontUseGui() This method is called by the container to tell the Bean that even though a GUI environment might be available, the Bean shouldn't use it. For example, a container using a Bean on a server would call this method to tell the Bean there's no point in using the GUI environment. If a Bean chooses to comply with this method (and it should), the Bean should return true for subsequent calls to avoidingGui(). public abstract boolean needsGui() This method is called by the container to ask whether a Bean absolutely has to have a GUI environment. If a Bean can function in a non-GUI environment, it should return false. Note that it's safe to return true and then never use the GUI environment, but it's not safe to return false and use it anyway. public abstract void okToUseGui() This method is called by a container to tell a Bean that a GUI environment is available and the Bean can use it. This method might also be called after dontUseGui() to indicate that a previously unavailable GUI environment is available again. Note that a call to this method in no way implies that a Bean should use the GUI environment, for example, if it wasn't planning to.

Enterprise JavaBeans Although this book is targeted toward the Java 2 Standard Edition (J2SE), a brief crossover into the enterprise side is necessary to make some terminology clear. With the increasing emphasis on server-side Java development, you will likely hear the term Enterprise JavaBeans, or EJB, as often, if not more so, than JavaBeans itself. Both technologies are based on components, but their uses are quite different. EJB is a component model for building and deploying Java in a distributed multitier environment. This technology extends the JavaBeans component model to support server components. Partitioning Your Applications Modern object-oriented designs typically split application design into three pieces. These pieces are the user interface, the business logic, and the data. Client/server applications combine the user interface and business logic code of an application, but multitier applications split the functionality of a system into three or more pieces. In either case, the persistent data used by the system is maintained by a database management system (DBMS), which the application communicates with but isn't actually part of the application itself. In a multitier architecture, the client application contains only user interface functionality. The business logic is implemented in one or more middle tiers by components deployed on a corresponding number of servers. The result of moving the business and data logic to a server is that your application can take advantage of the power of multithreaded and multiprocessing systems. In addition, server components can pool and share scarce resources across users and even applications. As system demands increase, components that are heavily used can be replicated and distributed across multiple systems. This means that there is almost no limit to the scalability of a multitier system. As system resources become overtaxed, you can replicate another system to handle more of the load. In addition to providing enhanced performance, this replication can be used for redundancy. Redundant systems help to eliminate any single points of failure, which is of great concern for Internet applications that are expected to be available around the clock. Server Components EJBs fill the role of server components in multi-tier applications. Although different from standard JavaBeans in many ways, EJBs are still reusable software building blocks that can be assembled to efficiently create new applications. Because you always develop EJBs to run on a server, they never have a visual appearance. They instead are created to manage persistent data and to provide functionality that can be exposed to multiple client applications simultaneously. Beyond reuse, the EJB architecture provides a framework that frees you from coding intricate transaction management and security logic for each application you develop. You might instead focus on the business logic required by your systems while taking advantage of a robust and scalable infrastructure that is capable of being run on every platform supported by Java. The subject of EJB goes well beyond this brief introduction. The important point here is that you understand the differences between standard JavaBeans and EJBs. You should take a look at the Java 2 Enterprise Edition (J2EE) information on Sun's Web site to go more in depth on the subject.

You might also like