参考问题Dubbo异常处理
由于dubbo会将自定义异常或者第三方异常包装直接放入RuntimeException,并且使用了
StringUtils.toString(exception))
按照dubbo的服务化最佳实践
异常
基于现状目前引用的第三方异常以及自定义异常很难一次性加入到api包中。并且unchecked异常也很难全部声明到方法签名上。
遂做了如下改造
/** * Created by qixiaobo on 2017/7/3. */ @Activate(group = Constants.PROVIDER, before = {"exception"}, value = {"customException"}) public class CustomExceptionFilter implements Filter { private static Logger logger = LoggerFactory.getLogger(CustomExceptionFilter.class); @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // 如果是checked异常,直接抛出 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; } // 是JDK自带的异常,直接抛出 String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 是Dubbo本身的异常,直接抛出 if (exception instanceof RpcException) { return result; } //其他exception ,减少问题,直接将exception序列化成RuntimeException,同时放入指定的异常类型值attachment中 // 否则,包装成RuntimeException抛给客户端 RpcResult rpcResult = new RpcResult(new RuntimeException(exception.getMessage())); rpcResult.setAttachment("customException", exception.getClass().getName());//已经包装过后续ExceptionFilter无需处理 result = rpcResult; } catch (Throwable e) { logger.error(e.getMessage(), e); return result; } } return result; } }
customException=com.air.tqb.dubbo.filter.CustomExceptionFilter
<dubbo:provider timeout="30000" group="${dubbo.group}" retries="0" owner="qixiaobo" id="f6-provider" filter="customException"/>
效果自然是有的
策略比较简单:
- 在ExceptionFilter处理之前提前将异常转化成RuntimeException,将原异常的message取出,放入RuntimeException
- 放入Attachment到RpcResult以期许在消费者获取具体的异常类型。
结果却是可以拿到较短的message,并且消除了大量的堆栈填充。但是在消费者却无法获取指定的attachment。
直接给出结论DubboCodec中:
@Override protected void encodeResponseData(Channel channel, ObjectOutput out, Object data) throws IOException { Result result = (Result) data; Throwable th = result.getException(); if (th == null) { Object ret = result.getValue(); if (ret == null) { out.writeByte(RESPONSE_NULL_VALUE); } else { out.writeByte(RESPONSE_VALUE); out.writeObject(ret); } } else { out.writeByte(RESPONSE_WITH_EXCEPTION); out.writeObject(th); } }
很明显在做encodeResponse的时候Dubbo直接取出了对应的类型
- 如果异常为空取出value
- 如果value为空则写入空返回(异步或者oneWay)
- 如果value不为空则写入value
- 否则将异常写入
很明显 期间并未操作到attachment。
那么在请求的时候我们查看一下
@Override protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException { RpcInvocation inv = (RpcInvocation) data; out.writeUTF(inv.getAttachment(Constants.DUBBO_VERSION_KEY, DUBBO_VERSION)); out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); out.writeUTF(inv.getMethodName()); out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); Object[] args = inv.getArguments(); if (args != null) for (int i = 0; i < args.length; i++){ out.writeObject(encodeInvocationArgument(channel, inv, i)); } out.writeObject(inv.getAttachments()); }
确实从invocation中取出了attachment并写入请求流中。
因此在消费者通过DubboCodec是无法将attachment传给消费者的