Java 1.3 introduced java.lang.reflect.Proxy, which allows code to dynamically implement interfaces. This is done by asking Java to create a proxy around an InvocationHandler that you define. Whenever a method on the proxy is invoked, the proxy delegates the invocation to the invocation handler. For example, the call someMethod("arg1", "arg2") is invoked on proxy3 would result in the associated invocation handler being called with invoke(proxy3, "someMethod", new Object[] { "arg1", "arg2" }). I always think of dynamic proxies as the complement to reflection. Reflection allows a single method call (Method.invoke(...)) to be directed to any number of methods. With dynamic proxies, various different methods’ calls converge to the same handler method (InvocationHandler.invoke(...)).
The easiest practical demonstration of a dynamic proxy focuses on hiding implementations from clients. When I say ‘hiding’, I mean preventing clients from downcasting and even preventing them from getting anything useful out of instance.getClass(). Here’s how one would start such a proxy:
public T rudimentaryHideImplementation(T implementation) {
if (null == implementation) {
return null;
}
final ClassLoader loader = implementation.getClass().getClassLoader();
final Class[] interfaces = getImplementedInterfaces(implementation);
final InvocationHandler handler = new RudimentaryHandler(implementation);
return (T) Proxy.newProxyInstance(loader, interfaces, handler);
}
private static Class[] getImplementedInterfaces(Object implementation) {
return implementation.getClass().getInterfaces();
}
private static class RudimentaryHandler implements InvocationHandler {
private final T _implementation;
RudimentaryHandler(final T implementation) {
_implementation = implementation;
}
public Object invoke(Object proxy,
Method method,
Object[] objects)
throws Throwable {
try {
return method.invoke(_implementation, objects);
}
catch (InvocationTargetException itx) {
throw itx.getTargetException();
}
}
}
In the above code, getImplementedInterfaces(...) was oversimplified. Specifically, interfaces[] will only contain the interfaces implemented by the instance’s class; it will not contain interfaces imeplemented by its superclasses. Here’s a more correct version:
private Class[] getImplementedInterfaces(Object implementation) {
LinkedHashSet interfaces = new LinkedHashSet();
for (Class c = implementation.getClass(); c != null; c = c.getSuperclass()) {
interfaces.addAll(Arrays.asList(c.getInterfaces()));
}
return interfaces.toArray(new Class[interfaces.size()]);
}
Any reflection gurus will tell you that I’ve also oversimplified the code inside the invocation handler. True. I will cover those details in Part 2.