Using Dynamic Proxies to Generate Event Listeners
Dynamically
by Mark Davidson
This brief article is an update to the article Generating
Event Listeners Dynamically. Since that article was written,
a new Dynamic
Proxy API has been introduced in Java 2 Standard Edition (J2SE)
version 1.3, which makes it simpler to dynamically create event
listener classes. The Dynamic Proxy API takes the place of the Proxy
generation and compilation classes that were provided with the original
article. This article may be of interest to developers of IDEs and
other kinds of applications that generate listeners for Swing and
AWT.
The Problem with the Old Mechanism
The code for dynamically generating listener classes described
in the old article no longer works in J2SE 1.3 because the security
model has been tightened up. In the old code, the default behavior
of the Proxies factory method was to create a dynamic adapter
class in the package for which the listener interface was defined.
For example, if the listener interface is defined in the java.awt.event
package, the generated adapter would also be placed in that package.
Starting with J2SE 1.3, an application cannot create new classes
in the core Java packages. The class loader throws a SecurityException
when an attempt is made to load or dynamically create a class into
the core Java namespace.
Although the original listener-compiler could have been changed
to generate its listener implementations in a different package,
it's much easier to use the new Dynamic
Proxy API. Something similiar to the original article's Proxies,
ProxyAssember and ProxyCompiler classes have been incorporated
in the Dynamic Proxy API and are now part of the core Java platform.
Introducing the New Dynamic Proxy API
The Dynamic Proxy API may be used to dynamically generate event
listeners. This API includes the InvocationHandler
interface and the Proxy
class, which contains a set of static factory methods. These classes
are defined in the java.lang.reflect
package.
To use the dynamic proxy mechanism, you pass an array of interfaces
to the java.lang.reflect.Proxy.newInstance() factory
method. The method returns a dynamically generated instance of a
class that implements these interfaces.
One of the arguments to the factory method is an instance of a
class that implements the java.lang.reflect.InvocationHandler
interface. The InvocationHandler is responsible for handling
method invocations on an instance of the dynamically created class.
This means that any method invocation on the dynamically created
object will be redirected to the InvocationHandler.invoke()
method. This functionality allows the application developer to have
complete control over the behavior of method invocations.
As an example, to create a proxy implementation of an interface
Foo, whose methods are all redirected to InvocationHandler
myHandler, one would write:
ClassLoader cl = Foo.class.getClassLoader();
Foo aFoo = (Foo)Proxy.newProxyInstance(cl,
new Class[]{Foo.class}, myHandler);
Dynamic event adapters may be created using this methodology by
implementing the InvocationHandler.invoke() method
to execute a method on a target object when a listener method is
called by a source object.
Example Implementation of the Dynamic Proxy API
All the code which demonstrates using the Dynamic Proxy API to
dynamically generate listener classes can be found in these files:
- GenericListener.java
- An implementation of the GenericListener class from the
original article that uses Dynamic Proxy API
- Demo.java - Example
code using the GenericListener. Unchanged from the original article.
These classes require the Dynamic Proxy API introduced in Java
2 SE version 1.3. You can download a beta version of J2SE 1.3 from
the Java Developer Connection. You will need to register at the
Java Developer Connection to gain access. Registration is free.
In the GenericListener
implementation, DefaultInvoker is a static inner class which
implements the java.lang.reflect.InvocationHandler
handler interface. The invoke() method handles the default Object
methods such as toString(), hashCode() and equals().
It also handles default return values for any other method that
the subclass doesn't handle.
/**
* Implementation of the InvocationHandler which handles the basic
* object methods.
*/
private static class DefaultInvoker implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getDeclaringClass() == Object.class) {
String methodName = method.getName();
if (methodName.equals("hashCode")) {
return proxyHashCode(proxy);
} else if (methodName.equals("equals")) {
return proxyEquals(proxy, args[0]);
} else if (methodName.equals("toString")) {
return proxyToString(proxy);
}
}
// Although listener methods are supposed to be void, we
// allow for any return type here and produce null/0/false
// as appropriate.
return nullValueOf(method.getReturnType());
}
...
} |
The GenericListener.create() method generates a listener
object and returns it to the calling method. The first step is the
creation of an InvocationHandler object from the DefaultInvoker
class. The handler overrides the invoke() method
so that the targetMethod is executed on the target
object when the matching listener method is invoked. If the invoked
method is not the desired listener method then it is passed to the
superclass for handling.
/**
* Return a class that implements the interface that contains
* the declaration for listenerMethod. In this new class,
* listenerMethod will apply target.targetMethod
* to the incoming Event.
*/
public static Object create(
final Method listenerMethod,
final Object target,
final Method targetMethod)
{
/* Create an instance of the DefaultInvoker and override the invoke
* method to handle the invoking the targetMethod on the target.
*/
InvocationHandler handler = new DefaultInvoker() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// Send all methods execept for the targetMethod to
// the superclass for handling.
if (listenerMethod.equals(method)) {
return targetMethod.invoke(target, args);
} else {
return super.invoke(proxy, method, args);
}
}
};
Class cls = listenerMethod.getDeclaringClass();
ClassLoader cl = cls.getClassLoader();
return Proxy.newProxyInstance(cl, new Class[]{cls}, handler);
}
|
An instance of a dynamic listener class is created with the java.lang.reflect.Proxy.newProxyInstance
factory method using the listener interface and the created handler
object. (The generated proxy classes are internally cached according
to the class loader and list of interfaces). The dynamic listener
class instance is returned to the calling method.
Demo.java shows an example of a dynamic
listener generated for a button. We create an ActionListener
for button2 so that when the actionPerformed()
method is executed, it calls the button2Action() method
on the current Demo object. The convenient version of the
GenericListener.create() method is called, which allows
for the specification of string method names.
ActionListener button2ActionListener =
(ActionListener)(GenericListener.create(
ActionListener.class,
"actionPerformed",
this,
"button2Action"));
button2.addActionListener(button2ActionListener);
|
The GenericListener.create() method constructs a
listener class from the specification of a listener interface, the
listener method, a target, and a target method. The generated proxy
is added as an action listener on the button. The Demo class
implementation has not changed from the original article.
Summary
The creation of a dynamic event listener object using the Dynamic
Proxy API introduced in Java 2 SE version 1.3 is a simpler mechanism
of creating generic event adapters. This mechanism is compatible
with the security changes introduced in J2SE version 1.3, and accomplishes
the following goals:
- It reduces the number of anonymous inner classes and single-use
inner classes for event handling.
- The internal listener classes are generated automatically and
at runtime.
- IDEs and other tools that load classes dynamically can use it
to generate event bindings for listener interfaces that are not
known until runtime.
|