Recently I was fixing a bug in some code I’m responsible for, and the bug was interesting and general enough to share the details of.
A common approach in Java is to use dynamic proxies to provide decorator-style behavior. Doing this allows you to add additional behavior “around” an object without the object itself or it’s callers being aware of the decorating. The only requirement to use this built-in dynamic proxying is that the object must be accessed through an interface (third-party bytecode generation products like cglib do not have this restriction). For an example of how this technique is used, see my entry about Java active objects.
The key part of creating a dynamic proxy is to implement the InvocationHandler interface. The dynamic proxy object (which is generated by Java library code) calls the invoke method of this interface to dispatch method invocations at runtime.
An extremely common pattern is to implement the InvocationHandler interface something like this:
class MyHandler implements InvocationHandler {
private Object delegate;
public MyHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// wrapping behavior could go here, before the real method is invoked ....
Object value = method.invoke(delegate, args);
// ... or here, after the real method returns
return value;
}
}
The idea here is that the dynamic proxy is providing some AOP-style behavior (resource management, logging, security, etc), but the real work is being done by a delegate object. The delegate object directly implements the interface that is being proxied, and all of the interface methods are forwarded to the delegate by the InvocationHandler (using Java reflection).
There is a subtle bug with the above code, and any InvocationHandler written as above should be treated as suspect.
If you read the documentation for the java.lang.reflect.Method.invoke method you’ll see that it can throw an InvocationTargetException. This occurs when the method being reflectively invoked throws any exception.
Part of the reason the InvocationTargetException class exists is because of Java’s checked exceptions. Since reflectively calling a method could result in a checked exception being thrown but not handled (since the signature of Method.invoke does not declare it), all exceptions thrown by the target are wrapped in a checked InvocationTargetException, which is declared in the signature of Method.invoke.
Normally when calling Method.invoke() the InvocationTargetException must be explicitly handled, since it is a checked exception. However, in the above case we’re calling it from inside an InvocationHandler’s invoke method, whose signature declares that it throws Throwable. Because of this, it is very easy to write an InvocationHandler that throws an InvocationTargetException out of its invoke method (which the above code will do).
Now if you read the documentation for the InvocationHandler.invoke method, you’ll see that it describes how Java dynamic proxies respond to any Throwable thrown out of the InvocationHandler. In particular, if the exception is either a checked exception that is declared by the proxied interface, or is an unchecked exception, it will be propagated directly to the caller of the proxied method. However, if the exception is checked and is not declared by the proxied interface, it will first be wrapped in an UndeclaredThrowableException. This is analogous in some ways to how Method.invoke wraps all exceptions in an InvocationTargetException. Again, the reason has a lot to do with the checked exception system in Java.
Remembering that InvocationTargetException is a checked exception, what this all boils down to is that any InvocationHandler written as above does not explictly handle the InvocationTargetException from Method.invoke() and will end up propogating an UndeclaredThrowableException to client code. The client code calling the proxied method is hardly ever going to expect this exception.
Given that most of the time, the goal of dynamic proxying is to provide transparent proxying of a service, this situation is hardly going to result in transparency. When client code invokes a proxied method, and the “real” implementation throws any exception (checked or unchecked), that exception should propagated to the calling client code. Any other implementation will result in client code that needs to be aware of the proxying, which means losing one of the main advantages of using dynamic proxies in the first place.
Here’s the correct implementation of the InvocationHandler.invoke() method from the above code:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Object value = method.invoke(delegate, args);
return value;
}
catch (InvocationTargetException ex) {
throw ex.getCause();
}
}
This implementation has all the right properties: any exception (checked or unchecked) thrown by the “real” method will be directly propagated to callers of the dynamic proxy. Re-throwing the InvocationTargetHandler’s cause will not repopulate that exception’s stack trace, so the client code will be able to see the real cause of the exception including the original stack trace.
If the thrown exception will propagate all the way to the top of the call stack, the distinction between the above two methods doesn’t really matter much. That is, in the case where the exception is totally unexpected by client code and will be caught by a top-level exception handler, the second approach isn’t technically needed. However, client code often explicitly handles exceptions and performs some sort of recovery. The only way to allow client code that does that is to properly handle the InvocationTargetException as in the second code example.
Admittedly the first code example is buggy. However, a lot of this complexity could have been avoided were it not for the Java language’s checked exception design. This feature is a source of controversy among Java programmers - some love checked exceptions while others have grown to hate them. I admit that I can see both sides of the argument, but I fall firmly on the side of the fence that says checked exceptions were a nice experiment but have proven to be a failure.
Note:
One commenter noted that InvocationTargetException.getTargetException() should be used instead of getCause(). The two methods are actually equivalent for InvocationTargetException, so using either works.