博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
jdk动态代理异常处理分析,UndeclaredThrowableException
阅读量:6229 次
发布时间:2019-06-21

本文共 4651 字,大约阅读时间需要 15 分钟。

hot3.png

背景

在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) 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 moreCaused 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://my.oschina.net/hebaodan/blog/1584134

你可能感兴趣的文章
RAC 环境下的重要参数
查看>>
你知道,人工智能如何增强数据中心的安全性
查看>>
苗圩:从国家战略高度加快推进智能网联汽车发展
查看>>
团队如何进行Code Review
查看>>
中国联通开展多场景蜂窝车联网业务示范
查看>>
1星|《追随》:洞察力太差,有效信息太少,咨询经验太少(举的例子以跟自己孩子的互动为主)...
查看>>
Android:MVC模式(上)
查看>>
vi编辑器的使用(2)
查看>>
bootstrap-.col-md-* 栅格类
查看>>
解释器模式-类行为型
查看>>
虚拟现实强势“入侵”游戏盛典China Joy
查看>>
bootstrap-分页导航(翻页分页导航)
查看>>
innobackupex备份mysql数据库
查看>>
HTML5新标签的兼容性处理
查看>>
iptables学习笔记
查看>>
jQuery中的$(window)与$(document)的用法区别
查看>>
java中多种写文件方式的效率对比实验
查看>>
Cisco 2960 配置
查看>>
阿里大鱼数据库存储方案
查看>>
if判断的几种用法
查看>>