时间:2021-05-02
背景
在rpc接口调用场景或者使用动态代理的场景中,偶尔会出现undeclaredthrowableexception,又或者在使用反射的场景中,出现invocationtargetexception,这都与我们所期望的异常不一致,且将真实的异常信息隐藏在更深一层的堆栈中。本文将重点分析下undeclaredthrowableexception
先给结论
使用jdk动态代理接口时,若方法执行过程中抛出了受检异常但方法签名又没有声明该异常时则会被代理类包装成undeclaredthrowableexception抛出。
问题还原
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 接口定义 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) proxy.newproxyinstance(service.getclass().getclassloader(), service.getclass().getinterfaces(), new iserviceproxy(service)); try { serviceproxy.foo(); } catch (exception e){ e.printstacktrace(); } } }运行上面的maintest,得到的异常堆栈为
? 1 2 3 4 5 6 7 8 9 10 11 12 13 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而我们期望的是
? 1 2 3 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文件得到具体代理类: 截取部分:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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。即:
? 1 2 3 4 5 6 7 8 @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
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://my.oschina.net/hebaodan/blog/1584134
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
详解javaJDK动态代理类分析(java.lang.reflect.Proxy)/***JDK动态代理类分析(java.lang.reflect.Proxy使
java代理有jdk动态代理、cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)原理
今天来介绍另一种更为强大的代理——Cglib动态代理。 什么是Cglib动态代理? 我们先回顾一下上一篇的jdk动态代理,jdk动态代理是通过接口来在运
JDK动态代理实现原理动态代理机制通过实现InvocationHandler接口创建自己的调用处理器通过为Proxy类指定ClassLoader对象和一组int
1.概述JDK动态代理是利用java反射机制生成一个实现接口的匿名类,在调用具体方法前调用InvocationHandler来处理Cglib动态代理是利用asm