docker · 2024-09-04 0

dubbo 异常处理 ExceptionFilter

1.概述

项目使用了 Dubbo 进行不同系统服务间的调用,当服务提供端发生异常时,我们希望把异常传递给消费端,由消费端对异常进行 try-catch 捕获并处理。但在实际使用中,发现以往的异常处理在 dubbo 服务中并不能奏效。例如,自定义异常类 MyException 继承 RuntimeException,当服务端抛出这个异常时,消费端并不能捕获它。

版本:dubbo-2.7.23

2.ExceptionFilter

// ExceptionFilter.java
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
    if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
        try {
            Throwable exception = appResponse.getException();

            // directly throw if it's checked exception
            if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                return;
            }
            // directly throw if the exception appears in the signature
            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;
            }

            // for the exception not found in method's signature, print ERROR message in server's log.
            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);

            // 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;
            }
            // directly throw if it's JDK exception
            String className = exception.getClass().getName();
            if (className.startsWith("java.") || className.startsWith("javax.")) {
                return;
            }
            // directly throw if it's dubbo exception
            if (exception instanceof RpcException) {
                return;
            }

            // otherwise, wrap with RuntimeException and throw back to the client
            appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
        } catch (Throwable e) {
            ...
        }
    }
}

源码分析结论:

  1. 如果是 checked 异常,直接返回
  2. 如果是方法签名声明的异常,直接返回
  3. 异常类和接口类在同一个 jar 包,直接返回
  4. 异常类的包名是都以 java.或是 javax.开头 的直接返回(是 JDK 自带的异常,直接抛出)
  5. 如果是 dubbo 本身的异常 RpcException 直接抛出
  6. 否则,封装成一个新的 RunTimeException 抛出