background
In RPC interface calling scenarios or using dynamic proxy scenarios, an UndeclaredThrowableException occasionally occurs, or in a reflection scenario, an InvocationTargetException occurs, which are inconsistent with the exception we expect, and the real exception information is hidden in a deeper stack. This article will focus on the analysis of UndeclaredThrowableException
Give a conclusion first
When using the jdk dynamic proxy interface, if a detected exception is thrown during the execution of the method but the method signature does not declare the exception, it will be wrapped in the proxy class as an UndeclaredThrowableException.
Restore the problem
// Interface definition public interface IService { void foo() throws SQLException;}public class ServiceImpl implements IService{ @Override public void foo() throws SQLException { throw new SQLException("I test throw an checked Exception"); }}// Dynamic proxy public class IServiceProxy implements InvocationHandler { private Object target; IServiceProxy(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); }}public class MainTest { public static void main(String[] args) { IService service = new ServiceImpl(); IService serviceProxy = (IService) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), new IServiceProxy(service)); try { serviceProxy.foo(); } catch (Exception e){ e.printStackTrace(); } }}Run the MainTest above and the exception stack is
java.lang.reflect.UndeclaredThrowableException at com.sun.proxy.$Proxy0.foo(Unknown Source) at com.learn.reflect.MainTest.main(MainTest.java:16)Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.learn.reflect.IServiceProxy.invoke(IServiceProxy.java:19) ... 2 moreCaused by: java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ... 7 more
What we expect is
java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ...
Cause analysis
In the above problem restoration, the real SQLException is wrapped in two layers, first wrapped by InvocationTargetException, and then wrapped by UndeclaredThrowableException. Among them, InvocationTargetException is the detected exception, and UndeclaredThrowableException is the runtime exception. Why is it wrapped? It also starts with the proxy class generated by dynamic proxy.
The jdk dynamic proxy will generate the specific implementation class of the delegate interface at runtime. We manually generate the class file through ProxyGenerator, and then use idea to parse the class file to obtain the specific proxy class: Intercept the part:
public final class IServiceProxy$1 extends Proxy implements IService { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public IServiceProxy$1(InvocationHandler var1) throws { super(var1); } public final void foo() throws SQLException { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | SQLException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.learn.reflect.IService").getMethod("foo", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}When calling the foo method of the "delegate class", the foo method of the proxy class IServiceProxy$1 is actually called, and the main logic of the proxy class is to call the invoke method of the InvocationHandler. The logic of exception handling is to directly throw RuntimeException, the exception declared by the interface, and the error are thrown, and other exceptions are wrapped as UndeclaredThrowableException. At this point, maybe you have already got it, maybe you have a question, in the interface implementation, it is indeed throw new SQLException, why is it still wrapped? Let’s look at the invoke method of IServiceProxy. It directly executes the target method through reflection. This is the problem. Method.invoke(Object obj, Object... args) method declaration has been explained that if the target method throws an exception, it will be wrapped as an InvocationTargetException. (For details, please check javadoc)
Therefore, the summary is: In the specific method implementation, the SQLException is thrown and is reflected and wrapped as an InvocationTargetException. This is a checked exception. When the proxy class handles the exception, it finds that the exception is not declared in the interface, so it is packaged as an UndeclaredThrowableException.
Solution
In the invoke method body that implements InvocationHandler, try catch the method.invoke(target, args); call, and throw the cause of InvocationTargetException. Right now:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return method.invoke(target, args); } catch (InvocationTargetException e){ throw e.getCause(); } }Off topic
Why does an undeclared checked exception in the proxy class turn into an UndeclaredThrowableException? Because of the Java inheritance principle: when a subclass overrides the parent class or a method that implements the parent interface, the thrown exception must be within the exception list supported by the original method. The proxy class implements the parent interface or overwrites the parent class method
refer to
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html#icomments
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.