背景
在RPC接口调用场景或者使用动态代理的场景中,偶尔会出现UndeclaredThrowableException,又或者在使用反射的场景中,出现InvocationTargetException,这都与我们所期望的异常不一致,且将真实的异常信息隐藏在更深一层的堆栈中。本文将重点分析下UndeclaredThrowableException
先给结论
使用jdk动态代理接口时,若方法执行过程中抛出了受检异常但方法签名又没有声明该异常时则会被代理类包装成UndeclaredThrowableException抛出。
问题还原
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// 接口定义
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);
}
}
public
class
MainTest {
public
static
void
main(String[] args) {
IService service =
new
ServiceImpl();
IService serviceProxy = (IService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new
IServiceProxy(service));
try
{
serviceProxy.foo();
}
catch
(Exception e){
e.printStackTrace();
}
}
}
|
运行上面的MainTest,得到的异常堆栈为
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
|
而我们期望的是
1
2
3
|
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文件得到具体代理类: 截取部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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。
解决方法
在实现InvocationHandler的invoke方法体中,对method.invoke(target, args);调用进行try catch,重新 throw InvocationTargetException的cause。即:
1
2
3
4
5
6
7
8
|
@Override
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
try
{
return
method.invoke(target, args);
}
catch
(InvocationTargetException e){
throw
e.getCause();
}
}
|
题外话
为什么代理类中对未声明的受检异常转为UndeclaredThrowableException? 因为Java继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。 代理类实现了父接口或覆盖父类方法
参考
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html#icomments
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
<div class="art_xg">
您可能感兴趣的文章:
- java动态代理(jdk与cglib)详细解析
- java jdk动态代理详解
- 深入理解java动态代理的两种实现方式(JDK/Cglib)
- java代理 jdk动态代理应用案列
- 浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)
- Java JDK动态代理的基本原理详细介绍
- Spring AOP注解失效的坑及JDK动态代理
- Java JDK 动态代理的使用方法示例
- Java JDK动态代理(AOP)的实现原理与使用详析
- 深度剖析java中JDK动态代理机制
原文链接:https://my.oschina.net/hebaodan/blog/1584134