• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

jdk中动态代理异常处理分析:UndeclaredThrowableException

java 搞代码 4年前 (2022-01-05) 24次浏览 已收录 0个评论

最近在工作中遇到了报UndeclaredThrowableException的错误,通过查找相关的资料,终于解决了,所以这篇文章主要给大家介绍了关于jdk中动态代理异常处理分析:UndeclaredThrowableException的相关资料,需要的朋友可以参考下

背景

在RPC接口调用场景或者使用动态代理的场景中,偶尔会出现UndeclaredThrowableException,又或者在使用反射的场景中,出现InvocationTargetException,这都与我们所期望的异常不一致,且将真实的异常信息隐藏在更深一层的堆栈中。本文将重点分析下UndeclaredThrowableException

先给结论

使用jdk动态代理接口时,若方法执行过程中抛出了受检异常但方法签名又没有声明该异常时则会被代理类包装成UndeclaredThrowableException抛出。

问题还原

 // 接口定义 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"); } } // 动态代理 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) <i style="color:transparent">来源gaodai$ma#com搞$代*码*网</i>Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), new IServiceProxy(service)); try { serviceProxy.foo(); } catch (Exception e){ e.printStackTrace(); } } }

运行上面的MainTest,得到的异常堆栈为

 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 more Caused by: java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ... 7 more

而我们期望的是

 java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ...

原因分析

在上述问题还原中,真实的SQLException被包装了两层,先被InvocationTargetException包装,再被UndeclaredThrowableException包装。 其中,InvocationTargetException为受检异常,UndeclaredThrowableException为运行时异常。 为何会被包装呢,还要从动态代理的生成的代理类说起。

jdk动态代理会在运行时生成委托接口的具体实现类,我们通过ProxyGenerator手动生成下class文件,再利用idea解析class文件得到具体代理类: 截取部分:

 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()); } } }

在调用“委托类”的foo方法时,实际上调用的代理类IServiceProxy$1的foo方法,而代理类主要逻辑是调用InvocationHandler的invoke方法。 异常处理的逻辑是,对RuntimeException、接口已声明的异常、Error直接抛出,其他异常被包装成UndeclaredThrowableException抛出。 到这里,或许你已经get了,或许你有疑问,在接口实现中的确是throw new SQLException,为什么还会被包装呢? 再来看IServiceProxy的invoke方法,它就是直接通过反射执行目标方法,问题就在这里了。 Method.invoke(Object obj, Object… args)方法声明中已解释到,若目标方法抛出了异常,会被包装成InvocationTargetException。(具体可查看javadoc)

所以,串起来总结就是: 具体方法实现中抛出SQLException被反射包装为会被包装成InvocationTargetException,这是个受检异常,而代理类在处理异常时发现该异常在接口中没有声明,所以包装为UndeclaredThrowableException。

解决方法

在实现InvocationHandler的invoke方法体中,对method.invoke(target, args);调用进行try catch,重新 throw InvocationTargetException的cause。即:

 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return method.invoke(target, args); } catch (InvocationTargetException e){ throw e.getCause(); } }

题外话

为什么代理类中对未声明的受检异常转为UndeclaredThrowableException? 因为Java继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。 代理类实现了父接口或覆盖父类方法

参考

https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html#icomments

总结

以上就是jdk中动态代理异常处理分析:UndeclaredThrowableException的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:jdk中动态代理异常处理分析:UndeclaredThrowableException

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址