问题描述 Dubbo在Provider端抛出异常(包括但不限于自定义异常)的时候会自动转换成RuntimeException,异常信息会被放在RuntimeException的msg中,导致我们在Consumer端异常处理的时候不能正确获取到我们想要的异常或者信息。
原因分析 Dubbo中的异常处理 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 package com.alibaba.dubbo.rpc.filter; import java.lang.reflect.Method; import com.alibaba.dubbo.common.Constants;import com.alibaba.dubbo.common.extension.Activate;import com.alibaba.dubbo.common.logger.Logger;import com.alibaba.dubbo.common.logger.LoggerFactory;import com.alibaba.dubbo.common.utils.ReflectUtils;import com.alibaba.dubbo.common.utils.StringUtils;import com.alibaba.dubbo.rpc.Filter;import com.alibaba.dubbo.rpc.Invocation;import com.alibaba.dubbo.rpc.Invoker;import com.alibaba.dubbo.rpc.Result;import com.alibaba.dubbo.rpc.RpcContext;import com.alibaba.dubbo.rpc.RpcException;import com.alibaba.dubbo.rpc.RpcResult;import com.alibaba.dubbo.rpc.service.GenericService; @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; } 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的处理方式主要是:
如果provider实现了GenericService接口,直接抛出
如果是checked异常,直接抛出
在方法签名上有声明,直接抛出
异常类和接口类在同一jar包里,直接抛出
是JDK自带的异常,直接抛出
是Dubbo本身的异常,直接抛出
否则,包装成RuntimeException抛给客户端
解决方案 第一步:修改源码 第一步:修改源码,在provider端新建filter
包,创建DubboExceptionFilter
类。我的自定义异常是 GlobalException
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 @Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER}) public class DubboExceptionFilter implements Filter , Filter.Listener { private ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(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()) { try { Throwable exception = appResponse.getException(); if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return ; } try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClasses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClasses) { if (exception.getClass().equals(exceptionClass)) { return ; } } } catch (NoSuchMethodException e) { return ; } logger.error(CONFIG_FILTER_VALIDATION_EXCEPTION, "" , "" , "Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().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 ; } String className = exception.getClass().getName(); if (className.startsWith("java." ) || className.startsWith("javax." )) { return ; } if (exception instanceof RpcException) { return ; } if (exception instanceof GlobalException) { return ; } if (exception instanceof DuplicateKeyException) { return ; } appResponse.setException(new RuntimeException (StringUtils.toString(exception))); } catch (Throwable e) { logger.warn(CONFIG_FILTER_VALIDATION_EXCEPTION, "" , "" , "Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().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(CONFIG_FILTER_VALIDATION_EXCEPTION, "" , "" , "Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); } public void setLogger (ErrorTypeAwareLogger logger) { this .logger = logger; } }
第二步:新建文件 在provider的resources文件夹新建 /META_INF/dubbo/org.apache.dubbo.rpc.Filter
文件,内容为:
1 dubboExceptionFilter = com.***.provider.filter.DubboExceptionFilter
第三步:过滤器配置 在provider的 applicatio.yml
文件中配置如下内容
1 2 3 dubbo: provider: filter: -exception, dubboExceptionFilter
其中 -exception
代表过滤Dubbo原本的exception方法,否则Dubbo自身默认的 ExceptionFilter 任然在工作(一个异常抛出会依次进入两个过滤器;dubboExceptionFilter
代表添加自己定义的Filter。
第四步:异常处理Handler 在Consumer中编写ExceptionHandler
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 package com.***.consumer.exception;import com.fasterxml.jackson.databind.exc.InvalidFormatException;import com.ghrk.common.exception.GlobalException;import com.ghrk.common.utils.R;import org.apache.dubbo.common.utils.CollectionUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DuplicateKeyException;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.NoHandlerFoundException;import javax.validation.ConstraintViolation;import javax.validation.ConstraintViolationException;import java.util.Set;@RestControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @ExceptionHandler(GlobalException.class) public R handleGlobalException (GlobalException e) { R r = new R (); r.put("code" , e.getCode()); r.put("msg" , e.getMessage()); logger.error(e.getMessage(), e); return r; } @ExceptionHandler(DuplicateKeyException.class) public R handleDuplicateKeyException (DuplicateKeyException e) { logger.error(e.getMessage(), e); return R.error("已存在该记录" ); } @ExceptionHandler(RuntimeException.class) public R handleException (RuntimeException e) { logger.error(e.getMessage(), e); return R.error("运行异常,请重试!" ); } @ExceptionHandler(Exception.class) public R handleException (Exception e) { logger.error(e.getMessage(), e); return R.error(); } }