0 results found
DeCo
dubbo处理自定义异常问题
2019/05/17 java dubbo 异常

在使用dubb的时候发现了一个问题,在api包中自定义了异常,在服务提供端抛出,在服务消费端竟然无法捕获

dubbo-api

import demo.dubbo.spring.boot.api.exception.ApiException;
import demo.dubbo.spring.boot.api.exception.ApiRuntimeException;

public interface EchoService {

    String echo(String message);

    String echoExp(String message) throws ApiException;

    String echoRuntimeExp(String message);
}
public class ApiException extends Exception {
    private static final long serialVersionUID = 7811529833876460537L;
}
public class ApiRuntimeException extends RuntimeException {
    private static final long serialVersionUID = -5973522858593078694L;
}

dubbo-provider

@Service(
        version = "${echo.service.version}",
        application = "${dubbo.application.id}",
        protocol = "${dubbo.protocol.id}",
        registry = "${dubbo.registry.id}"
)
public class DefaultEchoService implements EchoService {

    @Override
    public String echo(String message) {
        String response = "Echo : " + message;
        System.out.println(response);
        return response;
    }

    @Override
    public String echoExp(String message) throws ApiException {
        String response = "Echo : " + message;
        if (true) {
            throw new ApiException();
        }
        return response;
    }

    @Override
    public String echoRuntimeExp(String message) {
        String response = "Echo : " + message;
        if (true) {
            throw new ApiRuntimeException();
        }
        return response;
    }
}

dubbo-consumer

  @Reference(
        version = "${echo.service.version}",
        application = "${dubbo.application.id}",
        url = "dubbo://localhost:12345"
)
private EchoService echoService;

@GetMapping("/exp")
public String echoExp(@RequestParam String message) throws ApiException {
    try {
        return echoService.echoExp(message);
    } catch (ApiException e) {
        System.out.println("ApiException");
    } catch (RuntimeException e) {
        System.out.println("RuntimeException");
    } catch (Exception e) {
        System.out.println("Exception");
    }
    return "errormsg";
}

@GetMapping("/expRun")
public String echoExpRun(@RequestParam String message) {
    try {
        return echoService.echoRuntimeExp(message);
    } catch (ApiRuntimeException e) {
        System.out.println("ApiRuntimeException");
    } catch (RuntimeException e) {
        System.out.println("RuntimeException");
    } catch (Exception e) {
        System.out.println("Exception");
    }
    return "errormsg";
}

控制台打印

RuntimeException
RuntimeException

自定义异常没有捕获到。。RuntimeException倒是捕获成功了。。

查阅了一下资料以后是因为dubbo对异常进行了处理,如果Dubbo的暴露抛出异常(Throwable),则会被暴露方的ExceptionFilter拦截到,执行以下invoke方法:

/**
 * ExceptionInvokerFilter
 * <p>
 * Functions:
 * <ol>
 * <li>unexpected exception will be logged in ERROR level on provider side. Unexpected exception are unchecked
 * exception not declared on the interface</li>
 * <li>Wrap the exception not introduced in API package into RuntimeException. Framework will serialize the outer exception but stringnize its cause in order to avoid of possible serialization problem on client side</li>
 * </ol>
 * <p>
 * 功能:
 * <ol>
 * <li>不期望的异常打ERROR日志(Provider端)<br>
 *     不期望的日志即是,没有的接口上声明的Unchecked异常。
 * <li>异常不在API包中,则Wrap一层RuntimeException。<br>
 *     RPC对于第一层异常会直接序列化传输(Cause异常会String化),避免异常在Client出不能反序列化问题。
 */
@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();

                    // directly throw if it's checked exception
										// 如果是checked异常,直接抛出
                    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;
                    }

										// 未在方法签名上定义的异常,在服务器端打印ERROR日志	
                    // 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);
										
										// 异常类和接口类在同一jar包里,直接抛出
                    // 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;
                    }
										// 是JDK自带的异常,直接抛出
                    // directly throw if it's JDK exception
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
										// 是Dubbo本身的异常,直接抛出
                    // directly throw if it's dubbo exception
                    if (exception instanceof RpcException) {
                        return result;
                    }

										// 否则,包装成RuntimeException抛给客户端
                    // otherwise, wrap with RuntimeException and throw back to the client
                    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;
        }
    }

}

调试发现在生产者端是返回的是自定义的异常


消费端catch到的是动态代理异常UndeclaredThrowableException

请杯咖啡呗~
支付宝
微信
本文作者:DeCo
版权声明:本文首发于DeCo的博客,转载请注明出处!