JavaBeans from Square One
By Bruce Eckel
Java is invaluable for creating reusable pieces of code, and the most reusable unit of code is the class, which comprises a cohesive unit of characteristics and behaviors that can be reused, either via composition or through inheritance.
Inheritance and polymorphism are essential to object-oriented programming, but when you're putting together an application, what you really want is components that you can drop into your design like an electronic engineer places chips on a circuit board (or even, in the case of Java, onto a Web page). It seems, too, that there should be some way to accelerate this module-assembly style of programming.
Visual programming first became successful, very successful, with Microsoft's Visual Basic (VB), followed by Borland's Delphi (the primary inspiration for the JavaBeans design). The reason that visual programming tools have been so successful is that they dramatically speed up the process of building an application -- certainly the user interface -- but often other portions of the application as well. These programming tools represent components visually, which makes sense since they usually display some kind of visual component, like a button or text field. The visual representation is, in fact, often identical to the component's appearance in the running program. So part of visual programming involves dragging a component from a palette and dropping it onto a form -- the application-builder tool writes code as you do this -- and that code causes the component to be created in the running program.
Simply dropping the component onto a form is normally not enough to complete the program. Usually the component's characteristics need to be changed: color, font, what database it's connected to, and so on. Modifiable characteristics, or "properties," can be manipulated inside the application-builder tool, and when you create the program this configuration data is saved so that it can be rejuvenated when the program is started.
But an object is more than characteristics; it's also a set of behaviors. At design time, visual-component behaviors are represented by "events," or things that can happen to the component. Normally, you determine what will happen when an event occurs by tying code to it.
Here's the critical part: The application-builder tool can dynamically interrogate a component to find out what properties and events it supports. Once it knows what they are, it can display them, allowing you to change properties and saving the state when you build the program. Generally, double clicking on an event causes the application-builder tool to create a code body and ties it to that particular event. You simply write the code that executes when the event occurs.
Because the application-builder tool manages all these connection details, you can focus on the program's appearance and functionality.
What is a Bean?
After the dust settles, then, a component is really just a block of code, typically embodied in a class. The key issue is the ability of the application-builder tool to discover the properties and events for that component. To create a VB component, the programmer had to write a fairly complicated piece of code, following certain conventions to expose the properties and events. Delphi was a second-generation visual-programming tool actively designed around visual programming, making it much easier to create a visual component. JavaBeans, however, has brought the creation of visual components to its most advanced state, because a Bean is just a class. You needn't write any extra code or use special language extensions to make something a Bean. You simply modify your method name to indicate to the application-builder tool whether it is a property, an event, or just an ordinary method.
For a property named
Xxx
, for example, you typically create two methods:getxxx
andsetxxx
. (The first letter is automatically decapitalized to produce the property name.) The type produced by theget
function is the same as the type of the argument to theset
function. The name of the property and the type for theget
andset
are not related.
You can use this approach for a Boolean property as well, but you may also use
isxxx
instead ofgetxxx.
Ordinary Bean methods don't conform to this naming convention, but they'republic
.
Events use the "listener" approach:
addFooBarListener(FooBarListener)
andremoveFooBarListener(FooBarListener)
to handle aFooBarEvent
. The built-in events and listeners will usually satisfy your needs, but you can also create your own.
(Most of the method-name changes that occurred between Java 1.0 and Java 1.1 had to do with adapting to the
get
andset
naming convention, in order to make that particular component into a Bean.)
We can use these guidelines to create a very simple Bean; see Listing One. You can see that it's just a class. Normally, all your fields will be
private
, and accessible only through methods. Following the naming convention, the properties arejumps
,color
,spots
, andjumper
. Although the name of the internal identifier is the same as the name of the property in the first three cases,jumper
shows that the property name does not force you to use any particular name for internal variables (or indeed, to even have any internal variables for that property).
This Bean handles
ActionEvent
andKeyEvent
, based on the naming of theadd
andremove
methods for the associated listener. Finally, the ordinary methodcroak()
is still part of the Bean simply because it's apublic
method, not because it conforms to any naming scheme.
One of the most critical parts of the Bean scheme occurs when you drag a Bean off a palette and plop it down on a form. The application-builder tool must be able to create the Bean (which it can, if there's a default constructor) and then, without access to the Bean's source code, extract all the necessary information to create the property sheet and event handlers.
Part of the solution is already evident from an earlier column in this series ("Discovering Information at Run Time: Part Deux," March, 1997): Java 1.1 "reflection" allows all the methods of an anonymous class to be discovered. This solves the Bean problem without any extra language keywords like those required in other visual-programming languages. In fact, one of the prime reasons reflection was added to Java 1.1 was to support Beans (as well as object serialization and remote method invocation).
This might lead you to expect that the creator of the application-builder tool would have to reflect each Bean and hunt through its methods to find its properties and events. However, Java designers wanted to provide a standard interface for everyone to use, not only to make Beans simpler to use, but also to provide a standard gateway to the creation of more complex Beans. This interface is the
Introspector
class, and its most important method is thestatic getBeanInfo()
. When you pass this method aClass
handle, it fully interrogates that class and returns aBeanInfo
object that can be dissected to find properties, methods, and events.
Normally, you won't care about any of this -- you'll probably get most of your Beans off the shelf, drag them onto your form, then configure their properties and write handlers for the events that interest you. However, using the
Introspector
to display information about a Bean is an interesting and educational exercise; see Listing Two.
BeanDumper.dump() is the method that does all the work. First, it tries to create a BeanInfo object; if successful, it calls the methods of BeanInfo that produce information about properties, methods, and events. Introspector.getBeanInfo()'s second argument tells the Introspector where to stop in the inheritance hierarchy. Here, it stops before it parses all the methods from Object, since we're not interested in them. For properties, getPropertyDescriptors() returns an array of PropertyDescriptors. For each PropertyDescriptor, you can call getPropertyType() to find the class of object passed in and out via the property methods. Then, you can extract the pseudonym for each property from the method names with getName(), the method for reading with getReadMethod(), and the method for writing with getWriteMethod(). These last two methods return a Method object that can actually be used to invoke the corresponding method on the object (this is part of reflection).
For the public methods (including the property methods),
getMethodDescriptors()
returns an array ofMethodDescriptor
s: You can get the associatedMethod
object for each and print out their names.
For the events,
getEventSetDescriptors()
returns an array ofEventSetDescriptor
s that can be queried to find out the class of the listener, the methods of that listener class, and the add- and remove-listener methods. TheBeanDumper
program prints out all of this information.
If you invoke
BeanDumper
on theFrog
class like this:java BeanDumper Frog
, the output will look like Listing Three.
This reveals most of what the
Introspector
sees as it produces aBeanInfo
object from your Bean. The property type and name are independent. The property name is decapitalized except when it begins with more than one capital letter in a row, and the method names (such as the read and write methods) are produced from aMethod
object that can be used to invoke the associated method on the object.
The public method list includes both methods associated with a property or event and those that are not, such as
croak()
. These are all the methods that you can call programmatically for a Bean, and the application-builder tool can list them while you're making method calls, to ease your task.
Finally, you can see that the events are fully parsed out into the listener, its methods and the add- and remove-listener methods. Basically, once you have the
BeanInfo
, you can find out everything of importance for the Bean. You can also call its methods, even though you don't have any other information except the object itself (again, a feature of reflection).
A More Sophisticated Bean
This next example is slightly more sophisticated, albeit frivolous. It's a canvas that draws a little circle around the mouse whenever it moves. When you press the mouse, the word "Bang!" appears in the middle of the screen, and an action listener is fired.
You can adjust the size of the circle as well as the color, size, and text of the word displayed when you press the mouse. A
BangBean
also has its ownaddActionListener()
andremoveActionListener()
, so you can attach your own listener to be fired when the user clicks on theBangBean
.
Listing Four shows how
BangBean
implements theSerializable
interface. The application-builder tool can "pickle" all the information for theBangBean
using serialization after the program designer has adjusted the property values. When the Bean is created as part of the running application, these pickled properties are restored so you get exactly what you designed.
All the fields are
private
; normally, access to a Bean is allowed only through methods, usually using the property scheme.
To avoid multithreading issues,
addActionListener()
is "unicast" -- it only notifies one listener when the event occurs -- and may therefore throw aTooManyListenersException
. Normally, you'll use "multicast" events, which notify many listeners. In fact, you should expect all Beans to be run in a multithreaded environment; for more details, see Chapter 14 of "Thinking in Java."
Pressing the mouse puts the text in the middle of the
BangBean
; if theactionListener
field is notnull
, itsactionPerformed()
is called. Whenever the mouse is moved, its new coordinates are captured and the canvas is repainted (erasing any text on the canvas).
TestBangBean creates a Frame and places a BangBean within it, attaching a simple ActionListener to the BangBean. Normally, of course, the application-builder tool would create most of the code that uses the Bean.
When you run the
BangBean
throughBeanDumper
, you'll notice many more properties and actions. That's becauseBangBean
is inherited fromCanvas
, andCanvas
is itself a Bean, so you see its properties and events as well.
More Complex Bean Support
Creating a Bean can be remarkably simple, but you aren't limited to what I've shown here. The JavaBeans design can scale to more complex situations. There are several ways to add sophistication with properties:
Indexing. Here, I've shown single properties, but it's also possible to represent multiple properties in an array. This is called an "indexed property." You simply provide the appropriate methods and the
Introspector
recognizes an indexed property so your application-builder tool can respond appropriately.
Binding. A "bound" property notifies other objects via a
PropertyChangeEvent
. The other objects can then change themselves based on the change to the Bean.
Constraining. Other objects can veto unacceptable changes to a constrained property. The other objects are notified using a
PropertyChangeEvent
, and they may throw aProptertyVetoException
to prevent the change from happening and to restore the old values.
There are also ways to customize the way your Bean is represented at design time:
- A custom property sheet for your particular Bean can be automatically invoked when your Bean is selected; all other Beans will use the ordinary property sheet.
- A custom editor can be automatically invoked for a particular property, instead of the ordinary property sheet.
- A custom
BeanInfo
class for your Bean can produce different information than the default created by theIntrospector
.
It's also possible to turn "expert" mode on and off in all
FeatureDescriptor
s to distinguish between basic features and more complicated ones.
Conclusion
The arrival of JavaBeans on the scene has not been without disruption. For Sun, supporting JavaBeans has meant changes to Java 1.0, such as the new 1.1 event model, plus adding some features such as serialization. However, these changes represent genuine improvements to Java technology and will insure that Java remains an important, strategic platform for years to come.
(Get the source code for this article here.)
Bruce is the author of Thinking in Java (freely available at www.EckelObjects.com/Eckel) from which this article is adapted, Thinking in C++ (Prentice-Hall, 1995), and C++ Inside & Out (Osborne/McGraw-Hill 1993). He is the C++ & Java track chair for the Software Development conference and provides seminars and design consulting in C++ and Java. He can be reached at [email protected].