配景
在RPC接口挪用场景可能利用动态署理的场景中,偶然会呈现UndeclaredThrowableException,又可能在利用反射的场景中,呈现InvocationTargetException,这都与我们所期望的异常纷歧致,且将真实的异常信息埋没在更深一层的仓库中。本文将重点阐明下UndeclaredThrowableException
先给结论
利用jdk动态署理接口时,若要领执行进程中抛出了受检异常但要领签名又没有声明该异常时则会被署理类包装成UndeclaredThrowableException抛出。
问题还原
// 接口界说
public interface IService {
void foo() throws SQLException;
}
public class ServiceImpl implements IService{
@Override
public void foo() throws SQLException {
throw new SQLException("I test throw an checked Exception");
}
}
// 动态署理
public class IServiceProxy implements InvocationHandler {
private Object target;
IServiceProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
运行上面的MainTest,获得的异常仓库为
java.lang.reflect.UndeclaredThrowableException at com.sun.proxy.$Proxy0.foo(Unknown Source) at com.learn.reflect.MainTest.main(MainTest.java:16) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.learn.reflect.IServiceProxy.invoke(IServiceProxy.java:19) ... 2 more Caused by: java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ... 7 more
而我们期望的是
java.sql.SQLException: I test throw an checked Exception at com.learn.reflect.ServiceImpl.foo(ServiceImpl.java:11) ...
原因阐明
在上述问题还原中,真实的SQLException被包装了两层,先被InvocationTargetException包装,再被UndeclaredThrowableException包装。 个中,InvocationTargetException为受检异常,UndeclaredThrowableException为运行时异常。 为何会被包装呢,还要从动态署理的生成的署理类说起。
jdk动态署剖析在运行时生成委托接口的详细实现类,我们通过ProxyGenerator手动生成下class文件,再操作idea理会class文件获得详细署理类: 截取部门:
public final class IServiceProxy$1 extends Proxy implements IService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public IServiceProxy$1(InvocationHandler var1) throws {
super(var1);
}
public final void foo() throws SQLException {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | SQLException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.learn.reflect.IService").getMethod("foo", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
在挪用“委托类”的foo要领时,实际上挪用的署理类IServiceProxy$1的foo要领,而署理类主要逻辑是挪用InvocationHandler的invoke要领。 异常处理惩罚的逻辑是,对RuntimeException、接口已声明的异常、Error直接抛出,其他异常被包装成UndeclaredThrowableException抛出。 到这里,或者你已经get了,或者你有疑问,在接话柄现中简直是throw new SQLException,昆山软件开发,为什么还会被包装呢? 再来看IServiceProxy的invoke要领,它就是直接通过反射执行方针要领,问题就在这里了。 Method.invoke(Object obj, Object... args)要领声明中已表明到,若方针要领抛出了异常,会被包装成InvocationTargetException。(详细可查察javadoc)
所以,串起来总结就是: 详细要领实现中抛出SQLException被反射包装为会被包装成InvocationTargetException,昆山软件公司,这是个受检异常,而署理类在处理惩罚异常时发明该异常在接口中没有声明,所以包装为UndeclaredThrowableException。
办理要领