Wednesday 17 January 2007

Java Dynamic Proxies: One Step from Aspect-oriented Programming

Dynamic Proxies are a nifty, often overlooked feature of the Java language. Introduced in J2SE 1.3, they allow you to intercept method calls so you can interpose additional behavior between a class caller and its"callee". From the caller's perspective, a proxy is no different from the real class that it encapsulates — it presents the same interfaces and follows all the rules and conventions expected of the real class. For savvy programmers, proxies present a unique time saving opportunity. Why spend effort inserting and maintaining such common code as debugging and logging when you can just centralize it and put in a proxy instead? Logging, retry semantics,performance metrics, performance optimizations, test stubs, and caching are examples of application concerns that cut across class implementations regardless of design and business function. Trends within the Java community are modeling such concerns as aspects—important to the application but orthogonal to the main task at hand. Rather than coding this logic painstakingly throughout your application, wouldn't it be nice if you could just express this behavior in one place and have it apply to every class? With Dynamic proxies, you can easily do so—and once youunderstand proxies, you've made a small conceptual leap towards the bigger picture—aspect-oriented programming (AOP).

Dynamic Proxy Basics
A dynamic proxy is a class that implements a list of interfaces, which you specify at runtime when you create the proxy. To create a proxy, use the static method java.lang.reflect.Proxy.newP
roxyInstance().This method takes three arguments:

  • The class loader to define the proxy class
  • An invocation handler to intercept and handle method calls
  • A list of interfaces that the proxy instance implements
The returned Proxy instance behaves like any other class that implements the supplied interfaces. The statement, proxy instanceof MyInterface, returns true and casts to any of the target interfaces. For example,
MyInterface obj = (MyInterface) proxy
will also succeed.

From the code perspective, the proxy instance is no different from a normal class. Provided the actual creation of the proxy is encapsulated (e.g., inside a factory or ServiceLocator), the proxy is totally transparent. Behavior can be inserted dynamically without changing the way your classes are wired or how your code interacts. This transparency enables you to add common code easily and quickly without affecting class collaborations or having to add (or test) your extra code explicitly for every class.

At the heart of the Proxy mechanism is the InvocationHandler interface. You must supply an InvocationHandler instance when you create a proxy instance. The InvocationHandler is responsible for intercepting and dispatching all method calls made on the proxy. It contains one method: invoke(Object proxy, Method method, Object[] args). The first argument is the proxy instance that has been invoked, the second argument is the target method, and the third argument (args) are the runtime parameters that the caller supplies. You must add any additional behavior inside the invoke() method, as this will get executed whenever a method is executed on the proxy.

Take a common cross cutting concern, logging, as an example. Create a simple InvocationHandler class that logs method entries and exits using System.out.println. Your class must implement the InvocationHandler interface and provide an implementation of the invoke() method. Pass the real class inas a parameter on the constructor, as follows:

public class LoggingHandler implements InvocationHandler {
    protected Object delegate;

    public LoggingHandler(Object delegate) { 
         this.delegate = delegate; 
    }

    public Object invoke(Object proxy,Method method,
                   Object[] args) throws Throwable {
        try {
            System.out.println("Calling method "+ method+ " at "
                + System.currentTimeMillis());
            Object result = method.invoke(delegate, args);
            return result;
         } catch (InvocationTargetException e) {
             throw e.getTargetException();
         } finally {
             System.out.println("Called method("+ method+ " at "
                + System.currentTimeMillis());
         }
      }
}

No comments:

Post a Comment