| Title: Multiple Business Interface Hazzards |
| <a name="MultipleBusinessInterfaceHazzards-UndeclaredThrowableException"></a> |
| # UndeclaredThrowableException |
| |
| When two java interfaces are implemented by a proxy and those two |
| interfaces declare the *same method* but with *different throws clauses* |
| some very nasty side effects happen, namely you loose the ability to throw |
| any checked exceptions that are not in the throws clause of both methods. |
| |
| |
| import junit.framework.TestCase; |
| |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.UndeclaredThrowableException; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class ExceptionTest extends TestCase { |
| |
| public void test() throws Exception { |
| ClassLoader classLoader = this.getClass().getClassLoader(); |
| Class[] |
| interfaces = new Class[]{One.class, Two.class}; |
| |
| InvocationHandler h = new TestInvocationHandler(); |
| |
| Object proxy = |
| java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, h); |
| |
| One one = (One) proxy; |
| |
| try { |
| one.run(new CommonException()); |
| } catch (CommonException e) { |
| // this will work |
| } catch(UndeclaredThrowableException u) { |
| Throwable t = u.getCause(); |
| fail("Undeclared: "+t); |
| } catch(Throwable t){ |
| fail("Caught: "+t); |
| } |
| |
| try { |
| one.run(new OneException()); |
| } catch (OneException e) { |
| } catch(UndeclaredThrowableException u) { |
| Throwable t = u.getCause(); |
| fail("Undeclared: "+t); // This will always be the code that |
| executes |
| } catch(Throwable t){ |
| fail("Caught: "+t); |
| } |
| |
| Two two = (Two) proxy; |
| try { |
| two.run(new CommonException()); |
| } catch (TwoException e) { |
| } catch(UndeclaredThrowableException u) { |
| Throwable t = u.getCause(); |
| fail("Undeclared: "+t); // This will always be the code that |
| executes |
| } catch(Throwable t){ |
| fail("Caught: "+t); |
| } |
| |
| } |
| |
| public static class CommonException extends Exception { |
| public CommonException() { |
| } |
| } |
| |
| public static interface One { |
| void run(Object o) throws OneException, CommonException; |
| } |
| |
| public static class OneException extends Exception { |
| public OneException() { |
| } |
| } |
| |
| public static interface Two { |
| void run(Object o) throws TwoException, CommonException; |
| } |
| |
| public static class TwoException extends Exception { |
| public TwoException() { |
| } |
| } |
| |
| private static class TestInvocationHandler implements InvocationHandler |
| { |
| public Object invoke(Object proxy, Method method, Object[] |
| args) throws Throwable { |
| throw (Throwable)args[0] |
| ; |
| } |
| } |
| } |
| |
| |
| |
| <a name="MultipleBusinessInterfaceHazzards-IllegalArgumentException"></a> |
| # IllegalArgumentException |
| |
| This one is less of a runtime problem as doing this will cause things to |
| fail up front. When two java interfaces are implemented by a proxy and |
| those two interfaces declare the *same method* but with *different return |
| types* the VM proxy code will refuse to create a proxy at all. Take this |
| code example: |
| |
| |
| |
| import junit.framework.TestCase; |
| |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class ReturnTest extends TestCase { |
| |
| public void test() throws Exception { |
| ClassLoader classLoader = this.getClass().getClassLoader(); |
| Class[] |
| interfaces = new Class[]{ReturnTest.One.class, ReturnTest.Two.class}; |
| |
| InvocationHandler h = new ReturnTest.TestInvocationHandler(); |
| |
| Object proxy = |
| java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, h); |
| |
| One one = (One) proxy; |
| try { |
| Object object = one.run(new ThingOne()); |
| } catch (Throwable t) { |
| fail("Caught: " + t); |
| } |
| |
| Two two = (Two) proxy; |
| try { |
| Object object = two.run(new ThingTwo()); |
| } catch (Throwable t) { |
| fail("Caught: " + t); |
| } |
| |
| } |
| |
| public static interface One { |
| ThingOne run(Object o); |
| } |
| |
| public static class ThingOne { |
| } |
| |
| public static interface Two { |
| ThingTwo run(Object o); |
| } |
| |
| public static class ThingTwo { |
| } |
| |
| private static class TestInvocationHandler implements InvocationHandler |
| { |
| public Object invoke(Object proxy, Method method, Object[] |
| args) throws Throwable { |
| return args[0] |
| ; |
| } |
| } |
| } |
| |
| |
| Running this code will result in the following exception: |
| |
| |
| java.lang.IllegalArgumentException: methods with same signature |
| run(java.lang.Object) but incompatible return types: [class ReturnTest$ThingOne, class ReturnTest$ThingTwo] |
| at |
| sun.misc.ProxyGenerator.checkReturnTypes(ProxyGenerator.java:669) |
| at |
| sun.misc.ProxyGenerator.generateClassFile(ProxyGenerator.java:420) |
| at |
| sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:306) |
| at java.lang.reflect.Proxy.getProxyClass(Proxy.java:501) |
| at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:581) |
| at ReturnTest.test(ReturnTest.java:36) |
| at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
| at |
| sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) |
| at |
| sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) |
| at |
| com.intellij.rt.execution.junit2.JUnitStarter.main(JUnitStarter.java:32) |
| |
| |