动态代理中目标方法异常被封装为InvocationTargetException,需在InvocationHandler中解包:运行时异常和Error直接抛出;受检异常须匹配接口throws声明,否则包装为RuntimeException。

Java 动态代理中,目标方法抛出的异常默认会被封装进 InvocationTargetException,直接捕获目标异常需要在代理处理器(InvocationHandler)中对它进行“解包”。
理解异常包装机制
当通过 Method.invoke() 调用目标对象方法时,若目标方法抛出受检异常或运行时异常,反射会将其**统一包裹为 InvocationTargetException**,其 getCause() 返回原始异常。动态代理底层正是基于反射实现,因此代理方法调用失败后,invoke() 方法抛出的也是这个包装异常。
在 InvocationHandler 中解包并重抛
最常用、最直接的做法是在 invoke() 方法里捕获 InvocationTargetException,提取 cause 并重新抛出:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 检查异常类型:若
cause是运行时异常(RuntimeException或Error),可直接抛出(JVM 允许) - 若
cause是受检异常(checked exception),需确保该异常已声明在代理接口方法的throws子句中;否则只能包装为运行时异常再抛出(如RuntimeException或自定义包装类) - 不建议简单吞掉异常或只打印日志——这会丢失调用上下文和原始堆栈
示例代码片段
假设接口 Service 声明了 void doWork() throws IOException:
立即学习“Java免费学习笔记(深入)”;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
// 若是运行时异常或错误,直接抛出
if (cause instanceof RuntimeException || cause instanceof Error) {
throw cause;
}
// 若是受检异常,且方法签名允许,就抛出原异常
if (Arrays.asList(method.getExceptionTypes()).contains(cause.getClass())) {
throw cause;
}
// 否则包装为 RuntimeException
throw new RuntimeException("Unexpected checked exception", cause);
}
}
注意代理接口的 throws 声明
动态代理无法绕过 Java 的编译期异常检查规则:
- 如果目标方法抛出
SQLException,但代理接口对应方法没在throws中声明它,那么即使你在invoke()中尝试抛出,编译器也会报错(除非转为运行时异常) - 推荐做法:让代理接口的每个方法
throws子句包含所有可能被目标方法抛出的受检异常类型,或统一声明一个顶层受检异常(如自定义ServiceException)

















