Introduction to Java Dynamic Proxy


Bhaskar S 11/04/2011


Overview

We have all had situations where we wanted to intercept calls to some method(s) and do interesting things like logging or measuring performance etc. Enter the Proxy design pattern.

The word proxy means substitute for another. In other words, a proxy is an object that implements the interface of a target object and acts as a substitute for the target object. Let us explore this popular design pattern with an example of an arbitrary Product Inventory checker application. Given a product code, find how many are in the inventory.

Hands-on with Code

The following is an interface for our Product Inventory checker:

Listing.1
package com.polarsparc.dynamicproxy;

public interface ProductInventory {
public int checkInventory(String code);
}

The following is an arbitrary implementation of the ProductInventory interface that kind of mimics database access:

Listing.2
package com.polarsparc.dynamicproxy;

import java.util.Random;

public class DbProductInventory implements ProductInventory {
private Random rand = new Random();

@Override
public int checkInventory(String code) {
int qty = rand.nextInt(10);

// Simulate DB access delay for upto 5 secs

try {
Thread.sleep(rand.nextInt(5000));
}
catch (Exception ex) {
}

return qty;
}
}

The following is an arbitrary application that uses the above Product Inventory checker implementation DbProductInventory:

Listing.3
package com.polarsparc.dynamicproxy;

public class ProductInventoryApplication1 {
public static void main(String[] args) {
ProductInventory inventory = new DbProductInventory();

System.out.printf("Quantity for P1000 = %d\n", inventory.checkInventory("P1000"));
}
}

After testing our arbitrary Product Inventory checker application, we release it to production for general use. Everything seems to go well until one day the users start complaining of slowness in using our Product Inventory checker application.

To analyze and troubleshoot the performance issue, we decide to measure the performance of the checkInventory method in our Product Inventory checker application. A simple way to achieve that would be to instrument the method checkInventory in our ProductInventory implementation class DbProductInventory as follows:

Listing.4
package com.polarsparc.dynamicproxy;

import java.util.Random;

public class DbProductInventory implements ProductInventory {
private Random rand = new Random();

@Override
public int checkInventory(String code) {
int qty = rand.nextInt(10);

long start = System.currentTimeMillis();

// Simulate DB access delay for upto 5 secs
try {
Thread.sleep(rand.nextInt(5000));
}
catch (Exception ex) {
}

long end = System.currentTimeMillis();

System.out.printf("[%s] checkInventory for code %s took %d ms", new Date().toString(), code, (end-start));

return qty;
}
}

This would work, but what if we wanted to instrument many more method(s). Yikes !!! That is a lot of painstaking and cumbersome task. A better and cleaner approach would be to implement the same using the Proxy design pattern.

The following class implements the Proxy design pattern for our ProductInventory instance:

Listing.5
package com.polarsparc.dynamicproxy;

import java.util.Date;

public class ProductInventoryProxy {
private ProductInventory target;

public ProductInventoryProxy(ProductInventory target) {
this.target = target;
}

public int checkInventory(String code) {
long start = System.currentTimeMillis();

int qty = target.checkInventory(code);

long end = System.currentTimeMillis();

System.out.printf("[%s] checkInventory for code %s took %d ms\n",
new Date(), code, (end-start));

return qty;
}
}

To use the Proxy implementation, we need to change the arbitrary application as follows:

Listing.6
package com.polarsparc.dynamicproxy;

public class ProductInventoryApplication2 {
public static void main(String[] args) {
ProductInventory target = new DbProductInventory();

ProductInventoryProxy inventory = new ProductInventoryProxy(target);

System.out.printf("Quantity for P1000 = %d\n", inventory.checkInventory("P1000"));
}
}

See how clean and elegant it is !!! This is the power of the Proxy design pattern.

The one drawback to this approach is that we will need a proxy implementation for every interface that we wish to instrument in our application. Wouldn't it be great if we just had one class for instrumentation and use it dynamically at runtime across many interface(s) in our application !!! This is exactly what the Java Dynamic Proxy that was introduced in Java 1.3 does.

Java dynamic proxies allow one to dynamically create proxy classes on the fly at runtime for specified interface(s). All method calls on a dynamic proxy are dispatch to a single method called invoke on a single interface called the InvocationHandler. Every dynamic proxy class has a corresponding invocation handler class associated with it.

The following is the interface for InvocationHandler taken from the Java JDK source code for illustration purposes:

Listing.7
package java.lang.reflect;

public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

The InvocationHandler interface defines a single method called invoke. Every time a method from one the interface(s) of the dynamic proxy is called, it is dispatched to the invoke method on the corresponding implementation of the InvocationHandler.

The invoke method is called with the following arguments:

  1. The instance of the dynamic proxy class on which the method was called
  2. The instance of method of the corresponding interface method that was called on the dynamic proxy
  3. The array of arguments that were passed to the corresponding interface method that was called on the dynamic proxy

The following is our instrumentation class MethodTimingProxy that implements the InvocationHandler interface for measuring the checkInventory method in our Product Inventory checker application:

Listing.8
package com.polarsparc.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class MethodTimingProxy implements InvocationHandler {
private Object target;

public MethodTimingProxy(Object target) {
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long start = System.currentTimeMillis();

Object out = method.invoke(target, args);

long end = System.currentTimeMillis();

System.out.printf("[%s] %s <proxy> for code %s took %d ms\n",
new Date().toString(), method.getName(), args[0], (end-start));

return out;
}
}

Notice that the above implementation of the InvocationHandler interface is generic in nature and does not have references to any of our Product Inventory checker application interface or classes.

Next, we will need a way to dynamically create an instance of the proxy for the interface ProductInventory. Remember that the proxy will need reference to an instance of MethodTimingProxy class. In order to create the dynamic proxy, we will use the following factory class:

Listing.9
package com.polarsparc.dynamicproxy;

import java.lang.reflect.Proxy;

public class ProductInventoryProxyFactory {
public static ProductInventory createProductInventoryProxy(ProductInventory target) {
return (ProductInventory) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MethodTimingProxy(target));
}
}

The call to Proxy.newProxyInstance dynamically creates the byte code for the proxy at runtime and instantiates it in the Java Virtual Machine.

To use the Java Dynamic Proxy, we need to change the arbitrary application as follows:

Listing.10
package com.polarsparc.dynamicproxy;

public class ProductInventoryApplication3 {
public static void main(String[] args) {
ProductInventory target = new DbProductInventory();

ProductInventory inventory =
ProductInventoryProxyFactory.createProductInventoryProxy(target);

System.out.printf("Quantity for P1000 = %d\n", inventory.checkInventory("P1000"));
}
}

Cool, ain't it !!! We can use the same MethodTimingProxy implementation to measure the performances of other method(s) in other interface(s) in our application.