So far, we’ve seen how to create and use beans within a bean application builder environment. That is the primary motivation for JavaBeans, at least in GUI 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 could 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.
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 use 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 22-1.
Table 22-1. Methods of the java.beans.Beans class
Operator | Equivalent |
---|---|
| |
| |
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.[47]
Beans.isInstanceOf()
and Beans.getInstanceOf()
do the jobs of checking a bean’s type and casting it to a new type.
These methods were intended to allow one or more beans to work together
to implement “virtual” or dynamic types. They are supposed to allow
beans to take control of this behavior, providing different “views” of
themselves. However, they currently don’t add any functionality and
aren’t widely used.
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:
// Serialize a Juggler instance to a file...
import
magicbeans.sunw.demo.juggler.Juggler
;
import
java.io.*
;
public
class
SerializeJuggler
{
public
static
void
main
(
String
[]
args
)
throws
Exception
{
Juggler
duke
=
new
Juggler
();
ObjectOutputStream
oout
=
new
ObjectOutputStream
(
new
FileOutputStream
(
"juggler.ser"
)
);
oout
.
writeObject
(
duke
);
oout
.
close
();
}
}
Once you have the frozen Juggler, 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
,
JComponent
.
class
)
)
{
JComponent
comp
=
(
JComponent
)
Beans
.
getInstanceOf
(
bean
,
JComponent
.
class
);
getContentPane
().
add
(
"Center"
,
comp
);
}
else
{
System
.
out
.
println
(
"Bean is not a JComponent..."
);
}
}
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 as an
argument and making sure that our magicbeans.jar
file is in your classpath. The name should not
include the .ser extension in the name; the
Beans.instantiate()
method adds this
automatically in its search for the serialized or class version. The
juggler should spring back to life, juggling once again as shown in
Figure 22-9.
In BackFromTheDead
, we use
Beans.instantiate()
to load our
serialized bean by name. We then 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 JComponent
. 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.
Everything we’ve done here could be done using the plain java.io.ObjectInputStream
discussed in Chapter 12. 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 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 12 for a
discussion of object deserialization using the readObject()
method.
We’ve discussed reflection largely in terms of how design tools use it to analyze classes. Today, reflection is frequently 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 16, 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. If we start relying heavily on special adapter classes, we can quickly end up with as many adapters as components. 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.
The java.beans.EventHandler
is a dynamic event dispatcher that simply calls methods in response to
events. What makes the EventHandler
unusual in Java 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.
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.
This 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 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 as well as
other options. We won’t cover every detail of the tool here.
The EventHandler
uses the
java.lang.reflect.Proxy
, which 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 1 for more information about proxy
classes.
[47] This feature would seemingly be applicable to XML-serialized
Java beans using the XMLOutputStream
as well, but it is not
currently implemented for them. This is another sign that the
JavaBeans APIs have stagnated.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.