Using Dynamic Proxies to Generate Event Listeners Dynamically

   

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.

 

Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve
JavaOne Banner
Java 8 banner (182)