共计 4381 个字符,预计需要花费 11 分钟才能阅读完成。
Dubbo 异常处理
处理 Dubbo 调用异常有两种方式:
封装 Pojo 出参,使用状态码
利用对象来返回错误吗的方式有些违背 Dubbo 的初衷,可以包含的信息过少,不方便定位问题
定义业务异常类
推荐方式,异常类可以包含更多信息,语义友好。
Dubbo 统一异常管理
Dubbo 提供了,基于 SPI 又提供了 filter 功能,此处不做过多解释,有兴趣的可以直接看 Dubbo 官方的 SPI 机制介绍 http://dubbo.apache.org/zh-cn…。我们可以通过 Dubbo 的 Filter 机制,来实现异常统一处理。
实现方式
从 ExtensionLoader 类中可以发现,spi 加载的目录可以看到,加载了三个目录
private static final String SERVICES_DIRECTORY = “META-INF/services/”;
private static final String DUBBO_DIRECTORY = “META-INF/dubbo/”;
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + “internal/”;
其中 DUBBO_INTERNAL_DIRECTORY 目录下在源码中有一份 com.alibaba.dubbo.rpc.Filter 文件,定义了 Dubbo 内部的默认过滤器,可以采用最简单的方式来处理异常:覆盖默认 filter
只需要在项目里定义一个 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter,然后覆盖源码中的定义即可
com.alibaba.dubbo.rpc.Filter
cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
// 此处修改为你自定义的 ExceptionFilter 即可
exception=com.xx.xx.dubbo.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
ExceptionFilter
@Slf4j
@Activate(group = Constants.PROVIDER,order = 10000)
public class ExceptionFilter implements Filter {
@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.
log.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(exception instanceof AppException){
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) {
log.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) {
log.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;
}
}
}
为什么要以覆盖的方式来定义 filter?
因为 dubbo 默认的 exceptinFilter 为了避免类型兼容问题,会将所有自定义异常转换为 string,用 RuntimeException 包装后返回给 consumer 方,导致调用者拿不到真实的异常类,所以此处直接覆盖是最为轻松简单的办法。