动态代理中的 UndeclaredThrowableException 以及其他异常

最近看 Github 发现别人写的全局异常处理中有用到这个类的,整理学习一下

这里指的是 JDK 动态代理,就是实现 InvocationHandler 接口的那种情况,直接把代码贴过来,您可以先自己分析一下可能会出现的异常,再往下看我分析地到位与否。

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.SocketException;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @description: JDK 动态代理以及可能会出现的异常情况
 * @date: 2019/10/28 下午7:48
 * @version: V1.0
 */
@Slf4j
public class JDKDynamicProxyTest {

    interface CustomInterface {

        void say();

        /**
         * 这里的返回值,如果是包装类则不会有空指针异常
         * 如果是基本数据类型,可能会产生 NPE(InvocationHandler#invoke 方法返回 null 的情况下)
         * @param num
         * @return
         */
        Integer getPow(Integer[] num);
    }

    @Slf4j
    static class RealSubject implements CustomInterface {

        @Override
        public void say() {
            log.info("I'm real subject,这是我的 say() 方法.");
        }

        @Override
        public Integer getPow(Integer[] num) {
            log.info("I'm real subject,这是我 getPow() 方法.");
            Optional<Integer> reduce = Stream.of(num).map(i -> i * i).reduce(Integer::sum);
            log.info("reduce.get()= {}", reduce.get());
            say();
            return reduce.get();
        }
    }

    @Slf4j
    static class DynamicProxy implements InvocationHandler {

        /** 真正的对象 **/
        private Object instance;

        DynamicProxy(Object o) {
            instance = o;
        }

        /**
         * 空指针异常:如果这个方法的返回值是 null,而接口的返回类型是基本数据类型,就会产生 NPE
         * ClassCastException:
         * UndeclaredThrowableException:如果该方法抛出了可检查性异常,就会抛出 UndeclaredThrowableException 包着这个可检查性异常
         * @param proxy     最终生成的代理对象(就是 Proxy#newProxyInstance 方法生成的对象)
         * @param method    被代理对象的某个具体方法
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.info("proxy 类名为:{}", proxy.getClass().getName());
            log.info("----------->进入代理类的 invoke 方法<--------------");
            Object invoke = method.invoke(instance, args);
            log.info("----------->method.invoke()方法结束<--------------");
            //if (true) {
                //这里直接抛出检查性异常,会被包装成 UndeclaredThrowableException
              //  throw new SocketException("dsadsa");
            //}
            return null;//这里返回 null,null 在转化为 int 类型时,会报空指针异常
        }
    }

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Integer [] intList = new Integer[]{1,2,3,4,5,6,7,8};
        InvocationHandler handler = new DynamicProxy(realSubject);

        /**
         * Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.
         */
        CustomInterface proxyInstance  = (CustomInterface) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),//代理对象实现的接口列表
                handler);
        try {
            Integer pow = proxyInstance.getPow(intList);
            proxyInstance.say();
        }catch (Exception e) {
            if (e instanceof UndeclaredThrowableException) {
                log.error("未声明的可检查性异常", ((UndeclaredThrowableException) e).getUndeclaredThrowable());
            }
            else {
                log.error("some ", e);
            }
        }

        log.info("proxyInstance.getClass().getName() = {}", proxyInstance.getClass().getName());
    }

}

InvocationHandler 接口

这个接口就是 JDK 动态代理的关键,其中只包含下面一个方法:

/**
 * Processes a method invocation on a proxy instance and returns
 * the result.  This method will be invoked on an invocation handler
 * when a method is invoked on a proxy instance that it is
 * associated with.
 * 
 * @throws  Throwable the exception to throw from the method
 * invocation on the proxy instance.  The exception's type must be
 * assignable either to any of the exception types declared in the
 * {@code throws} clause of the interface method or to the
 * unchecked exception types {@code java.lang.RuntimeException}
 * or {@code java.lang.Error}.  If a checked exception is
 * thrown by this method that is not assignable to any of the
 * exception types declared in the {@code throws} clause of
 * the interface method, then an
 * {@link UndeclaredThrowableException} containing the
 * exception that was thrown by this method will be thrown by the
 * method invocation on the proxy instance.
 *
 * @see     UndeclaredThrowableException
 */ 
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

先看方法描述,大致意思就是,当在代理对象上调用某个方法时,这个 invoke 方法会被调用。三个参数分别代表,代理对象、调用的方法以及入参。

注意这个方法抛出的可是所有异常的爹 Throwable,包括 ErrorException,其实我们大部分情况下关心的还是 Exception正常运行时,可预料的意外情况),不仅包含运行时异常 RuntimeException 还包含非运行时异常 IOException。再看这个方法的异常描述,异常的类型必须是运行时异常或 Error。如果抛出的是一个可检查性异常,就会产生一个 UndeclaredThrowableException 来将这个异常包起来。写到这里,也基本没啥东西了,大家再对照看一下上面的例子 invoke 方法中被我注释掉的 if(true) 那里,就可以了。

这个 UndeclaredThrowableException extends RuntimeException 是运行时异常,说白了,他就是用来包装可检查性异常的运行时异常,有点绕口。我们知道 Spring 大量运用各种代理,因此在全局异常处理中,如果检查到抛出的异常类型是 UndeclaredThrowableException,需要我们再调用它的 getUndeclaredThrowable() 方法来获取这个真正的异常。

参考文章

UndeclaredThrowableException

原文地址:https://www.cnblogs.com/Zhoust/p/14994589.html