當前位置:
首頁 > 知識 > Jdk 動態代理異常處理分析,UndeclaredThrowableException

Jdk 動態代理異常處理分析,UndeclaredThrowableException

(點擊

上方公眾號

,可快速關注)




來源:hebaodan ,


my.oschina.net/hebaodan/blog/1584134




背景



在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);


    }


}



運行上面的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




看完本文有收穫?請轉發分享給更多人


關注「ImportNew」,提升Java技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 ImportNew 的精彩文章:

使用 partclone 備份磁碟分區
關於 ArrayList 的 5 道面試題

TAG:ImportNew |