最近看 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()
方法来获取这个真正的异常。