JavaBeans 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.
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).
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
package. All of the familar AWT components, like
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:
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 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.
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.
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
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.
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). 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.
 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.
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.
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.
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.
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.
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
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
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.
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
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
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
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
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.
PropertyChangeEvent from the
Events submenu, and click on our Molecule as the target for the event.
Hook the event to the
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
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.
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.
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
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
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
PropertyChangeEvent for each bindable property.
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
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.
How are constrained properties implemented? Normally,
PropertyChangeEvents are delivered to a
method in the listener. Constrained properties are implemented by
PropertyChangeEvents to a
separate listener method called
vetoableChange() method throws a
PropertyVetoException if it doesn't like a
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.