12/29/2011

Java Dynamic Proxy Example

In this post, I will go over the basic constructs of dynamic proxy, followed by some notes and things to watch out when implementing it. First, the 5 elements of a dynamic proxy as implemented in this example:

TestImpl: the class behind the proxy, not to be directly invoked by the client. In some cases, this type may not exist.

TestIF: the proxy interface implemented by TestImpl, and the dynamic proxy. This is the interface type the client should reference. One or more interfaces are required to create dynamic proxy. If there is no such interface, you may need to dynamically generate them with tools like ASM.

TestInvocationHandler: the InvocationHandler that handles method invocation on the proxy from the client. It contains an instance of TestImpl to delegate method invocations to.

Test: the test main class.

$Proxy0: the dynamic proxy that implements TestIF, and the client-facing implementation of TestIF.

public interface TestIF {
String hello(String name);
}
public class TestImpl implements TestIF {
public String hello(String name) {
return String.format("Hello %s, this is %s", name, this);
}
}
import java.lang.reflect.*;
public class TestInvocationHandler implements InvocationHandler {
private Object testImpl;

public TestInvocationHandler(Object impl) {
this.testImpl = impl;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(Object.class == method.getDeclaringClass()) {
String name = method.getName();
if("equals".equals(name)) {
return proxy == args[0];
} else if("hashCode".equals(name)) {
return System.identityHashCode(proxy);
} else if("toString".equals(name)) {
return proxy.getClass().getName() + "@" +
Integer.toHexString(System.identityHashCode(proxy)) +
", with InvocationHandler " + this;
} else {
throw new IllegalStateException(String.valueOf(method));
}
}
return method.invoke(testImpl, args);
}
}
import java.lang.reflect.*;
public class Test {
public static void main(String... args) {
TestIF t = (TestIF) Proxy.newProxyInstance(TestIF.class.getClassLoader(),
new Class<?>[] {TestIF.class},
new TestInvocationHandler(new TestImpl()));
System.out.printf("t.hello(Duke): %s%n", t.hello("Duke"));
System.out.printf("t.toString(): %s%n", t);
System.out.printf("t.hashCode(): %H%n", t);
System.out.printf("t.equals(t): %B%n", t.equals(t));
System.out.printf("t.equals(new Object()): %B%n", t.equals(new Object()));
System.out.printf("t.equals(null): %B%n", t.equals(null));
}
}
Implementation notes:

When dispatching method invocations on proxy to delegate object, 3 methods from java.lang.Object need special handling: toString(), hashCode() and equals(Object). Since they are related to the proxy object identity, they should be serviced directly by the handler. One option is to base their return values on the proxy param, as in this example.

Other public methods from java.lang.Object are all final, and are not routed to the InvocationHandler by JVM. These methods are:
public final native Class<?> getClass();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException
The handler's invoke method should not invoke methods on the proxy object passed in as the first param (except the 6 final methods listed above), to avoid infinite loop and StackOverflowError. For example, this following debug line should not be used:
// AVOID THIS
System.out.printf("proxy=%s, method=%s, args=%s%n",
proxy, method, Arrays.toString(args));
If the handler already knows how to acquire the delegate TestImpl instance, through either direct instantiation or lookup, it can hide it completely from the client. So the only type of TestIF the client knows is the proxy.

The Object[] args passed into handler's invoke method is null, when the invoked method takes no param. This can come as a surprise to many. So a null check is needed before operating on args.

To run this example after compiling all java classes:
$ java Test

t.hello(Duke): Hello Duke, this is TestImpl@a3901c6
t.toString(): $Proxy0@24a37368, with InvocationHandler TestInvocationHandler@66edc3a2
t.hashCode(): 24A37368
t.equals(t): TRUE
t.equals(new Object()): FALSE
t.equals(null): FALSE
To save/keep/dump the generated proxy class file (with non-standard option):
$ java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true Test
To view the structure of the proxy class:
$ $JAVA_HOME/bin/javap -v \$Proxy0

8 comments:

error 1310 said...

Hello,

Great information in this post and I think this information will be helpful for us.

Robert Mark Bram said...

Thanks for this - excellent simple example of how to use a Java dynamic proxy.

I particularly appreciate the tips for not letting the proxy start calling methods on itself. I tried it out just to see the java.lang.StackOverflowError. As I was scrolling through all 1,025 lines of the stack trace (four lines over and over), I couldn't help whispering to myself: To Infinity.. And Beyond!

krisrr3 said...

Hi,

could use some of your experience for a little problem I am facing.

I have posted it on the Spring Forums
http://forum.springsource.org/showthread.php?129802-Getting-class-implementation-from-a-proxy

Can you please have a look and let me know what you think.

Cheers
Kris

javahowto said...

you shouldn't need to access the impl class. Everything you need from the service is expressed thru its interface/contract. Even if you do get hold of it, it may not behave the way you expected. If there is such a need, usually the service would expose a public method like unwrapSomething().

Ernesto said...

Wow!!! excellent tutorial. I an trying to use Java to see some proxys and this was the best reading I have made.

see my blog also

Srikanth said...

So, what is dynamic about this "dynamic proxy"? That TestImpl implements TestIF in known at compile time.. The invocation handler does "catch all method invocations" and "re-route to a proxy".

Is it possible for TestIF to be decided at runtime?

Sylvester Stallone said...

very nice and helpfull article.The translation service is really very helpful to me.
torrentHound UK proxy

Anna said...

Great and Useful Article.

Online Java Training

Online Java Training from India

Online Java Training

Online Java Training From India

Java Training Institutes in Chennai

Java Training in Chennai