UndeclaredThrowableException 详解

前言:在用Jersey做rest服务时,因为其使用统一异常接口,无法在controller中识别自定义异常。会报UndeclaredThrowableException。原文


在Java中一个使用动态代理的通用方式是提供装饰器方法。这将会使你增加额外的操作在对象的外面。

看一个常见的动态代理实现。

public interface InterfaceA {
    void display() throws SQLException;
}

public class ClassA implements InterfaceA {

    @Override
    public void display() throws SQLException {
        throw new SQLException();
    }
}

public class ProxyHandler implements InvocationHandler {

    private InterfaceA delegate;

    public ProxyHandler(InterfaceA delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Inside the invocation handler");
        return method.invoke(delegate, args);
    }
}

public class ProxyApp {
    public static void main(String[] args) {
        createAndTestProxy();
    }

    private static void createAndTestProxy() {
        InterfaceA interfaceA = (InterfaceA) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                                    new Class[]{InterfaceA.class}, new ProxyHandler(new ClassA()));
        try {
            interfaceA.display();
        } catch (java.sql.SQLException e) {
            throw new RuntimeException("Something bad happened", e);
        }
    }
}

如果你运行ProxyApp类去测试这个实现类,你将会得到下面的异常追踪信息

Inside the invocation handler
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.display(Unknown Source)
    at proxy.ProxyApp.createAndTestProxy(ProxyApp.java:23)
    at proxy.ProxyApp.main(ProxyApp.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:110)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at proxy.ProxyHandler.invoke(ProxyHandler.java:26)
    ... 8 more
Caused by: java.sql.SQLException
    at proxy.ClassA.display(ClassA.java:18)
    ... 13 more

而我们期望的异常栈信息应该是这样的。

Inside the invocation handler
Exception in thread "main" java.lang.RuntimeException: Something bad happened
    at proxy.ProxyApp.createAndTestProxy(ProxyApp.java:25)
    at proxy.ProxyApp.main(ProxyApp.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:110)
Caused by: java.sql.SQLException
    at proxy.ClassA.display(ClassA.java:18)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at proxy.ProxyHandler.invoke(ProxyHandler.java:28)
    at $Proxy0.display(Unknown Source)
    at proxy.ProxyApp.createAndTestProxy(ProxyApp.java:23)
    ... 6 more

上面这个例子的根本原因是InvocationHandler实现的一个小bug。这个调用处理程序内部会调用java.lang.reflect.Method.invoke方法并且
会抛出InvacationTargetException。这将会在方法被反射调用抛出任何异常时产生。
因为反射调用方法会导致一个检查异常被抛出却未被处理(因为这个方法中没有声明),目标类抛出的所有异常都会被包装成一个检查异常InvocationTargetException,这个异常在方法上要求声明。
通常来说,当方法被调用时,InvocationTargetException一定要捕获(检查型异常),然而上面的例子中我们抛出Throwable异常,这样非常容易将InvocationTargetException 抛出方法。
现在,如果你读了InvocationHandler.invoke方法的文档,其中说明了Java动态代理如何响应InvocationHandler抛出的Throwable。总的来说,如果一个异常时检查型异常并且没有在动态代理的借口处声明,那么它将会被包装成UndeclaredThrowableException.
记住InvocationTargetException是检查型异常,因此Methon.invoke中所有的异常都会被客户端代码捕获成UndeclaredThrowableException,客户端代码调用
代理方法得不到期望的异常。
动态代理的目标就是提供一个透明代理服务,而这种情况很难产生透明。当客户端调用代理方法,真实的实现抛出任何异常都传播到调用处,这就意味着丢失了动态代理最重要的优势。
下面是正确的例子。

public class ProxyHandler implements InvocationHandler {

    private InterfaceA delegate;

    public ProxyHandler(InterfaceA delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Inside the invocation handler");
        try {
            return method.invoke(delegate, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }
}

UndeclaredThrowableException这个错误异常被抛出的原因是Java系统做了很多检查型异常工作。这些复杂性的东西可以被避免,如果没有Java的检查型异常设计

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值