dubbo SpringMVC 统一异常处理

前言

在一般的项目中,我们总是会遇到各种各样的异常,如果每次都用try-catch就太麻烦了,所以我们的系统中需要一种统一的异常处理,幸好这种问题早就有了解决方案。下面来了解一下SpringMVC 和 dubbo的解决方案

SpringMVC 统一异常处理

这里使用最简单的@ControllerAdvice和@ExceptionHandler来实现

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultBody  ExceptionHandler(Exception e){
        System.out.println("未知异常!" +e.getMessage());
        return ResultBody.error(e.getMessage());
    }
}

通过这两个注解我们就可以完成记录所有的异常,并且根据各种不同的异常进行不同的处理。

缺点:

  • 这种统一的异常只能对controller生效,因为@ControllerAdvice是一个controller增强接口

但对于使用微服务的接口来说,这样是无法满足需求的。所以我们对于服务提供者需要使用dubbo的异常处理

dubbo统一异常处理

目标

我们期望的异常处理方式是,当provider发生异常时,我们期望将全部异常转化为特定的结果,方便调用方根据错误类型处理。

dubbo默认处理方式

dubbo其实有自己默认的异常处理机制,在Dubbo的 provider端 抛出异常(Throwable),则会被 provider端 的ExceptionFilter拦截到,执行以下invoke方法,里面有个实现Listener类,重写了onResponse。

这里面 dubbo的处理方式就很简单

  • 不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接抛出。

  • 在方法签名上有声明,直接抛出。

  • 如果异常类和接口类在同一个jar包中,直接抛出。

  • 以java.或javax.开头的异常直接抛出。

  • dubbo自身的异常,直接抛出。

  • 不是以上情况会做toString处理并被封装成RuntimeException抛出。

 @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
                     //不是RuntimeException类型的异常,并且是受检异常(继承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<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            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);
				 //如果异常类和接口类在同一个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;
                    }
                    // directly throw if it's JDK exception
                     //以java.或javax.开头的异常直接抛出。
                    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)));
                    return;
                } 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;
                }
            }
        }

可以看到在源码中 看到dubbo的异常处理方式都是将异常直接抛出,所以异常会传递到consumer这一端,那consumer如果要保证业务的正常处理,就需要将provider的异常进行处理,这样会造成不必要的代码量。

为了解决这个问题,我们需要重写这个类。

解决方案

新建自定义异常处理Filter

首先,我们认同 dubbo中异常处理类中,只处理运行时异常,对其他受检查,如io异常,则交由程序处理。

对于 :在方法签名上有声明,直接抛出。 也不做处理。

对于其余的异常,我们这里都进行处理。具体代码如下

@Activate(group = CommonConstants.PROVIDER)
public class MyDubboExceptionFilter extends ListenableFilter {
    public MyDubboExceptionFilter() {
        super.listener = new MyDubboExceptionFilter.ExceptionListener();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    static class ExceptionListener implements Filter.Listener {

        private Logger logger = LoggerFactory.getLogger(MyDubboExceptionFilter.ExceptionListener.class);

        @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<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            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 it's dubbo exception
                    if (exception instanceof RpcException) {
                        return;
                    }
					
                    //java底层异常 全部处理,转换MyResult类型 为底层异常
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        appResponse.setException(null);
                        appResponse.setValue(new MyResult(5001,"系统异常"));
                        return;
                    }
					//自定义业务异常 全部处理,转换MyResult类型
                    if (exception instanceof BussinessException) {
                        appResponse.setException(null);
                        appResponse.setValue(new MyResult(5002,"业务异常"));
                        //appResponse.setValue("业务异常");
                        return;
                    }
					
                    //其余异常 也转为 MyResult类型
                    // otherwise, wrap with RuntimeException and throw back to the client
                    appResponse.setException(null);
                    appResponse.setValue(new MyResult(5002,"未知异常"));
                    return;
                } 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;
                }
            }
        }

        @Override
        public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
            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);
        }

        // For test purpose
        public void setLogger(Logger logger) {
            this.logger = logger;
        }
    }
}

配置spi

在META-INF.dubbo下新建com.alibaba.dubbo.rpc.Filter 并配置自定义异常类

dubboExceptionFilter=com.example.filter.MyDubboExceptionFilter

启用配置

启用自定义异常处理类,并且禁用其他异常处理类

dubbo:
  provider:
    filter: dubboExceptionFilter,-exception

dubbo总结:

这里只是简单的将异常转化为 自定义的返回类型(Myresult),所以在定义调用接口时,必须返回类型都是(Myresult),否则会调用失败,这点没做兼容,以后再弄吧

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值