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

浙公网安备 33010602011771号