Полезная информация

Exploring Java

Previous: 17.7 Working with AudioChapter 18Next: 18.2 Building Beans
 

18. Java Beans

Contents:
What's a Bean?
Building Beans
Hand-Coding with Beans
Putting Reflection to Work

JavaBeans[1] is one of the most interesting new directions in Java. As a whole, it 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. To build an application, you may not even need to write code; eventually, when libraries of Beans are widespread, it will be possible to create applications entirely by connecting prefabricated modules using a graphical development tool.

[1] "JavaBeans" refers to the component architecture; "Java Beans" refers to components that use this architecture.

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

18.1 What's a Bean?

So, what exactly is or are Java Beans? As I said earlier, JavaBeans defines a set of rules; Java Beans are ordinary Java objects that play by these rules. That is, Java Beans are Java objects that conform to the JavaBeans API and design patterns so that they can be recognized and manipulated within visual application builder environments. Beans live and work in the Java VM, as do all Java objects. They communicate with their neighbors using events and other normal method invocations.

For examples of Beans, we have to look no further than the java.awt package. All of the familar AWT components, like Button, TextArea, Scrollpane, etc., are not only suggestive of things suitable to be Beans, but are, in fact, Beans! Much of what you learned in Chapter 13 about AWT concepts has prepared you for understanding Beans. Although the AWT components are relatively simple, Beans can also be large and complex application components, like spreadsheets or document editors. We'll talk more about what exactly makes a Bean a Bean in a moment. For now, I want to give you a better sense of how they are used.

I said that Java Beans are objects intended to be manipulated visually, within a graphical application builder. By that I mean that they will generally be chosen from a palette of tools and manipulated graphically in an application builder's workspace. In this sense Beans are somewhat like widgets used in a traditional GUI builder; user interface components can be assembled to make application "screens." But 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, Java Beans can be not only simple UI components like buttons and sliders but more complex and abstract components as well. It is easy to get the impression that Beans are, themselves, always graphical objects (like the AWT components that we mentioned). But Java Beans can implement any part of an application, including "invisible" parts that perform calculations, storage, and communications. We would like to be able to snap together a substantial application using prefabricated Beans, without ever writing a line of code! Three characteristics of the JavaBeans architecture make this possible:

Design patterns

The first important characteristic of a Java Bean 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 tell what events it can fire and receive; we can also learn about its properties (the equivalent of its public variables) and its methods. Beans can also, optionally, provide very explicit information about their features tailored specifically for builder tools.

Reflection

Reflection is an important new feature of the Java language. (It's discussed in Chapter 7.) Reflection makes it possible for Java code to inspect and manipulate Java objects at run-time. In the context of JavaBeans, reflection lets a development tool analyze a Bean's capabilities, change the values of its variables, and invoke its methods. Essentially, reflection allows Java objects that meet at run-time to do all of the things that could be done if they 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" (JavaSoft prefers the word "pickle") a living, breathing 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 of their state and all of their 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.

18.1.1 How Big Is a Bean?

Our examples of Beans 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. In the near future, Java Beans should also be able to team up at run-time to form groups that act like a single, virtual Bean.

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. In this sense, most well-behaved Java objects could already be considered Beans.

A more useful Bean would want to send and receive events or 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 recognized. Most nontrivial Beans will also provide information about themselves in the form of a BeanInfo class. A BeanInfo class implements the BeanInfo interface, which holds methods that can describe a Bean's features. Normally, this "bean info" is supplied by a separate class that is named for and packaged with the Bean.

18.1.2 The BeanBox

We can't have a meaningful discussion of Beans without spending a little time talking about the builder environments in which they will be used. In this book we will talk about the BeanBox container that comes with JavaSoft's Bean Development Kit (BDK).[2] BeanBox is by no means a real application builder environment. Its job is to provide a simple reference platform in which you can test your Beans. BeanBox reads basic Bean information, creates instances of Beans, and allows the most basic hookup of events and properties. It also comes with some interesting test Beans. Aside from that, it offers little. Its main advantage is that it is free (including source code) and universally available because it is written in pure Java. I'll use the BeanBox fairly extensively in this chapter to demonstrate how Beans work. But keep in mind that the BeanBox isn't a real development environment, and that real development tools will do a lot more.

[2] The BDK is available from http://splash.javasoft.com/beans. However, the basic Beans API and support classes are incorporated into Java 1.1; the BDK primarily provides tools like the BeanBox.

Running the BeanBox

Follow JavaSoft's directions for installing the BDK and running the BeanBox. Figure 18.1 shows the Bean palette, BeanBox work area, and a properties sheet (or customizer window). The properties sheet or customizer changes its contents based on the Bean selected in the work area.

Figure 18.1: The Bean palette, a BeanBox, and a properties sheet

Figure 18.1

To add a Bean to the BeanBox, drag it from the palette, and drop it into the work area. (If that doesn't work, try clicking on the Bean in the palette and then clicking in the work area.) Once placed in the BeanBox, a Bean can be selected by clicking on it or just outside of it. You can move the Bean within the BeanBox and reshape it by dragging its corners.

18.1.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 would expect its properties to include its color, label, and other features of its basic appearance. Properties are similar to an object's public variables. Like a variable, a property can be a primitive type (like a number or boolean) or it can be a complex object type (like a String or a collection of spreadsheet data). Unlike variables, properties are implemented using methods, so that a Bean can take action when a property changes. By sending an event when a property changes, a Bean can notify other interested Beans of the change. (See the section "Bound Properties" later in this chapter.)

Let's pull a Bean into the BeanBox and take a look at its properties. Drag a Juggler Bean (one of JavaSoft's test Beans) into the workspace; the animation should start, and Duke should begin juggling his coffee beans as soon as you put him in the BeanBox, as shown in Figure 18.2. Don't worry, we'll have him under our control soon enough.

Figure 18.2: Juggling Beans

Figure 18.2

When the Juggler Bean was first loaded by the BeanBox, it was inspected to discover its properties. When we select a juggler, the BeanBox displays these properties in the properties sheet and allows us to modify them. As you can see in the figure, the juggler has five properties. "Foreground" and "background" are colors; their current values are displayed in the corresponding box. "Font" is the font that would be used if juggler were displaying any text; an example of the font is shown. The "name" is a simple string that identifies the component (don't worry about the value of name just yet).

These four basic properties will become familiar to you; many GUI Beans inherit them from the AWT's Component class. For Juggler, these four properties don't do much. Since Juggler just displays images and doesn't do any drawing, the foreground and background colors and font won't have any visible effect. In fact, these really shouldn't be listed in the properties sheet at all. We'll show how to hide them in our own Beans later.

Now turn your attention to the property called "animationRate". This is a more useful feature. It is an integer property that controls the interval in milliseconds between displaying the juggler's frames. Try changing its value. The juggler changes speed as you type. Good Beans give you immediate feedback on changes to their properties.

Notice that the property sheet understands and provides a way to display and edit each of the different property types. For the foreground and background properties, the sheet displays the color; if you click on it, 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. The BeanBox understands and can edit the most useful basic Java types.

Since the types of properties are open ended, BeanBox can't possibly anticipate them all. Beans with more complex property types can supply a property editor; 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. The Molecule Bean that we'll play with in the next section uses a custom property editor that lets us choose the type of molecule.

18.1.4 Events Hookups and Adapters

Beans use events to communicate. As we described in Chapter 13, events are not limited to GUI or AWT 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.

Sometimes it is useful to place an adapter object between an event source and a listener. The adapter can translate the event into some other action, like a call to a different method or an update of some data. One of the jobs of the BeanBox is to let us hook up event sources to event listeners. Another job is to provide or produce adapters that allow us to hook up events in more complex ways.

But before we get into details, let's look at Figure 18.3 and try to get our Juggler under control. Grab a button Bean--the one called ExplicitButton--from the palette, and drop it in the workspace. Using the properties sheet, edit its label to "Start". Now while the Start button is selected, pull down the Edit menu of the BeanBox. Choose the submenu Events. You will see a menu showing the listener interfaces to which the button can send events. The names may not match the interface names that you're familiar with, but the relationship between the menu and the interfaces should be clear. (The button provides "friendly" names for the interfaces, rather than using the unadorned interface names. You also see a "bound property change" event category; that's another kind of listener defined by JavaBeans, which we'll discuss soon.) Select the Button push submenu (which corresponds to the ActionListener interface), and you'll see the actual event types that can be sent. In this case, there's only one: "action performed". Choose that. Recall that buttons and other AWT components generate ActionEvents when they are used; you have just chosen an event source. You should see a red line that looks like a rubber band stretching from the button. Drag the line over to the Juggler, and click on it. A dialog will appear, prompting you to choose a method to which to "hook" this event.

Figure 18.3: Connecting events to Beans

Figure 18.3

What does it mean to hook an event to a method? If you remember our discussion of AWT, you know that event sources signal event listeners through a very specific method, namely one defined by a listener interface. Furthermore, all the methods that can handle an ActionEvent accept an ActionEvent as an argument. Some of the methods the target dialog presents surely don't take ActionEvents as arguments. And if you take a peek at the Juggler source code, you will see that it doesn't even implement an appropriate listener interface. How can we direct events to it at all?

The answer is that the BeanBox automatically makes an adapter class for us, giving us the option of delivering an event to any method that could possibly make sense. That includes any method that could accept the ActionEvent object as an argument, including methods that take as an argument the type Object. More importantly, it includes methods that take no arguments at all. In that case, the BeanBox creates an adapter that throws away the ActionEvent and invokes the target method whenever the event is fired.

The Juggler methods we're interested in are startJuggling() and stopJuggling(). Select startJuggling to complete the hookup of our Start button. The BeanBox briefly displays a message saying that it is creating and compiling the necessary adapter class. Follow the same procedure to create a Stop button, and hook it to stopJuggling. Finally, the Juggler will do our bidding. You should be able to start and stop him with the buttons. Choose the Save option from the menu to save the state of the BeanBox; we'll use the controllable Juggler later in another example.

Let's look at one more interesting example, shown in Figure 18.4, before moving on. Grab a Molecule Bean, and place it in the BeanBox. 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. Now let's see what we can do with our molecule. Grab a TickTock Bean from the palette. TickTock is a timer. Every so many seconds, TickTock fires a PropertyChangeEvent, which is an event defined by JavaBeans that notifies Beans of a change to another Bean's properties. The timer is controlled by an integer property called "interval," which determines the number of seconds between events. TickTock is an "invisible" Bean; it is not derived from an AWT Component and doesn't have a graphical appearance, just as an internal timer in an application wouldn't normally have a presence on the screen. BeanBox represents invisible Beans by a simple dashed border and a label containing its name.

Figure 18.4: The Molecule Bean and the timer

Figure 18.4

Grab the PropertyChangeEvent from the Events submenu, and click on our Molecule as the target for the event. Hook the event to the rotateOnX() method. Now the Molecule should turn on its own, every time it receives an event from the timer (which is controlled by the "interval" property). Try changing the interval. You could also hook TickTock to the Molecule's rotateOnY() method, or you could use a different instance of TickTock and cause it to turn at different rates in each dimension by setting different intervals. There is no end to the fun.

18.1.5 Bound Properties

By using a combination of events and smart 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.

Grab two JellyBean Beans from the palette, and drop them in the BeanBox, as shown in Figure 18.5. You'll notice that a JellyBean has the four simple properties of an AWT component, plus an integer property called "price in cents". Select the Bind Property option under the Edit menu. (Yes, this is new, it wasn't there with our other Beans.) A dialog appears, asking which property we would like to bind. Choose "price in cents". Now drag the rubber band over to the second JellyBean. Another dialog appears, asking to which property you would like to bind this value. In this case, there is only one appropriate property: the corresponding "price in cents". However, if a JellyBean had other integer properties, the dialog would list more options. After you choose the price property, BeanBox will say that it is creating and compiling an adapter. When the hookup is complete, go back to the first Bean, and try changing its price. Switch to the second, and you'll see that its price has changed as well. The second Bean's property has been bound to the first.

Figure 18.5: Binding properties

Figure 18.5

How does this work? It's only slightly more complicated than our previous example in which we hooked an event to an arbitary method. In that case the BeanBox generated an adapter that received the event and, in turn, invoked the method. Bound properties rely on the fact that the source Bean promises to fire a PropertyChangeEvent whenever one of its properties changes. The JellyBean supports this feature, and that is why the Bind propery option appears in the menu for it. BeanBox uses the feature by generating an adapter that listens for the PropertyChangeEvent and updates the property value in the target. Whenver the adapter receives the event, it finds the new value and sets it in the target Bean. Try binding the price 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 loops from happening here.)

If you look under the Events submenu for one of the JellyBeans you'll see the PropertyChangeEvent that we described. You can use this event like any other event; for example, you could go ahead and hook it up to a method. Try setting things up so that your Molecule rotates when you change the price of the JellyBean. A more appropriate use for PropertyChangeEvent would be to connect it to the reportChange() method of a ChangeReporter test Bean. The ChangeReporter will then display a message describing each change event it receives.

The JellyBean only has type of PropertyChangeEvent. How does the recipient know which property has changed? Well, for a simple Bean, a PropertyChangeEvent is fired whenever any bound property changes; information in the event describes which property changed. A sophisticated Bean could provide a separate type of PropertyChangeEvent for each bindable property.

18.1.6 Constrained 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 does not go forward, and the old value is used.

The JellyBean supports one constrainable property: the "price in cents". To try this out, grab a Voter Bean from the palette. The Voter Bean listens for constrained PropertyChangeEvents and votes "yes" or "no" on them, depending on the value of its "vetoAll" property. Hook up the "vetoableChange" event from one of your JellyBeans to the vetoableChange() method of the Voter Bean. By default, the Voter vetos all change requests, as shown in Figure 18.6. Try changing the price of the JellyBean. The BeanBox should notify you that the value cannot be changed. If you set the "vetoAll" property to "yes," you will be free to change the price again.

Figure 18.6: Vetoing all change requests

Figure 18.6

How are constrained properties implemented? 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.

Beans can handle the process of proposing changes in two ways. The first is to use a "two phase commit" style, in which the Bean first issues a vetoable change. If the change passes (i.e., none of the listeners throw a PropertyVetoException), the Bean issues a regular property change. The Bean takes no action until the second (regular) property change arrives. The second strategy is to allow Beans to act on the vetoable change; if the change is rejected by a listener, the Bean sends out a followup vetoable change to restore the property's original value. In this scenario, it would be legitimate to ignore a crazy listener that wouldn't take the old value back.

Keep in mind that binding properties and constraining properties are two separate issues. We can have either one without the other. How popular builder environments will choose to represent the two features remains to be seen. While the BeanBox does a good job of binding properties for us, it does not currently shield us from the details of hooking up constrained properties. In a real builder environment, the two processes would presumably be made to look more similar.


Previous: 17.7 Working with AudioExploring JavaNext: 18.2 Building Beans
17.7 Working with AudioBook Index18.2 Building Beans

Other Books in this LibraryJava in a NutshellJava Language ReferenceJava AWTJava Fundamental ClassesExploring Java