代理类的异常处理

Java从1.4开始提供了一个代理类,Proxy,此类在反射/服务/接口方面非常有用, 很多时候我们只是将我们的接口公开,并且只是公开接口中的某些方法,或者在某些方法上做一些拦截处理, 即简单的AOP控制,这时候Proxy类就大有用处了。

package demo;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    static interface ITest {
        void doit() throws IOException;
    }

    static class DefaultTest implements ITest {
        public void doit() throws IOException {
            throw new IOException("unsupported");
        }
    }

    static ITest getTest() {
        final ITest obj = new DefaultTest();
        return (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class[] { ITest.class }, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(obj, args);
            }
        });
    }

    public static void main(String[] args) {
        ITest test = getTest();
        try {
            test.doit();
        } catch (IOException ioe) {
            System.err.println("1>" + ioe.getMessage());
            ioe.printStackTrace();
        } catch (Exception e) {
            System.err.println("2>" + e.getMessage());
            e.printStackTrace();
        }
    }
}

一直以来以为Proxy类是这样使用的,但是也仅限于学习,事实上没有在实际的应用中使用过, 恰好昨天用在网站上了,早上一看有一些我从来没有见过的异常,如下:

2>null
java.lang.reflect.UndeclaredThrowableException
    at demo.$Proxy0.doit(Unknown Source)
    at demo.ProxyTest.main(ProxyTest.java:31)
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 demo.ProxyTest$1.invoke(ProxyTest.java:23)
    ... 2 more
Caused by: java.io.IOException: unsupported
    at demo.ProxyTest$DefaultTest.doit(ProxyTest.java:15)
    ... 7 more

显然,我们没有得到正确的异常,而很多业务中异常是一个非常重要的环节。看了下Proxy类和InvocationHandler类以及UndeclaredThrowableException,没看明白怎么处理,google下也没有找到问题的根源。然后我就想既然这个类这么常用(Proxy很常见),那么肯定有例子。本来想看看Spring的源码,发现手头没有懒得找, 直接在Eclipse中使用F4,居然还找到了一个,com.sun.corba.se.spi.orbutil.proxy.DelegateInvocationHandlerImpl类中有一段实例:

return new InvocationHandler() {
    public Object invoke( Object proxy, Method method, Object[] args )
    throws Throwable {
        // This throws an IllegalArgument exception if the delegate
        // is not assignable from method.getDeclaring class.
        try {
            return method.invoke( delegate, args ) ;
        } catch (InvocationTargetException ite) {
            // Propagate the underlying exception as the
            // result of the invocation
            throw ite.getCause() ;
        }
    }
} ;

显然这里捕捉了异常,并且抛出了原异常,这就是关键。我改造了下我的代码就顺利的得到了我的异常。

try {
    return method.invoke(obj, args);
} catch (InvocationTargetException e) {
    throw e.getCause();//关键在于要抛出真实的异常,如果多层次代理调用,则需要循环处理
}

再次运行输出如下:

1>unsupported
java.io.IOException: unsupported
    at demo.ProxyTest$DefaultTest.doit(ProxyTest.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 demo.ProxyTest$1.invoke(ProxyTest.java:25)
    at demo.$Proxy0.doit(Unknown Source)
    at demo.ProxyTest.main(ProxyTest.java:36)

此时已经得到了正确的异常。这里有一个有趣的事情,一般throw 后面都跟着一个Throwable实例,如果throw null会发生什么呢?