Dynamic Proxies
The engineers at Oozinoz occasionally face performance problems. They’d like a way to instrument code without making major changes to the design.
Java has a feature that can help with this: Dynamic proxies let you wrap a proxy object around another object. You can arrange for the outer object—the proxy—to intercept all the calls intended for the wrapped object. The proxy will usually pass these calls on to the wrapped object, but you can add code that executes before or after the intercepted calls. Limitations to dynamic proxies prevent you from wrapping any arbitrary object. Under the right conditions, though, dynamic proxies give you complete control over the operation of an object that you want to wrap with a proxy.
Dynamic proxies work with the interfaces that an object’s class implements. The calls that the proxy can intercept are calls that one of these interfaces defines. If you have a class that implements an interface with methods you want to intercept, you can use dynamic proxies to wrap an instance of that class.
To create a dynamic proxy, you must have a list of the interfaces that you want to intercept. Fortunately, you can usually obtain this list by interrogating the object that you want to wrap, using a line such as:
Class[] classes = obj.getClass().getInterfaces();
This code establishes that the methods you want to intercept are those that belong to interfaces that an object’s class implements. To build a dynamic proxy, you need two other ingredients: a class loader and a class that contains the behavior that you want to execute when your proxy intercepts a call. As with the list of interfaces, you can obtain an appropriate class loader by using the one associated with the object that you want to wrap:
ClassLoader loader = obj.getClass().getClassLoader();
The last ingredient that you need is the proxy object itself. This object must be an instance of a class that implements the InvocationHandler interface in the java.lang.reflect package. That interface declares the following operation:
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable;
When you wrap an object in a dynamic proxy, calls intended for the wrapped object are diverted to this invoke() operation, in a class that you supply. Your code for the invoke() method will probably need to pass each method call on to the wrapped object. You can pass on the invocation with a line such as:
result = m.invoke(obj, args);
This line uses reflection to pass along the desired call to the wrapped object. The beauty of dynamic proxies is that you can add any behavior you like before or after executing this line.
Suppose that you want to log a warning if a method takes a long time to execute. You might create an ImpatientProxy class with the following code:
package app.proxy.dynamic; import java.lang.reflect.*; public class ImpatientProxy implements InvocationHandler { private Object obj; private ImpatientProxy(Object obj) { this.obj = obj; } public Object invoke( Object proxy, Method m, Object[] args) throws Throwable { Object result; long t1 = System.currentTimeMillis(); result = m.invoke(obj, args); long t2 = System.currentTimeMillis(); if (t2 - t1 > 10) { System.out.println( "> It takes " + (t2 - t1) + " millis to invoke " + m.getName() + "() with"); for (int i = 0; i < args.length; i++) System.out.println( "> arg[" + i + "]: " + args[i]); } return result; } }
This class implements the invoke() method so that it checks the time it takes for the wrapped object to complete an invoked operation. If that execution time is too long, the ImpatientProxy class prints a warning.
To put an ImpatientProxy object to use, you need to use the Proxy class in the java.lang.reflect package. The Proxy class will need a list of interfaces and a class loader, as well as an instance of ImpatientProxy. To simplify the creation of a dynamic proxy, we might add the following method to the ImpatientProxy class:
public static Object newInstance(Object obj) { ClassLoader loader = obj.getClass().getClassLoader(); Class[] classes = obj.getClass().getInterfaces(); return Proxy.newProxyInstance( loader, classes, new ImpatientProxy(obj)); }
This static method creates the dynamic proxy for us. Given an object to wrap, the newInstance() method extracts the object’s list of interfaces and class loader. The method instantiates the ImpatientProxy class, passing it the object to wrap. All these ingredients are then passed to the Proxy class’s newProxyInstance() method.
The returned object will implement all the interfaces that the wrapped object’s class implements. We can cast the returned object to any of these interfaces.
Suppose that you are working with a set of objects, and some operations seem to run slowly for some objects. To find which objects are behaving sluggishly, you can wrap the set in an ImpatientProxy object. The following code shows this example:
package app.proxy.dynamic; import java.util.HashSet; import java.util.Set; import com.oozinoz.firework.Firecracker; import com.oozinoz.firework.Sparkler; import com.oozinoz.utility.Dollars; public class ShowDynamicProxy { public static void main(String[] args) { Set s = new HashSet(); s = (Set)ImpatientProxy.newInstance(s); s.add(new Sparkler( "Mr. Twinkle", new Dollars(0.05))); s.add(new BadApple("Lemon")); s.add(new Firecracker( "Mr. Boomy", new Dollars(0.25))); System.out.println( "The set contains " + s.size() + " things."); } }
This code creates a Set object to hold a few items. The code then wraps this set, using an ImpatientProxy object, casting the result of the newInstance() method back to a Set. The result is that the s object behaves just like a set, except that the code in ImpatientProxy will issue a complaint if any method takes too long to execute. For example, when the program calls the set’s add() method, our ImpatientProxy object intercepts the call. The ImpatientProxy object passes the call along to the real set but times the result of each call.
Running the ShowDynamicProxy program produces the following output:
> It takes 1204 millis to invoke add() with > arg[0]: Lemon The set contains 3 things.
The ImpatientProxy code helps us identify which object takes a long time to add to a set. It’s the "Lemon" instance of the BadApple class. The BadApple code is as follows:
package app.proxy.dynamic; public class BadApple { public String name; public BadApple(String name) { this.name = name; } public boolean equals(Object o) { if (!(o instanceof BadApple)) return false; BadApple f = (BadApple) o; return name.equals(f.name); } public int hashCode() { try { Thread.sleep(1200); } catch (InterruptedException ignored) { } return name.hashCode(); }
public String toString() { return name; } }
The ShowDynamicProxy code uses an ImpatientProxy object to monitor calls to a set. There is no connection, though, between sets and ImpatientProxy. Once you write a dynamic proxy class, you can use it to wrap any object, so long as the object is an instance of a class that implements an interface that declares the behavior you want to intercept.
The idea that you should be able to create behavior that runs before and after intercepted method calls is one of the ideas behind aspect-oriented programming. In AOP, an aspect is a combination of advice—code that you want to drop in—and point cuts—definitions of execution points in your code where you want the drop-in code to run. AOP is a book-length topic, but you can get a taste of applying reusable behavior to a variety of objects by experimenting with dynamic proxies.
Dynamic proxies in Java let you wrap an object with a proxy that intercepts the object’s calls and that can add behavior before or after passing the call along. This lets you create reusable behavior that you can drop in on an arbitrary object, in a fashion similar to aspect-oriented programming.