| = Multiple Business Interface Hazzards |
| :index-group: Unrevised |
| :jbake-date: 2018-12-05 |
| :jbake-type: page |
| :jbake-status: published |
| |
| = 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. |
| |
| [source,java] |
| ---- |
| 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}; |
| |
| [source,java] |
| ---- |
| InvocationHandler h = new TestInvocationHandler(); |
| |
| Object proxy = |
| ---- |
| |
| java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, h); |
| |
| [source,java] |
| ---- |
| 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); } |
| |
| [source,java] |
| ---- |
| 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); } |
| |
| [source,java] |
| ---- |
| } |
| |
| 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] ; } } } |
| |
| = 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: |
| |
| [source,java] |
| ---- |
| 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}; |
| |
| [source,java] |
| ---- |
| InvocationHandler h = new ReturnTest.TestInvocationHandler(); |
| |
| Object proxy = |
| ---- |
| |
| java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, h); |
| |
| [source,java] |
| ---- |
| 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: |
| |
| [source,properties] |
| ---- |
| java.lang.IllegalArgumentException: methods with same signature |
| ---- |
| |
| run(java.lang.Object) but incompatible return types: [class |
| ReturnTestlatexmath:[$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) |