Dubbo的服务异常处理
Table of Contents generated with DocToc (opens new window)
# Dubbo异常处理
# 起因
按我们非rpt式的写法,在provider的serviceImpl中,抛出一个业务异常BusinessException(继承RuntimeException),但是我们会发现,在consumer端接收到的异常信息只是RuntimeException,只是message是我们自己的信息,其他的都只是一个普通的RuntimeException,这里就开始疑惑,为什么Dubbo服务端不能识别我们自定义的异常类型呢?
# 源码 org.apache.dubbo.rpc.filter.ExceptionFilter
@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
//响应入口开始
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
//上面的语句的意思是 如果有异常 且没有实现GenericService这个接口则进入下一步 否则不执行任何操作
try {
Throwable exception = appResponse.getException();
// directly throw if it's checked exception
// 如果不是 运行时异常 或者 受检查异常 直接返回
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// directly throw if the exception appears in the signature
// 如果调用的这个方法上面 有throws xxException 签名 直接抛出这个签名的异常
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// directly throw if exception class and interface class are in the same jar file.
// 这里是如果异常类和接口类在同一个jar包中,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return;
}
// directly throw if it's JDK exception
// 如果这个异常是jdk的异常 直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// directly throw if it's dubbo exception
// 如果是Dubbo自定义的这个RpcException 直接抛出
if (exception instanceof RpcException) {
return;
}
// otherwise, wrap with RuntimeException and throw back to the client
// 都不是以上几种情况的时候 只把message保留 然后封装为一个RuntimeException抛出
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
// For test purpose
public void setLogger(Logger logger) {
this.logger = logger;
}
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 能正常抛出的情况总结
首先,服务类不能实现
GenericService
接口,否则不会处理异常抛出的异常得是
RuntimeException
或受检异常 及其子类- 自定义异常通常是RuntimeException
Impl方法上有
throws xxException
签名,可以直接抛出- 作为生产服务端,不应显式抛出异常给客户的进行处理,所以也不适用
调用的接口类和抛出的异常类在同一个jar包中,可以直接抛出
- 较大的项目一般都会有一些common包,定义好异常类型,使用二方包的方式引用,所以也不适用
全类名以
java.
或javax.
开头,可以直接抛出- 这个方案不现实,也不符合规范,所以不采用
抛出的异常是
RpcException
及其子类,可以直接抛出
其他的情况,就只用发现的异常的message封装一个RuntimeException返回
# 处理方案
最好的方案是重写一下这个Filter,加上处理业务异常的逻辑
将
ExceptionFilter
copy 到自己的项目改名为DubboExceptionFilter
或者新建一个Filter 实现org.apache.dubbo.rpc.Filter
接口@Activate(group = Constants.PROVIDER) public class DubboExceptionFilter implements Filter { private final Logger logger; public DubboExceptionFilter() { this(LoggerFactory.getLogger(DubboExceptionFilter.class)); } public DubboExceptionFilter(Logger logger) { this.logger = logger; } @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // directly throw if it's checked exception if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // for the exception not found in method's signature, print ERROR message in server's log. logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // directly throw if exception class and interface class are in the same jar file. String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // directly throw if it's JDK exception String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 在此处指定我们要处理的异常类的包名前缀 if (className.startsWith("com.zdk.xx.exception")) { return result; } // directly throw if it's dubbo exception if (exception instanceof RpcException) { return result; } // otherwise, wrap with RuntimeException and throw back to the client return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); throw e; } } }
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81指定我们要处理的异常类的包名前缀,避免被Dubbo封装为RpcException
// 在此处指定我们要处理的异常类的包名前缀 if (className.startsWith("com.zdk.xx.exception")) { return result; }
1
2
3
4在resources目录下添加纯文本文件
META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
并添加内容dubboExceptionFilter=com.dliberty.core.exception.DubboExceptionFilter
1这里利用的是Dubbo的自定义扩展机制
修改dubbo 的配置文件,将
DubboExceptionFilter
加载进去并且去掉自身的ExceptionFilter<dubbo:provider filter="dubboExceptionFilter,-exception" ></dubbo:provider>
1
这样就完成了对业务异常的完美处理了