Dubbo异常处理
处理Dubbo调用异常有两种方式:
封装Pojo出参,使用状态码
利用对象来返回错误吗的方式有些违背Dubbo的初衷,可以包含的信息过少,不方便定位问题
定义业务异常类
推荐方式,异常类可以包含更多信息,语义友好。
Dubbo统一异常管理
Dubbo提供了,基于SPI又提供了filter功能,此处不做过多解释,有兴趣的可以直接看Dubbo官方的SPI机制介绍。
我们可以通过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.CacheFiltervalidation=com.alibaba.dubbo.validation.filter.ValidationFilterecho=com.alibaba.dubbo.rpc.filter.EchoFiltergeneric=com.alibaba.dubbo.rpc.filter.GenericFiltergenericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFiltertoken=com.alibaba.dubbo.rpc.filter.TokenFilteraccesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilteractivelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilterclassloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFiltercontext=com.alibaba.dubbo.rpc.filter.ContextFilterconsumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter//此处修改为你自定义的ExceptionFilter即可exception=com.xx.xx.dubbo.ExceptionFilterexecutelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilterdeprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFiltercompatible=com.alibaba.dubbo.rpc.filter.CompatibleFiltertimeout=com.alibaba.dubbo.rpc.filter.TimeoutFiltertrace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilterfuture=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFiltermonitor=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方,导致调用者拿不到真实的异常类,所以此处直接覆盖是最为轻松简单的办法。