Dubbo过滤器 - ExceptionFilter
概述 ExceptionFilter 是服务端的过滤器,它不是一个统一处理异常的过滤器,关注点不在于捕获异常,而是为了找到那些返回的自定义异常,会把异常包装成 RuntimeException 。 因为异常类可能不存在于消费端,避免消费端出现不能反序列化异常的情况。
ExceptionFilter 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 82 83 84 85 86 87 @Activate (group = Constants.PROVIDER)public class ExceptionFilter implements Filter { private final Logger logger; public ExceptionFilter () { this (LoggerFactory.getLogger(ExceptionFilter.class )) ; } public ExceptionFilter (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(); if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } 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; } 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); String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } String className = exception.getClass().getName(); if (className.startsWith("java." ) || className.startsWith("javax." )) { return result; } if (exception instanceof RpcException) { return result; } 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; } } }
Dubbo 内部的异常处理类 ExceptionFilter 的处理逻辑如上面代码所示,其中 1-6 种情况客户端都能反序列化成功,第 7 步是一个兜底操作,为了防止客户端反序列化异常失败。
异常处理 Dubbo 官方推荐异常处理方式如下:
1 2 3 4 5 6 7 * 建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。 * 如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。 * 查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。 * 服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。
自定义异常 通过 ExceptionFilter 源码可知,自定义的异常如果不符合前面的 6 种情况,那么最后返回给客户端的异常会被包装成 RuntimeException ,自定义的异常也就无效了。既然如此,能不能剔除掉 ExceptionFilter 过滤器,这样就不会将自定义的异常自动包装了。但是这样的话又回到了最开始的痛点,没有 ExceptionFilter 过滤器处理客户端可能无法正确反序列化自定义的异常。因此,我们就可以自定义一个 Filter 来统一处理这种情况。
BizException 一般自定义异常都是unchecked 。
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public class BizException extends RuntimeException { private static final long serialVersionUID = 8056580241015398148L ; private String code; public BizException () { super (); } public BizException (final String code, final String msg, final Throwable cause) { super (msg, cause); this .code = code; } public BizException (final String code, final String msg) { super (msg); this .code = code; } public BizException (final String msg, final Throwable cause) { super (msg, cause); } public BizException (final String msg) { super (msg); } public BizException (final ResultCode resultCode) { super (resultCode.getMsg()); this .code = resultCode.getCode(); } public BizException (final ResultCode resultCode, final Throwable cause) { super (resultCode.getMsg(), cause); this .code = resultCode.getCode(); } public String getCode () { return code; } public void setCode (String code) { this .code = code; } public BizException (final Throwable cause) { super (cause); } @Override public synchronized Throwable fillInStackTrace () { return this ; } }
自定义 Filter 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 @Activate (group = {CommonConstants.PROVIDER}, order = 999999 )public class ProviderExceptionFilter implements Filter { private Environment environment; public Environment getEnvironment () { return environment; } public void setEnvironment (Environment environment) { this .environment = environment; } @Override public Result invoke (Invoker<?> invoker, Invocation invocation) throws RpcException { JsonKvFormat kvFormat = makeJsonKvFormat(invoker, invocation); Result result = null ; try { long startTime = System.currentTimeMillis(); result = invoker.invoke(invocation); long endTime = System.currentTimeMillis(); long timeOut = Long.parseLong(environment.getProperty("time.out" )); if ((endTime - startTime) > timeOut) { recordTimeOutLog(kvFormat, endTime - startTime); } if (result.hasException() && GenericService.class ! = invoker.getInterface()) { Throwable exception = result.getException(); if (exception instanceof BizException) { String code = ((BizException) exception).getCode(); recordBizLog(kvFormat, result); result.setValue(com.yunhu.rpc.result.Result.failResult(code, exception.getMessage())); } else if (exception instanceof NoRecordException) { String code = ((NoRecordException) exception).getCode(); result.setValue(com.yunhu.rpc.result.Result.failResult(code, exception.getMessage())); } else { recordErrorLog(kvFormat, exception); result.setValue(com.yunhu.rpc.result.Result.failResult(BizExceptionEnum.SYSTEM_ERROR.getCode(), BizExceptionEnum.SYSTEM_ERROR.getMessage())); } result.setException(null ); return result; } return result; } catch (Exception e) { recordErrorLog(kvFormat, e); result.setException(null ); result.setValue(com.yunhu.rpc.result.Result.failResult(BizExceptionEnum.SYSTEM_ERROR.getCode(), BizExceptionEnum.SYSTEM_ERROR.getMessage())); } return result; }
说明:
这里自定义 Filter 的前提是返回结果是以状态码表示的,异常的处理最终也是通过状态码返回。
自定义的 Filter 的优先级相比较 Dubbo 内置的 ExceptionFilter 的优先级要高。即使调用出现异常,自定义 Filter 会清除异常信息,到 ExceptionFilter 时就不会走异常处理流程了。
小结 本篇文章对 Dubbo 框架的异常处理过滤器进行了介绍,并实现了自定义的异常处理。