背景
為洋縣等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及洋縣網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)、洋縣網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
在RPC接口調(diào)用場(chǎng)景或者使用動(dòng)態(tài)代理的場(chǎng)景中,偶爾會(huì)出現(xiàn)UndeclaredThrowableException,又或者在使用反射的場(chǎng)景中,出現(xiàn)InvocationTargetException,這都與我們所期望的異常不一致,且將真實(shí)的異常信息隱藏在更深一層的堆棧中。本文將重點(diǎn)分析下UndeclaredThrowableException
先給結(jié)論
使用jdk動(dòng)態(tài)代理接口時(shí),若方法執(zhí)行過(guò)程中拋出了受檢異常但方法簽名又沒(méi)有聲明該異常時(shí)則會(huì)被代理類(lèi)包裝成UndeclaredThrowableException拋出。
問(wèn)題還原
// 接口定義 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"); } } // 動(dòng)態(tài)代理 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(); } } }
運(yùn)行上面的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) ...
原因分析
在上述問(wèn)題還原中,真實(shí)的SQLException被包裝了兩層,先被InvocationTargetException包裝,再被UndeclaredThrowableException包裝。 其中,InvocationTargetException為受檢異常,UndeclaredThrowableException為運(yùn)行時(shí)異常。 為何會(huì)被包裝呢,還要從動(dòng)態(tài)代理的生成的代理類(lèi)說(shuō)起。
jdk動(dòng)態(tài)代理會(huì)在運(yùn)行時(shí)生成委托接口的具體實(shí)現(xiàn)類(lèi),我們通過(guò)ProxyGenerator手動(dòng)生成下class文件,再利用idea解析class文件得到具體代理類(lèi): 截取部分:
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()); } } }
在調(diào)用“委托類(lèi)”的foo方法時(shí),實(shí)際上調(diào)用的代理類(lèi)IServiceProxy$1的foo方法,而代理類(lèi)主要邏輯是調(diào)用InvocationHandler的invoke方法。 異常處理的邏輯是,對(duì)RuntimeException、接口已聲明的異常、Error直接拋出,其他異常被包裝成UndeclaredThrowableException拋出。 到這里,或許你已經(jīng)get了,或許你有疑問(wèn),在接口實(shí)現(xiàn)中的確是throw new SQLException,為什么還會(huì)被包裝呢? 再來(lái)看IServiceProxy的invoke方法,它就是直接通過(guò)反射執(zhí)行目標(biāo)方法,問(wèn)題就在這里了。 Method.invoke(Object obj, Object... args)方法聲明中已解釋到,若目標(biāo)方法拋出了異常,會(huì)被包裝成InvocationTargetException。(具體可查看javadoc)
所以,串起來(lái)總結(jié)就是: 具體方法實(shí)現(xiàn)中拋出SQLException被反射包裝為會(huì)被包裝成InvocationTargetException,這是個(gè)受檢異常,而代理類(lèi)在處理異常時(shí)發(fā)現(xiàn)該異常在接口中沒(méi)有聲明,所以包裝為UndeclaredThrowableException。
解決方法
在實(shí)現(xiàn)InvocationHandler的invoke方法體中,對(duì)method.invoke(target, args);調(diào)用進(jìn)行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(); } }
題外話
為什么代理類(lèi)中對(duì)未聲明的受檢異常轉(zhuǎn)為UndeclaredThrowableException? 因?yàn)镴ava繼承原則:即子類(lèi)覆蓋父類(lèi)或?qū)崿F(xiàn)父接口的方法時(shí),拋出的異常必須在原方法支持的異常列表之內(nèi)。 代理類(lèi)實(shí)現(xiàn)了父接口或覆蓋父類(lèi)方法
參考
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html#icomments
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持。
新聞名稱:jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException
本文地址:http://chinadenli.net/article2/ppgeic.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)公司、動(dòng)態(tài)網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)公司、軟件開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)