最近看 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()); } }这个接口就是 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,包括 Error、Exception,其实我们大部分情况下关心的还是 Exception(正常运行时,可预料的意外情况),不仅包含运行时异常 RuntimeException 还包含非运行时异常 IOException。再看这个方法的异常描述,异常的类型必须是运行时异常或 Error。如果抛出的是一个可检查性异常,就会产生一个 UndeclaredThrowableException 来将这个异常包起来。写到这里,也基本没啥东西了,大家再对照看一下上面的例子 invoke 方法中被我注释掉的 if(true) 那里,就可以了。
这个 UndeclaredThrowableException extends RuntimeException 是运行时异常,说白了,他就是用来包装可检查性异常的运行时异常,有点绕口。我们知道 Spring 大量运用各种代理,因此在全局异常处理中,如果检查到抛出的异常类型是 UndeclaredThrowableException,需要我们再调用它的 getUndeclaredThrowable() 方法来获取这个真正的异常。
UndeclaredThrowableException