Chapter 21. JavaBeans

CONTENTS
  •  21.1 What's a Bean?
  •  21.2 The NetBeans IDE
  •  21.3 Properties and Customizers
  •  21.4 Event Hookups and Adapters
  •  21.5 Binding Properties
  •  21.6 Building Beans
  •  21.7 Limitations of Visual Design
  •  21.8 Serialization Versus Code Generation
  •  21.9 Customizing with BeanInfo
  •  21.10 Hand-Coding with Beans
  •  21.11 BeanContext and BeanContextServices
  •  21.12 The Java Activation Framework
  •  21.13 Enterprise JavaBeans

JavaBeans is a component architecture for Java. It is a set of rules for writing highly reusable software elements that can be linked together in a plug-and-play fashion to build applications. Writing objects to the JavaBeans specification means you won't have to write as much custom code to glue them together. It also allows you to leverage JavaBean-aware development tools. With some integrated development environments (IDEs), it is even possible to build large parts of applications just by connecting prefabricated JavaBeans.

JavaBeans is a rich topic, but we can't give it more than a brief overview here. If this overview whets your appetite, look for Developing Java Beans by Robert Englander (O'Reilly).

21.1 What's a Bean?

What exactly is or are JavaBeans? JavaBeans defines a set of rules; JavaBeans are ordinary Java objects that play by these rules. That is, JavaBeans are Java objects that conform to the JavaBeans API and design patterns. By doing so, they can be recognized and manipulated within visual application builder environments, as well as by hand coding. Beans live and work in the Java runtime system, as do all Java objects. They communicate with their neighbors using events and other normal method invocations.

For Bean examples, we need look no further than the javax.swing packages. All the familiar components, such as JButton, JTextArea, JScrollpane, etc., are not only suitable for Beans, but are also, in fact, Beans! Much of what you learned in Chapter 15 about the Swing components has prepared you for understanding Beans. Although most of the Swing components aren't very useful in isolation, in general Beans can also be large and complex application components, such as spreadsheets or document editors. The HotJavaBrowser Bean, for example, is a complete web browser cast in the form of a JavaBean. We'll talk more about exactly what makes a Bean a Bean in a moment. For now, we want to give you a better sense of how they are used.

JavaBeans are intended to be manipulated visually within a graphical application builder. They are generally chosen from a palette of tools and manipulated graphically in an application builder's workspace. In this sense, Beans resemble widgets used in a traditional GUI builder: user interface components that can be assembled to make application "screens." In traditional GUI builders, the result is usually just some automatically generated code that provides a skeleton on which you hang the meat of your application. GUI builders generally build GUIs, not entire applications.

In contrast, JavaBeans can be not only simple UI components such as buttons and sliders, but also more complex and abstract components. It is easy to get the impression that Beans are, themselves, always graphical objects (like the Swing components that we mentioned), but JavaBeans can implement any part of an application, including "invisible" parts that perform calculations, storage, and communications. Ideally, we would like to snap together a substantial application using prefabricated Beans, without ever writing a line of code! Three characteristics of the JavaBeans architecture make it possible to work with application components at this level:

Design patterns

The most important characteristic of a JavaBean is simply a layer of standardization. Design patterns (i.e., coding conventions) let tools and humans recognize the basic features of a Bean and manipulate it without knowing how it is implemented. We might say that Beans are "self-documenting." By examining a Bean, we can tell what events it can fire and receive; we can also learn about its properties (the equivalent of its public variables) and methods. Beans can also provide explicit information about their features tailored specifically for IDEs.

Reflection

Reflection is an important feature of the Java language. (It's discussed in Chapter 7.) Reflection makes it possible for Java code to inspect and manipulate new Java objects at runtime. In the context of JavaBeans, reflection lets a development tool analyze a Bean's capabilities, examine the values of its fields, and invoke its methods. Essentially, reflection allows Java objects that meet at runtime to do all the things that could be done if the objects had been put together at compile time. Even if a Bean doesn't come bundled with any "built-in" documentation, we can still gather information about its capabilities and properties by directly inspecting the class using reflection.

Object serialization

Finally, the Java Serialization API allows us to "freeze-dry" (some prefer the word "pickle") a live application or application component and revive it later. This is a very important step; it makes it possible to piece together applications without extensive code generation. Rather than customizing and compiling large amounts of Java code to build our application on startup, we can simply paste together Beans, configure them, tweak their appearance, and then save them. Later, the Beans can be restored with all their state and interconnections intact. This makes possible a fundamentally different way of thinking about the design process. It is easy to use serialized objects from handwritten Java code, as well, so we can freely mix "freeze-dried" Beans with plain old Bean classes and other Java code. In Java 1.4, a new "long-term" object serialization mechanism was added that saves JavaBeans in an XML format that is very resilient to changes in classes.

21.1.1 How Big Is a Bean?

Our Bean examples have ranged from simple buttons to spreadsheets. Obviously, a button Bean would be much less complex than a spreadsheet and would be used at a different level of the application's design. At what level are Beans intended to be used? The JavaBeans architecture is supposed to scale well from small to large; simple Beans can be used to build larger Beans. A small Bean may consist of a single class; a large Bean may have many. Beans can also work together through their container to provide services to other Beans.

Simple Beans are little more than ordinary Java objects. In fact, any Java class that has a default (empty) constructor could be considered a Bean. A Bean should also be serializable, although the JavaBeans specification doesn't strictly require that. These two criteria ensure that we can create an instance of the Bean dynamically and that we can later save the Bean, as part of a group or composition of Beans. There are no other requirements. Beans are not required to inherit from a base Bean class, and they don't have to implement any special interface.

A useful Bean would want to send and receive events and expose its properties to the world. To do so, it follows the appropriate design patterns for naming the relevant methods so that these features can be automatically discovered. Most nontrivial Beans intended for use in an IDE also provide information about themselves in the form of a BeanInfo class. A BeanInfo class implements the BeanInfo interface, which holds methods that describe a Bean's features in more detail, along with extra packaging, such as icons for display to the user. Normally, this "Bean info" is supplied by a separate class that is named for and supplied with the Bean.

21.2 The NetBeans IDE

We can't have a meaningful discussion of Beans without spending a little time talking about the builder environments in which they are used. In this book, we use the NetBeans IDE to demonstrate our Beans. NetBeans is a popular, pure Java development environment for Java. In this case, the "integrated" in "integrated development environment" means that NetBeans offers powerful source-editor capabilities, templates that aid in the creation of various types of Java classes; and the ability to compile, run, and debug applications, all in one tool.

NetBeans is an open source project[1] with a modular architecture that allows it to be easily extended with new capabilities. For example, there are XML modules that assist you in creating and editing XML documents, as well as web modules that allow the IDE to edit documents and even act as a web server for testing. NetBeans even comes with a version of BeanShell, the Java scripting language created by one of the authors of this book! (See Appendix B.)

Because NetBeans is a full-blown production development environment, it has many features we don't use in these examples. For that reason, we can't really provide a full introduction to NetBeans in this book. We will provide only bare-bones directions here for demonstrating the JavaBeans in this chapter. But we hope that once you get into the tool and start looking around, you will want to learn more.

Some examples of other Java development environments that support JavaBeans are:

  • IBM's Visual Age for Java (http://www7.software.ibm.com/vad.nsf/Data/Document4600)

  • Borland/Inprise's JBuilder (http://www.inprise.com/jbuilder)

  • Metrowerks's CodeWarrior (http://www.metrowerks.com)

21.2.1 Installing and Running NetBeans

You have to install the Java 1.4 SDK before you can install NetBeans. Both the SDK (Version 1.4.0) and NetBeans itself (Version 3.3.1) are included on the CD-ROM that accompanies this book (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/). They can also be downloaded from http://java.sun.com/j2se/ and http://www.netbeans.org, respectively. Follow the simple installation instructions for those packages (you may have to reboot if you installed Java) and then launch NetBeans.

The first time it runs, NetBeans asks you to specify a default directory for your project files and whether you prefer multiple smaller windows or the single-window (full-screen) mode. We chose full-screen mode, as you will see in the examples, starting with Figure 21-1. NetBeans also asks if you want to associate Java files with it. If you answer yes, you can launch NetBeans by double-clicking on a Java file (at least in Windows).

Figure 21-1. NetBeans

figs/lj2.2101.gif

When you first start NetBeans, a welcome screen appears. Close it. Figure 21-1 shows the NetBeans application. The tab selected at the top is GUI Editing. This is where we'll be doing most of our work. The left side of the workspace is a file browser called Explorer, and the center is the GUI editor, showing one of our examples. Key parts of the GUI editor are the top "palettes" of Beans, the central layout area, and the properties editor or "customizer" on the right side. The properties editor changes its contents based on the current Bean selected in the work area.

NetBeans includes all the standard Swing components and provides a few more Beans of its own. Before we get started, we'll have to add the example Beans used in this chapter to the NetBeans palette as well. To do this, grab our demo Beans JAR file, magicbeans.jar, from the accompanying CD-ROM (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/) or from http://www.oreilly.com/catalog/learnjava2/. Save the file locally and then select Install New JavaBean from the Tools menu to add the beans. The wizard asks you to locate the JAR file and then shows you a list of Beans that it contains. Select all of them (shift-click on the first and last), then click ok. NetBeans prompts you for the Palette Category under which you wish to file these; select Beans. We'll see these Beans soon, when we start editing an application.

Now we must open a new Java class file to begin editing. First, select in the Explorer the folder where you'd like the new class file to be stored so that NetBeans will store it there for you. Now click on the "new file" icon on the far left of the toolbar or select New... from the File menu. NetBeans prompts you with a wizard for setting up the type of file you wish to create. Expand the folder labeled GUI Forms, and select JFrame. This gives us a Java class file extending JFrame with the basic structure of a GUI application already set up for us. Click Next, then give the file a name, such as LearnJava1, and choose a location for the file. You may leave the package set to the default package if you wish. Now click Finish.

NetBeans should now open two windows, as in Figure 21-2: the Source Editor, showing the LearnJava1 class it has started for us, and the Form Editor window. Select the Swing tab at the top of the Form Editor to see some of the Swing components available as Beans (they are shown as icons at the top of the window). Now select the Beans tab to see the Beans we imported earlier. You should see the friendly Dial component Bean from Chapter 17, along with a waving Duke (the Java mascot) and a small clock icon. The rest of our Beans have no pretty icons and can't easily be distinguished. That's because these simple example Beans aren't packaged as completely as the others. (We'll talk about packaging later in the chapter.) For now, right-click on one of the Beans, and select the Show Names option to display the class names of all the Beans in the palette. (You'll want to resize the border so you can see all the Beans).

Figure 21-2. LearnJava1 example in NetBeans

figs/lj2.2102.gif

To place a Bean into the workspace, simply click on it and then click in the workspace. The top right of the Form Editor holds a tree that shows all the components (visible and invisible) in the project. By right-clicking on the JFrame (our top-level container) in either the workspace or the tree, you can select the Set Layout option to specify the layout manager for the frame. For now, try using the AbsoluteLayout, provided by NetBeans. This allows you to arbitrarily place and move Beans within the container.

21.3 Properties and Customizers

Properties represent the "state" or "data" content of a Bean. They are features that can be manipulated externally to configure the Bean. For a Bean that's a GUI component, you might expect its properties to include its size, color, and other features of its basic appearance. Properties are similar in concept to an object's public variables. Like a variable, a property can be a primitive type (such as a number or boolean), or it can be a complex object type (such as a String or a collection of spreadsheet data). Unlike variables, properties are always manipulated using methods; this enables a Bean to take action whenever a property changes. By sending an event when a property changes, a Bean can notify other interested Beans of the change (see Section 21.5, later in this chapter).

Let's pull a couple of Beans into NetBeans and take a look at their properties. Grab a JButton from the Swing palette, and place it in the workspace. When the JButton was first loaded by NetBeans, it was inspected to discover its properties. When we select an instance of the button, NetBeans displays these properties in the properties sheet and allows us to modify them. As you can see in the figure, the button has seven basic properties. foreground and background are colors; their current values are displayed in the corresponding box. font is the font for the label text; an example of the font is shown. text is the text of the button's label. You can also set an image icon for the button, the "tool tip" text that appears when the mouse hovers over the item, and a keyboard-shortcut identifier, called a mnemonic in NetBeans. Try typing something new in the text field of the property sheet, and watch the button label change. Click on the background color to enter a numeric color value, or, better yet, hit the "..." button to pop up a color-chooser dialog.

Most of these basic properties will become familiar to you because many GUI Beans inherit them from the base JComponent class. If you click on the tab labeled Other Properties, you'll see some 40 additional properties inherited from JComponent. NetBeans is making an effort to categorize these for us. But as we'll see when we create our own Beans, we can choose to limit which of a Bean's properties are shown in the properties editor.

Now place a Juggler Bean in the workspace (this is one of Sun's original demonstration JavaBeans that we have updated). The animation starts, and Duke begins juggling his coffee Beans, as shown in Figure 21-3. If he gets annoying, don't worry, we'll have him under our control soon enough.

Figure 21-3. Juggling Beans

figs/lj2.2103.gif

You can see that this Bean has a different set of properties. The most interesting is the one called animationRate. It is an integer property that controls the interval in milliseconds between displays of the juggler's frames. Try changing its value. The juggler changes speed as you type each value. Good Beans give you immediate feedback on changes to their properties. Set the juggling property to False to stop the show.

Notice that the property sheet provides a way to display and edit each of the different property types. For the foreground and background properties of the JButton, the sheet displays the color; if you click on them, a color selection dialog pops up. Similarly, if you click on the font property, you get a font dialog. For integer and string values, you can type a new value into the field. NetBeans understands and can edit the most useful basic Java types.

Since the property types are open-ended, NetBeans can't possibly anticipate them all. Beans with more complex property types can supply a property editor . The Molecule Bean that we'll play with in the next section, for example, uses a custom property editor that lets us choose the type of molecule. If it needs even more control over how its properties are displayed, a Bean can provide a customizer. A customizer allows a Bean to provide its own GUI for editing its properties.

21.4 Event Hookups and Adapters

Beans use events to communicate. As we mentioned in Chapter 15, events are not limited to GUI components but can be used for signaling and passing information in more general applications. An event is simply a notification; information describing the event and other data are wrapped up in a subclass of EventObject and passed to the receiving object by a method invocation. Event sources register listeners who want to receive the events when they occur. Event receivers implement the appropriate listener interface containing the method needed to receive the events. This is Java's general event mechanism in a nutshell.

It's useful to place an adapter between an event source and a listener. An adapter can be used when an object doesn't know how to receive a particular event; it enables the object to handle the event anyway. The adapter can translate the event into some other action, such as a call to a different method or an update of some data. One of the jobs of NetBeans is to help us hook up event sources to event listeners. Another job is to produce adapter code that allows us to hook up events in more complex ways.

21.4.1 Taming the Juggler

Before we get into details, let's look at Figure 21-4 and try to get our Juggler under control. Using the properties sheet, change the label of our first button to read Start. Now click the small Connection Wizard icon at the top of the GUI builder (this is the second icon, showing two arrows pointing at one another). Now select first the button and then the Juggler by clicking on them. NetBeans pops up the Connection Wizard, indicating the source component (the button) and prompting you to choose from a large list of events (see Figure 21-4). Most of these are standard Swing events that can be generated by any kind of JComponent. What we're after is the button's action event. Expand the folder named action, and select actionPerformed as the target method. Choose Next to go to the target component screen for the Juggler. The wizard prompts us to choose a property to set on the Juggler as in Figure 21-5. The display shows three of the Juggler's properties. Choose juggling as the target and click Next. Now enter true in the value box and click Finish. We have completed a hookup between the button and the Juggler. When the button fires an action event, the "juggling" property of the Juggler is set to true.

Figure 21-4. Selecting a source event in the Connection Wizard

figs/lj2.2104.gif

Figure 21-5. Specifying a target operation in the Connection Wizard

figs/lj2.2105.gif

If you take a look at the Source Editor window, you can see that NetBeans has generated some code to make this connection for us. Specifically, in the initComponents() method of our template class, it has created an anonymous inner class to serve as the ActionListener for ActionEvents from our button (which it has named jButton1):

jButton1.addActionListener(new java.awt.event.ActionListener(  ) {     public void actionPerformed(java.awt.event.ActionEvent evt) {         jButton1ActionPerformed(evt);     } });

The adapter calls a private method that sets the property on our Juggler:

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {     juggler1.setJuggling(true); }

You'll notice that most of the text that was written for us is shaded light blue. This is to indicate that it is autogenerated and can't be directly modified. The body of the private method is open, however, and we could modify it to perform arbitrary activities when the button is pushed. In NetBeans the hookup is just a starting point.

This may all seem a little obtuse. After all, if we had made the Juggler an ActionListener in the first place, we would expect to hook it directly to the button. The use of adapters provides a great deal of flexibility, however, as we'll see next.

To complete our example, repeat the process and add a second JButton labeled Stop. Click the Connection Wizard icon; select the Stop button and the Juggler as its target. Again, choose the actionPerformed method as the source, but this time, instead of selecting a property on the Juggler, click the Method call radio button to see a list of available methods on the Juggler Bean. Scroll all the way down and select the stopJuggling() method. Click Finish to complete the hookup, and look at the generated code if you wish. Now we have seen an example of hooking up a source of action events to produce an arbitrary method call on a Bean.

Now the Juggler will do our bidding. Hit the green arrow "run" icon and watch as NetBeans compiles and runs our example. You should be able to start and stop Duke with the buttons! When you are done, close the window, and return to the GUI editor. Save and close this example, and let's move on.

21.4.2 Molecular Motion

Let's look at one more interesting example, shown in Figure 21-6. Create a new project class as before, choosing the JFrame template. Call this one LearnJava2.

Grab a Molecule Bean and place it in the workspace. If you run the example now, you will see that by dragging the mouse within the image, you can rotate the model in three dimensions. Try changing the type of molecule using the properties sheet: ethane is fun.[2] Now let's see what we can do with our molecule. Grab a Timer Bean from the palette. Timer is a clock. Every so many seconds, Timer fires an event. The timer is controlled by an integer property called delay, which determines the number of seconds between events. Timer is an "invisible" Bean; it is not derived from a JComponent and doesn't have a graphical appearance, just as an internal timer in an application wouldn't normally have a presence on the screen. NetBeans represents invisible Beans in the component tree at the top right of the GUI builder. When you wish to select the Timer, click on it in the tree.

Figure 21-6. The Molecule Bean and the Timer

figs/lj2.2106.gif

Let's hook the Timer to our Molecule. Activate the connection wizard and select the Timer (from the tree) and then the Molecule. Choose the Timer onTime() method from the list. Click Next and select the Method call radio button. Find and select the rotateOnX() method and click Finish. Run the example. Now the Molecule should turn on its own every time it receives an event from the timer. Try changing the timer's interval. You can also hook the Timer to the Molecule's rotateOnY() method, Use a different instance of TickTock and, by setting different intervals, make it turn at different rates in each dimension. There's no end to the fun.

21.5 Binding Properties

By using a combination of events and adapters, we can connect Beans in many interesting ways. We can even "bind" two Beans together so that if a property changes in the first Bean, the corresponding property is automatically changed in the second Bean. In this scenario, the Beans don't necessarily have to be of the same type, but to make sense, the properties do.

Close the Molecule project and start a new one. Grab two NumericField Beans from the palette, drop them in NetBeans, and select one of them, as shown in Figure 21-7. You'll notice that a NumericField has many of the standard properties of a Swing component. If you select the Other Properties tab, you can also find an integer property called value. This is the value of the field. You can set it or enter a number directly into the field. NumericField rejects nonnumeric text.

Figure 21-7. Binding properties

figs/lj2.2107.gif

Now let's bind the value property of the fields. Activate the connection wizard and choose the propertyChange() method. This is the listener method for PropertyChangeEvent, a generic event sent by Beans when one of their properties changes. When a Bean fires property-change events in response to changes in a particular property, that property is said to be "bound." This means that it is possible to bind the property to another Bean through the generic mechanism. In this case, the value of our NumericField Beans is a bound property.

Choose Next, and select the value property as the target on the other NumericField. Click Next again, and select Property on the Parameters screen. Click the "..." editor button to pop up a Select Property dialog. Select the source numeric field (probably named numericField1) from the pulldown menu, and then choose the value property. Click Ok and Finish to complete the hookup.

Run the application, and try entering values in the first field. The second field should change each time. The second Bean's value property has been bound to the first.

Try binding the value property in the other direction as well so that you can change the value in either Bean, and the changes are propagated in both directions. (Some simple logic in the Beans prevents infinite loops from happening here.)

NetBeans has again generated an adapter for us. This time the adapter listens for PropertyChangeEvents and invokes the setValue() method of our target field. Note that we haven't done anything earth-shaking. The PropertyChangeEvent does carry some extra information the old and new values of the property but we're not using them here. And with the connection wizard, you can use any event source as the impetus to set a property on your target Bean. Finally, as we've seen, the property can derive its value from any other Bean in the layout. The flexibility of the connection wizard is, to some extent, masking the purpose of the events, but that's okay. If we are interested in the specific property that changed, or if we want to apply logic about the value, we can fill in the generated method with our own code.

21.5.1 Constraining properties

In the previous section, we discussed how Beans fire PropertyChangeEvents to notify other Beans (and adapters) that a property has changed. In that scenario, the object that receives the event is simply a passive listener, as far as the event's source is concerned. JavaBeans also supports constrained properties, in which the event listener gets to say whether it will allow a Bean to change the property's value. If the new value is rejected, the change is cancelled; the event source keeps its old value.

The concept of constrained properties has not been heavily used in the normal operation of Swing, so we won't cover it in detail here. But it goes something like this.Normally, PropertyChangeEvents are delivered to a propertyChange() method in the listener. Constrained properties are implemented by delivering PropertyChangeEvents to a separate listener method called vetoableChange(). The vetoableChange() method throws a PropertyVetoException if it doesn't like a proposed change. In this way, components can govern the acceptable values of other components.

21.6 Building Beans

Now that you have a feel for how Beans look from the user's perspective, let's build some. In this section, we will become the Magic Beans Company. We will create some Beans, package them for distribution, and use them in NetBeans to build a very simple application. (The complete JAR file, along with all the example code for this chapter, is on the CD-ROM that accompanies this book (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/), and at http://www.oreilly.com/catalog/learnjava2.)

The first thing we'll remind you of is that absolutely anything can be a Bean. Even the following class is a Bean, albeit an invisible one:

public class Trivial implements java.io.Serializable {}

Of course, this Bean isn't very useful: it doesn't have any properties, and it doesn't do anything. But it's a Bean nonetheless, and we can drag it into NetBeans as long as we package it correctly. If we modify this class to extend JComponent, we suddenly have a graphical Bean that be seen in the layout, with lots of standard Swing properties, such as size and color information:

public class TrivialComponent extends JComponent {}

Next let's look at a Bean that's a bit more useful.

21.6.1 The Dial Bean

We created a nifty Dial component in Chapter 17. What would it take to turn it into a Bean? Well, surprise: it is already a Bean! The Dial has a number of properties that it exposes in the way prescribed by JavaBeans. A get method retrieves the value of a property; for example, getValue() retrieves the dial's current value. Likewise, a set method (setValue()) modifies the dial's value. The dial has two other properties, which also have get and set methods: minimum and maximum. This is all the Dial needs to inform a tool such as NetBeans what properties it has and how to work with them. Because Dial is a JComponent, it also has all the standard Swing properties, such as color and size. The JComponent provides the set and get methods for all its properties.

In order to use our Dial, we'll put it in a Java package named magicBeans and store it in a JAR file that can be loaded by NetBeans. The source code, which can be found on the accompanying CD-ROM (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/), includes an Ant build file (see Section 14.16 in Chapter 14) that compiles the code and creates the final JAR file.

First, create a directory called magicBeans to hold our Beans, add a package statement to the source files Dial.java, DialEvent.java, and DialListener.java, put the source files into the magicBeans directory, and compile them (javac magicBeans/Dial.java) to create class files. Next, we need to create a manifest file that tells NetBeans which of the classes in the JAR file are Beans and which are support files or unrelated. At this point, we have only one Bean, Dial.class, so create the following file, called magicBeans.manifest:

Name: magicbeans/Dial.class  Java-Bean: True

The Name: label identifies the class file as it will appear in the JAR: magicbeans/Dial.class. Specifications appearing after an item's Name: line and before an empty line apply to that item. (See Section 3.5 in Chapter 3 for more details.) We have added the attribute Java-Bean: True, which flags this class as a Bean to tools that read the manifest. We will add an entry like this for each Bean in our package. We don't need to flag support classes (such as DialEvent and DialListener) as Beans, because we won't want to manipulate them directly with NetBeans; in fact, we don't need to mention them in the manifest at all.

To create the JAR file, including our manifest information, enter this command:

% jar cvmf magicbeans.manifest magicbeans.jar magicbeans/*.class

If you loaded the precompiled examples as instructed earlier, then you already have the Dial Bean loaded into NetBeans. The version supplied in the precompiled magicbeans.jar file has additional packaging that allows it to appear with a spiffy icon in the palette, as we'll discuss a bit later. If you haven't loaded the example JAR, you can import the one we just created using the Import JavaBean wizard on the NetBeans Tools menu, as we described earlier. If you want to replace the Dial Bean on your palette, you can remove it by right-clicking on the icon and selecting the Delete option before importing the new JAR. Note that you may have to restart NetBeans in order for it to recognize that a Bean has changed.

You should now have an entry for Dial in the Bean palette. Drop an instance of the Dial Bean into NetBeans.

As Figure 21-8 shows, the Dial's properties value, minimum, and maximum are on the properties sheet and can be modified by NetBeans. If you created the previous Dial JAR, you'll see these properties along with all the Swing properties inherited from the JComponent class. The figure shows the Dial Bean as it appears later in this chapter, after we've learned about the BeanInfo class. We're almost there.

Figure 21-8. The Dial component as a Bean

figs/lj2.2108.gif

Now we're ready to put the Dial to use. Reopen the Juggler example that we asked you to save in the first section of this chapter. (Did you save it?) Add an instance of our new magic Dial Bean to the scenario, as shown in Figure 21-9.

Figure 21-9. The Juggler with a dialable animation rate

figs/lj2.2109.gif

Bind the value property of the Dial to the animationRate of the Juggler. Use the connection wizard, as before, selecting the Dial and then the Juggler. Select the DialEvent source and bind the animationRate property, selecting the Dial's value as the property source. When you complete the hookup, you should be able to vary the speed of the juggler by turning the dial. Try changing the maximum and minimum values of the dial to change the range.

21.6.2 Design Patterns for Properties

We said earlier that tools such as NetBeans found out about a Bean's properties by looking at its get and set methods. The easiest way to make properties visible is to follow these simple design patterns:

  • Method for getting the current value of a property:

    public PropertyType getPropertyName( )
  • Method for setting the value of a property:

    public void setPropertyName( PropertyType arg )
  • Method for determining whether a boolean-valued property is currently true:

    public boolean isPropertyName( )

The last method is optional and is used only for properties with boolean values. (You could just use the get method in this situation.)

The appropriate set and get methods for these features of our Bean are already in the Dial class, either methods that we added or methods inherited from the java.awt.Component and javax.swing.JComponent classes:

// inherited from Component  public Color getForeground( )   public void setForeground(Color c)     public Color getBackground( )   public void setBackground(Color c)     public Font getFont( )  public void setFont(Font f)     // many others from Component and JComponent    // part of the Dial itself  public int getValue( )  public void setValue(int v)    public int getMinimum( )  public void setMinimum(int m)    public int getMaximum( )  public void setMaximum(int m)

JavaBeans allows read-only and write-only properties, which are implemented simply by leaving out the get or set method.

NetBeans uses the Reflection API to find out about the Dial Bean's methods; it then uses these naming conventions to figure out what properties are available. When we use the properties editor to change a value, NetBeans dynamically invokes the correct set method to change the value.

If you look further at the JComponent class, you'll notice that other methods match the design pattern. For example, what about the setCursor() and getCursor() pair? NetBeans doesn't know how to display or edit a cursor, and we didn't supply an editor, so it ignores those properties in the properties sheet.

NetBeans automatically pulls the property's name from the name of its accessor methods; it then lowercases the name for display on the properties sheet. For example, the font property is not listed as Font. Later, we'll show how to provide a BeanInfo class that overrides the way these properties are displayed, letting you provide your own friendly property names.

21.6.2.1 Bean patterns in NetBeans

NetBeans automatically recognizes JavaBeans get and set method patterns in classes. In the Explorer, expand the link for the class, and you'll see a Bean Patterns folder. You can add properties automatically by right-clicking on the Bean Patterns folder and selecting Add, then Property. After you supply the name and type of the property, NetBeans automatically creates the necessary get and set methods. You can add instance variable and bound property support as well.

21.6.3 A (Slightly) More Realistic Example

We now have one nifty Bean for the Magic Beans products list. Let's round out the set before we start advertising. Our goal is to build the Beans we need to make a very simple form. The application performs a simple calculation after data is entered on the form.

21.6.3.1 A Bean for validating numeric data

One component we're sure to need in a form is a text field that accepts numeric data. Let's build a text-entry Bean that accepts and validates numbers and makes the values available as a property. You should recognize all the parts of the NumericField Bean:

//file: NumericField.java package magicbeans;    import javax.swing.*; import java.awt.event.*;    public class NumericField extends JTextField  {    private double value;        public NumericField(  ) {        super(6);       setInputVerifier( new InputVerifier(  ) {          public boolean verify( JComponent comp ) {             JTextField field = (JTextField)comp;             boolean passed = false;             try {                setValue( Double.parseDouble( field.getText(  ) ) );             } catch ( NumberFormatException e ) {                comp.getToolkit().beep(  );                field.selectAll(  );                return false;             }             return true;          }       } );          addActionListener( new ActionListener(  ) {          public void actionPerformed( ActionEvent e ) {             getInputVerifier(  ).verify( NumericField.this );          }       } );    }      public double getValue(  ) {       return value;    }    public void setValue( double newValue ) {       double oldValue = value;       value = newValue;       setText( "" + newValue );       firePropertyChange( "value", oldValue, newValue );    } }

NumericField extends the Swing JTextField component. The constructor defaults the text field to a width of six columns, but you can change its size in NetBeans through the "columns" property.

The heart of NumericField is the InputVerifier, which we have implemented as an anonymous inner class (see Section 17.1.4 in Chapter 17). Our verifier is called to parse the user's entry as a number, giving it a Double value. We have also added an ActionListener that validates when the user hits Enter in the field.

If parsing succeeds, we update the value property using our setValue() method. setValue() then fires a PropertyChangeEvent to notify any interested Beans. If the text doesn't parse properly as a number, we give feedback to the user by selecting (highlighting) the text.

Using NetBeans, verify the operation of this Bean by placing two NumericFields in the workspace and binding the value property of one to the other. You should be able to enter a new floating point value and see the change reflected in the other.

21.6.3.2 An invisible multiplier

Now, let's make an invisible Bean that performs a calculation rather than forming part of a user interface. Multiplier is a simple invisible Bean that multiplies the values of two of its properties (A and B) to produce the value of a third read-only property (C). Here's the code:

//file: Multiplier.java package magicbeans;    import java.beans.*;    public class Multiplier implements java.io.Serializable {    private double a, b, c;        synchronized public void setA( double val ) {        a = val;        multiply(  );    }       synchronized public double getA(  ) {        return a;     }       synchronized public void setB( double val ) {        b = val;        multiply(  );    }       synchronized public double getB(  ) {        return b;     }       synchronized public double getC(  ) {        return c;     }         synchronized public void setC( double val ) {        multiply(  );    }         private void multiply(  ) {       double oldC = c;       c = a * b;       propChanges.firePropertyChange(           "c", new Double(oldC) , new Double(c) );    }        private PropertyChangeSupport propChanges =  new PropertyChangeSupport(this);       public void addPropertyChangeListener(   PropertyChangeListener listener)     {         propChanges.addPropertyChangeListener(listener);    }    public void removePropertyChangeListener(   PropertyChangeListener listener)     {         propChanges.removePropertyChangeListener(listener);    } }

To make a Multiplier a source of PropertyChangeEvents, we enlist the help of a PropertyChangeSupport object. To implement Multiplier's methods for registering property-change listeners, we simply call the corresponding methods in the PropertyChangeSupport object. Similarly, a Multiplier fires a property change event by calling the PropertyChangeSupport object's firePropertyChange() method. This is the easiest way to get an arbitrary class to be a source of PropertyChangeEvents.

The code is straightforward. Whenever the value of property a or b changes, we call multiply(), which multiplies their values and fires a PropertyChangeEvent. So we can say that Multiplier supports binding of its c property.

21.6.3.3 Putting them together

Finally, let's demonstrate that we can put our Beans together in a useful way. Arrange three JLabels, three NumericFields, and a Multiplier as shown in Figure 21-10.

Figure 21-10. TextLabels, NumericFields, and a Multiplier

figs/lj2.2110.gif

Bind the values of the first two NumericFields to the a and b properties of the Multiplier; bind the c value to the third NumericField. Now we have a simple calculator. Try some other arrangements. Can you build a calculator that squares a number? Can you see how you might build a simple spreadsheet? Well, perhaps not. We'll address some of the limitations in the next section.

21.7 Limitations of Visual Design

The last example proved that we can create at least a trivial application by hooking Beans together in a mostly visual way. In other development environments, this kind of Bean hookup would have been even more streamlined. For example, Sun's "BeanBox" reference JavaBean container takes a different approach from NetBeans. It allows the developer to work with "live" JavaBean instances, dynamically generating adapter code at runtime and relying solely on object serialization to save the resulting work. This kind of design is, in a sense, the real goal of the JavaBeans architecture. It is true "what you see is what you get" (WYSIWYG) programming. However, pure visual design without the ability to integrate handwritten code, as we can do in NetBeans, has not yet proven to scale beyond these kinds of simple applications, and pure visual programming environments have thus far failed to catch on.

Sun is currently working on a replacement for the BeanBox called BeanBuilder. You can find out more about it at http://java.sun.com/products/javabeans/beanbuilder/.

21.8 Serialization Versus Code Generation

If you've been keeping an eye on the NetBeans source window while we've been working, you may have noticed the code that is being generated when you modify properties of Beans. By default, NetBeans generates method calls to the appropriate set methods after creating the Bean. But if you click on the Code Generation tab in the Property Editor window, you'll see that we have another option. By changing the Code Generation property from Generate Code to Serialize, you change NetBeans' behavior. Instead of generating method calls in the source code, it saves your fully configured Bean as a serialized object and then generates the appropriate code to load the freeze-dried Bean into the application from a file.

Try changing the code generation property for the Juggler Bean to Serialize. In the initComponents() area, you'll now see a line for that Bean that uses the static Beans.instantiate() method to load the Bean. Run the application. In the file browser window, you'll now see a serialized Java object file called something like LearnJava1_juggler1.ser (the name is controlled through the Serialize To property). We'll discuss working with serialized Beans in more detail later in this chapter and ask you to refer to this stored Bean file.

21.9 Customizing with BeanInfo

So far, everything NetBeans has known about our Beans has been determined by low-level reflection that is, by looking at the methods of our classes. The java.Beans.Introspector class gathers information on a Bean using reflection, then analyzes and describes a Bean to any tool that wants to know about it. The introspection process works only if the class follows the JavaBeans naming conventions for its methods; furthermore, it gives us little control over exactly what properties and events appear in NetBeans menus. For example, we've seen that NetBeans by default shows all the stuff we inherit from the base Swing component. We can change that by creating BeanInfo classes for our Beans. A BeanInfo class provides the JavaBeans introspector with explicit information about the properties, methods, and events of a Bean; we can even use it to customize the text that appears in menus in NetBeans (and in other IDEs).

A BeanInfo class implements the BeanInfo interface. That's a complicated proposition; in most situations, the introspector's default behavior is reasonable. So instead of implementing the BeanInfo interface, we extend the SimpleBeanInfo class, which implements all BeanInfo's methods. We can override specific methods to provide the information we want; when we don't override a method, we'll get the introspector's default behavior.

In the next few sections, we'll develop a DialBeanInfo class that provides explicit information about our Dial Bean.

21.9.1 Getting Properties Information

We'll start out by describing the Dial's properties. To do so, we must implement the getPropertyDescriptors() method. This method simply returns an array of PropertyDescriptor objects one for each property we want to publicize.

To create a PropertyDescriptor, call its constructor with two arguments: the property's name and the class. In the following code, we create descriptors for the Dial's value, minimum, and maximum properties. We then call a few methods of the PropertyDescriptor class to provide additional information about each property. If our methods were bound (generated PropertyChangeEvents when modified), we'd call the setBound() method of their PropertyDescriptors. Our code is prepared to catch an IntrospectionException, which can occur if something goes wrong while creating the property descriptors, such as encountering a nonexistent method:

//file: DialBeanInfo.java package magicbeans;  import java.beans.*;    public class DialBeanInfo extends SimpleBeanInfo {      public PropertyDescriptor[] getPropertyDescriptors( ) {      try {        PropertyDescriptor value =           new PropertyDescriptor("value", Dial.class);        PropertyDescriptor minimum =           new PropertyDescriptor("minimum", Dial.class);        PropertyDescriptor maximum =           new PropertyDescriptor("maximum", Dial.class);                 return new PropertyDescriptor [] { value, minimum, maximum };      }     catch (IntrospectionException e) {        return null;       }    } }

Perhaps the most useful thing about DialBeanInfo is that by providing explicit information for our properties, we automatically hide other properties introspection might find. After compiling DialBeanInfo and packaging it with the Dial, you'll see that its JComponent properties no longer appear in the NetBeans properties editor. (This has been the case all along if you started with the precompiled example JAR.)

A PropertyDescriptor can provide a lot of other information about a property: the names of the accessor methods (if you decide not to use the standard naming convention), information on whether the property is constrained, and a class to use as a property editor (if the standard property editors aren't sufficient).

21.9.1.1 Getting events information

The Dial Bean defines its own event: the DialEvent. We'd like to tell development tools about this event so that we can build applications using it. The process for telling the world about our event is similar to what we did previously: we add a method to the DialBeanInfo class called getEventSetDescriptors(), which returns an array of EventSetDescriptors.

Events are described in terms of their listener interfaces, not in terms of the event classes themselves, so our getEventSetDescriptors() method creates a descriptor for the DialListener interface. Here's the code to add to the DialBeanInfo class:

public EventSetDescriptor[] getEventSetDescriptors( ) {    try {      EventSetDescriptor dial = new EventSetDescriptor(       Dial.class, "dialAdjusted",       DialListener.class, "dialAdjusted");     dial.setDisplayName("Dial Adjusted");         return new EventSetDescriptor [] { dial };   }   catch (IntrospectionException e) {      return null;   }  }

In this method, we create an EventSetDescriptor object: dial. The constructor for an EventSetDescriptor takes four arguments: the class that generates the event, the name of the event (the name that is displayed, by default, by a development tool), the listener class, and the name of the method to which the event can be delivered. (Other constructors let you deal with listener interfaces that have several methods). After creating the descriptor, we call the setDisplayName() method to provide a more friendly name to be displayed by development tools such as NetBeans. (This overrides the default name specified in the constructor.)

Just as the property descriptors we supply hide the properties that were discovered by reflection, the EventSetDescriptors can hide the other events that are inherited from the base component classes. In theory, when you recompile DialBeanInfo, package it in a JAR, and load it into NetBeans, you should see only the two events that we have explicitly described: our own DialEvent and PropertyChangeEvent (displayed as "Dial Adjusted" and "Bound property change"). Unfortunately, the current version of NetBeans ignores this information.

Once we have an EventSetDescriptor, we can provide other kinds of information about the event. For example, we can state that the event is unicast, which means that it can have only one listener.

21.9.1.2 Supplying icons

Some of the Beans that come with NetBeans are displayed on the palette with a cute icon. This makes life more pleasant for everyone. To supply an icon for the BeanInfo object we have been developing, we have it implement the getIcon() method. You may supply up to four icons, with sizes of 16 x 16 or 32 x 32, in either color or monochrome. Here's the getIcon() method for DialBeanInfo:

public class DialBeanInfo extends SimpleBeanInfo {    ...    public java.awt.Image getIcon(int iconKind) {        if (iconKind == BeanInfo.ICON_COLOR_16x16) {        return loadImage("DialIconColor16.gif");      } else      if (iconKind == BeanInfo.ICON_COLOR_32x32) {        return loadImage("DialIconColor32.gif");      } else      if (iconKind == BeanInfo.ICON_MONO_16x16) {        return loadImage("DialIconMono16.gif");      } else      if (iconKind == BeanInfo.ICON_MONO_32x32) {        return loadImage("DialIconMono32.gif");      }      return null;    }

This method is called with a constant indicating what kind of icon is being requested; for example, BeanInfo.ICON_COLOR_16x16 requests a 16 x 16 color image. If an appropriate icon is available, it loads the image and returns an Image object. If the icon isn't available, it returns null. For convenience, you can package the images in the same JAR file as the Bean and its BeanInfo class.

Though we haven't used them here, you can also use a BeanInfo object to provide information about other public methods of your Bean (for example, array-valued properties) and other features.

21.9.1.3 Creating customizers and property editors

JavaBeans also lets you provide a customizer for your Beans. Customizers are objects that do advanced customization for a Bean as a whole; they let you provide your own GUI for tweaking your Bean. (For example, the Select Bean uses a customizer rather than the standard properties sheet.) We won't show you how to write a customizer; it's not too difficult, but it's beyond the scope of this chapter. Suffice it to say that a customizer must implement the java.beans.Customizer interface and should extend Component (or JComponent) so that it can be displayed.

A property editor isn't quite as fancy as a customizer. Property editors are a way of giving the properties sheet additional capabilities. For example, you would supply a property editor to let you edit a property type that is specific to your Bean. You could provide a property editor that would let you edit an object's price in dollars and cents. We've already seen a couple of property editors: the editor used for Color-valued properties is fundamentally no different from a property editor you might write yourself. In addition, the Molecule Bean uses a property editor to specify its moleculeName property.

Again, describing how to write a property editor is beyond the scope of this chapter. Briefly, a property editor must implement the PropertyEditor interface; it usually does so by extending the PropertyEditorSupport class, which provides default implementations for most of the methods.

21.10 Hand-Coding with Beans

So far, we've seen how to create and use Beans within a Bean application builder environment. That is the primary role of a JavaBean in development. But Beans are not limited to being used by automated tools. There's no reason we can't use Beans in handwritten code. You might use a builder to assemble Beans for the user interface of your application and then load that serialized Bean or a collection of Beans in your own code, just as NetBeans does when told to use object serialization. We'll give an example of that in a moment.

21.10.1 Bean Instantiation and Type Management

Beans are an abstraction over simple Java classes. They add, by convention, features that are not part of the Java language. To enable certain additional capabilities of JavaBeans, we have to use some special tools that take the place of basic language operations. Specifically, when working with Beans, we are provided with replacements for three basic Java operations: creating an object with new, checking the type of an object with the instanceof operator, and casting a type with a cast expression. In place of these, use the corresponding static methods of the java.beans.Beans class, shown in Table 21-1.

Table 21-1. Methods of the java.beans.Beans class

Operator

Equivalent

New

Beans.instantiate(classloader, name)

Instanceof

Beans.isInstanceOf(object, class)

Explicit cast

Beans.getInstanceOf(object, class)

Beans.instantiate() is the new operation for Beans. It takes a class loader and the name of a Bean class or serialized Bean as arguments. Its advantage over the plain new operator is that it can also load Beans from a serialized form. If you use instantiate(), you don't have to specify in advance whether you will provide the Bean as a class or as a serialized object. The instantiate() method first tries to load a resource file based on the name Bean, by turning package-style names (with dots) into a path-style name with slashes and then appending the suffix .ser. For example, magicbeans.NumericField becomes magicbeans/NumericField.ser. If the serialized form of the Bean is not found, the instantiate() method attempts to create an instance of the class by name. This feature will probably become more important in the future as other forms of Bean serialization are added. In Java 1.4, a new XMLEncoder was added to the java.beans package, allowing Beans to be serialized to an XML file format.

Beans.isInstanceOf() and Beans.getInstanceOf() do the jobs of checking a Bean's type and casting it to a new type. These methods are intended to allow one or more Beans to work together to implement "virtual" or dynamic types. In the future, these methods may be used to let Beans take control of this behavior, providing different "views" of themselves. However, they currently don't add any functionality.

21.10.2 Working with Serialized Beans

Remember the Juggler we serialized a while back? Well, it's time to revive him, just like Han Solo from his "Carbonite" tomb in Star Wars. We'll assume that you saved the Juggler by flipping on the Serialization property while working with the LearnJava1 class and that NetBeans therefore saved him in the file LearnJava1_juggler1.ser. If you didn't do this, you can use the following snippet of code to serialize the Bean to a file of your choice:

import sunw.demo.juggler.Juggler; import java.io.ObjectOutpuStream;    Juggler duke = new Juggler(  ); ObjectOutputStream oout = new ObjectOutputStream(     new FileOutputStream("duke.ser") ); oout.writeObject( duke ); oout.close(  );

Once you have the frozen Duke, compile the following small application:

//file: BackFromTheDead.java import java.awt.Component; import javax.swing.*;  import java.beans.*;    public class BackFromTheDead extends JFrame {      public BackFromTheDead( String name ) {      super("Revived Beans!");      try {        Object bean = Beans.instantiate(           getClass(  ).getClassLoader( ), name );          if ( Beans.isInstanceOf(bean, Component.class) ) {          Component comp = (Component)           Beans.getInstanceOf(bean, Component.class);          getContentPane( ).add("Center", comp);        } else {           System.out.println("Bean is not a Component...");        }     } catch ( java.io.IOException e1 ) {        System.out.println("Error loading the serialized object");     } catch ( ClassNotFoundException e2 ) {        System.out.println(          "Can't find the class that goes with the object");     }    }      public static void main(String [] args) {      JFrame frame = new BackFromTheDead( args[0] );     frame.pack( );     frame.setVisible(true);   }  }

Run this program, passing the name of your serialized object file as an argument and making sure that our magicbeans.jar file is in your classpath. Duke should spring back to life, juggling once again as shown in Figure 21-11.

Figure 21-11. The restored Juggler

figs/lj2.2111.gif

In BackFromTheDead, we use Beans.instantiate() to load our serialized Bean by name. Then we check to see whether it is a GUI component using Beans.isInstanceOf(). (It is, because the Juggler is a subclass of java.awt.Component.) Finally, we cast the instantiated object to a Component with Beans.getInstanceOf() and add it to our application's JFrame. Notice that we still need a static Java cast to turn the Object returned by getInstanceOf() into a Component. This cast may seem gratuitous, but it is the bridge between the dynamic Beans lookup of the type and the static, compile-time view of the type.

Note that everything we've done above could be done using the plain java.io.ObjectInputStream discussed in Chapter 11. But these Bean management methods are intended to shield the user from details of how the Beans are implemented and stored.

One more thing before we move on. We blithely noted the fact that when the Juggler was restored, the Bean began juggling again. This implies that threads were started when the Bean was deserialized. Serialization doesn't automatically manage transient resources such as threads or even loaded images. But it's easy to take control of the process to finish reconstructing the Bean's state when it is deserialized. Have a look at the Juggler source code (provided with the examples) and refer to Chapter 11 for a discussion of object deserialization using the readObject() method.

21.10.3 Runtime Event Hookups with Reflection

We've discussed reflection largely in terms of how design tools use it to analyze classes. But more and more reflection is finding its way into applications to perform dynamic activities that wouldn't be possible otherwise. In this section, we'll look at a dynamic event adapter that can be configured at runtime.

In Chapter 15, we saw how adapter classes could be built to connect event firings to arbitrary methods in our code, allowing us to cleanly separate GUI and logic in our applications. In this chapter, we have seen how NetBeans interposes this adapter code between Beans to do this for us.

The AWT/Swing event model reduces the need to subclass components to perform simple hookups. But if we start relying heavily on special adapter classes, we can quickly end up with as many adapters as objects. Anonymous inner classes let us hide these classes, but they're still there. A potential solution for large or specialized applications is to create generic event adapters that serve a number of event sources and targets simultaneously.

In Java 1.4, a new tool was added to the java.beans package; the EventHandler is a dynamic event dispatcher that simply calls methods in response to events.[3] What makes the EventHandler unique in Java (so far) is that it is the first standard utility to use reflection to allow us to specify the method by name. In other words, you ask the EventHandler to direct events to a handler by specifying the handler object and the string name of the method to invoke on that object. This is a big change from the normal style of coding in Java and comes with some associated risks. We'll talk more about those later.

We can use the create() method of EventHandler to get an adapter for a specified type of event listener, specifying a target object and method name to call when that event occurs. The target object doesn't have to be a listener for the particular event type or any other particular kind of object. The following application, DynamicHookup, uses the EventHandler to connect a button to a launchTheMissiles() method in our class:

//file: DynamicHookup.java import javax.swing.*; import java.awt.event.*; import java.beans.EventHandler;    public class DynamicHookup extends JFrame {   JLabel label = new JLabel( "Ready...", JLabel.CENTER );   int count;      public DynamicHookup(  ) {     JButton launchButton = new JButton("Launch!");     getContentPane(  ).add( launchButton, "South" );     getContentPane(  ).add( label, "Center" );         launchButton.addActionListener(                  (ActionListener)EventHandler.create(                         ActionListener.class, this, "launchTheMissiles"));   }   public void launchTheMissiles(  ) {     label.setText("Launched: "+ count++ );   }      public static void main(String[] args) {     JFrame frame = new DynamicHookup(  );         frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );     frame.setSize(150, 150);     frame.setVisible( true );   } }

Here we call the EventHandler's create() method, passing it the ActionListener class, the target object (this), and a string with the name of the method to invoke on the target when the event arrives. EventHandler internally creates a listener of the appropriate type and registers our target information. Not only do we eliminate an inner class, but the implementation of EventHandler may allow it to share adapters internally, producing very few objects.

The above example shows how we would call a method that takes no arguments. But the EventHandler can actually do more, setting JavaBeans properties in response to events. The following form of create() tells EventHandler to call the launchTheMissiles() method, passing the "source" property of the ActionEvent as an the argument:

EventHandler.create(ActionListener.class, target, "launchTheMissiles", "source")

All events have a source property (via the getSource()) method. But we can go further, specifying a chain of property "gets" separated by dots, which are applied before the value is passed to the method. For example:

EventHandler.create(ActionListener.class, target, "launchTheMissiles", "source.text")

The source.text parameter causes the value getSource().getText() to be passed as an argument to launchTheMissiles(). In our case, that would be the label of our button. Other forms of create() allow more flexibility in selecting which methods of a multimethod listener interface are used and other options. We won't cover every detail of the tool here.

21.10.3.1 Safety implications

The EventHandler is a powerful tool, but it was, in actuality, primarily intended for use by IDEs, not developers. Reflection allows you to do things at runtime that you couldn't do otherwise. Therein lies the problem with this technique: by using reflection to locate and invoke methods, we abandon Java's strong typing and head off in the direction of scripting languages. We add power at the expense of safety. If the method name specified to EventHandler doesn't exist, or if it exists but wants the wrong type of arguments, you may receive a runtime exception, or worse, it may be silently ignored.

21.10.3.2 How it works

The EventHandler uses a powerful new reflection feature introduced in Java 1.3. The java.lang.reflect.Proxy class is a factory that can generate adapters implementing any type of interface at runtime. By specifying one or more event listener interfaces (e.g., ActionListener), we get an adapter that implements those listener interfaces generated for us on the fly. The adapter is a specially created class that delegates all the method calls on its interfaces to a designated InvocationHandler object. See Chapter 7 for more information about the reflection interface proxy.

21.11 BeanContext and BeanContextServices

So far we've talked about some sophisticated mechanisms for connecting JavaBeans together at design time and runtime. However, we haven't talked at all about the environment in which JavaBeans live. To build advanced, extensible applications, we'd like a way for JavaBeans to find each other or "rendezvous" at runtime. The java.beans.beancontext package provides this kind of container environment. It also provides a generic "services" lookup mechanism for Beans that wish to advertise their capabilities. These mechanisms have existed for some time, but they haven't found much use in the standard Java packages. Still, they are interesting and important facilities that you can use in your own applications.

You can find a full explanation and example of how to use the Bean context to find Beans and listen for services in the expanded material on the CD-ROM that comes with the book (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/).

21.12 The Java Activation Framework

The Java Activation Framework (JAF) is a standard extension that can be used by Beans that work with many external data types, such as media retrieved from files and streams. It is essentially a generalized content/protocol handler mechanism for JavaBeans. The JAF is an extensible set of classes that wrap arbitrary, raw data sources to provide access to their data as streams or objects, identify the MIME type of the data, and enumerate a registered set of "commands" for operating on the data.

The JAF provides two primary interfaces: DataSource and DataHandler. The DataSource acts like the protocol handlers we discussed in Chapter 13. It wraps the data source and determines a MIME type for the data stream. The DataHandler acts like a content handler except it provides a great deal more than access to the data. A DataHandler is constructed to wrap a DataSource and interpret the data in different forms. It also provides a list of command operations that can be used to access the data. DataHandler also implements the java.awt.datatransfer.Transferable interface, allowing data to be passed among application components in a well-defined way.

The JAF hasn't been used much outside the Java Mail API, but you can find out more about JAF from Sun at http://java.sun.com/beans/glasgow/jaf.html.

21.13 Enterprise JavaBeans

Enterprise JavaBeans is a very big topic, and we can't do more than provide a few paragraphs to whet your appetite. If you want more information, see Enterprise JavaBeans by Richard Monson-Haefel (O'Reilly). The thrust of EJB is to take the JavaBeans philosophy of portable, pluggable components and extend it to the server side to accommodate the sorts of things that multitiered networked and database-centric applications require. Although EJB pays homage to the basic JavaBeans concepts, it is much larger and more special purpose. It doesn't have a lot in common with the kinds of things we've been talking about in this chapter. EJBs are server-side components for networked applications. What EJBs have in common with plain JavaBeans are the concepts of reusable, portable components that can be deployed and configured for specific environments. But in this case, the components encapsulate access to business logic and database tables instead of GUI and program elements.

EJB ties together a number of other Java enterprise-oriented APIs, including database access, transactions, and name services, into a single component model for server applications. EJB imposes a lot more structure on how you write code than plain old JavaBeans. But it does so in order to allow the server-side EJB container to take on a lot of responsibility and optimize your application's activities without you having to write a lot of code. Here are a few things Enterprise JavaBeans tackles:

  • Object life cycle and remote access

  • Container-managed persistence

  • Transaction management

  • Server resource pooling and management

  • Deployment configuration

EJB divides the world into two camps: Entity Beans, which represent data in a database, and Session Beans, which implement services and operations over entity Beans. These correspond well to the second and third tiers in a three-tiered business application. "Business logic" is represented by Session Bean services, and database access is made transparent through automated object mapping by Entity Beans.

Many aspects of EJB behavior can be controlled through "deployment descriptors" that customize Bean behavior for the target environment. The result is a high level of abstraction over ordinary business-specific code. It allows powerful, networked business application components to be packaged and reused in the sort of way that ordinary Beans are reused to build client-side applications.

Sun has created a reference EJB platform as part of Java 2 Enterprise Edition (J2EE); currently, the most robust EJB implementations are provided by third parties. Usually, the EJB container is packaged as part of a more general application server that performs other duties, such as running web services, servlets, and JSPs. There are many vendors of commercial EJB servers. Two popular alternatives are BEA's WebLogic, an application server with many high-end features, and JBoss, an open source application server and implementation of the J2EE APIs. JBoss can be downloaded from http://www.jboss.org.

[1]  Sun has its own, commercial version of NetBeans called Forte for Java. Forte is largely the same as NetBeans but includes additional tools for enterprise development.

[2]  As of this writing, Sun's Molecule example has some problems when used in NetBeans. Selecting a molecule type other than the default causes a compile-time error. You can use the Test Form button on the NetBeans form editor to try the other molecule types.

[3]  Prior to Java 1.3, we developed our own dynamic event adapter in this chapter. That example is still instructive if you want to work with Beans using reflection. You can find it in the file DynamicActionAdapter.java in the examples on the accompanying CD-ROM (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/) or the web site for this book.

CONTENTS


Learning Java
Learning Java, Second Edition
ISBN: 0596002858
EAN: 2147483647
Year: 2002
Pages: 30

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net