首页   >   代码编程   >   JAVA开发

如何在dubbo中捕获并处理自定义业务异常

在没有使用dubbo服务之前,一些业务校验失败的场景中,都是直接抛出一个自定义业务异常,用起来非常的丝滑流畅,但是在使用了dubbo服务化之后,每次抛出了自定义业务异常之后,dubbo会主动将其封装成RuntimeException,这样就会导致自定义异常传递信息丢失,从而导致consumer无法直接获取到provider提供的错误信息。

如下图,是我在provider中抛出的错误(为了下篇文章做准备,所以我提前重写了ExceptionFilter将错误日志打印出来):

如何在dubbo中捕获并处理自定义业务异常

由于将我的ServiceException当做字符串封装到了RuntimeException中,所以我在consumer中写的全局异常捕获就以失败告终。

package com.zhiri.app.api.application.handler;

import com.zhiri.api.core.server.common.Result;
import com.zhiri.common.base.ServiceException;
import com.zhiri.common.enums.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 全局异常处理器,返回特定的json提示信息
 *
 * @author SongFei
 * @date 2019/11/18 22:31
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 自定义业务异常
     */
    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    public Result<?> catchServiceException(ServiceException e) {
        log.error(ExceptionUtils.getStackTrace(e));
        return new Result<>(ResponseCode.BUSINESS_FAIL.getCode(), e.getMessage());
    }

    /**
     * 参数非法异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public Result<?> catchIllegalArgumentException(IllegalArgumentException e) {
        log.error(ExceptionUtils.getStackTrace(e));
        return new Result<>(ResponseCode.FAIL);
    }

    /**
     * 入参校验异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result<?> catchMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error(ExceptionUtils.getStackTrace(e));
        Result<?> result = Result.fail("illegal parameter");
        FieldError fieldError = e.getBindingResult().getFieldError();
        if (fieldError != null) {
            result.setTips(fieldError.getDefaultMessage());
        }
        return result;
    }

    /**
     * 通用Exception处理
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result<?> catchException(Exception e) {
        log.error(ExceptionUtils.getStackTrace(e));
        Result<?> result = new Result<>(ResponseCode.FAIL);
        result.setTips("系统异常,稍后再试");
        return result;
    }

}

因为RuntimeException的作用域太宽泛了,所以我们也无法通过监听它来获取一些有用的信息,就类似于上面的Exception监听,只能拿来做一个系统异常的提示。

那么,该如何解决这个dubbo自定义异常传递信息丢失的问题呢?

一、直接禁用dubbo的ExceptionFilter(不推荐)

在dubbo的配置中,提供了一个dubbo.provider.filter的选项,可用来禁掉这个ExceptionFilter。

dubbo.provider.filter=-exception

但是此种方式有一个非常大的弊端,在一些场景下,异常传递过程中会解析失败,导致无法根据日志排查问题。

二、对外接口统一封装格式

这个好理解了,所有的provider在提供接口时,返回值都封装一个特定的Result类,里面有基本的code、msg、data字段,就跟http调用一些api接口一样,可以从中判断出状态,拿到错误信息,以及接口响应值。

三、在provider中自定义ExceptionFilter(最优方式)

package com.zhiri.biz.center.application.filter;


import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;

import java.lang.reflect.Method;

/**
 * 自定义dubbo异常处理,不让dubbo包一层RuntimeException
 *
 * @author SongFei
 * @date 2020/5/7 17:03
 */
@Activate(group = CommonConstants.PROVIDER)
@Slf4j
public class DubboExceptionFilter implements Filter, Filter.Listener {

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

    @Override
    public void onMessage(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.
                log.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;
                }
                // customer exception
                if (className.startsWith("com.zhiri.common.base")) {
                    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) {
                log.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            }
        }
    }

    @Override
    public void onError(Throwable throwable, Invoker<?> invoker, Invocation invocation) {
        log.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + throwable.getClass().getName() + ": " + throwable.getMessage(), throwable);
    }

}

上面的的代码是针对dubbo2.7.5版本,如果是低版本的dubbo,此处写法会有稍许不同,大家在使用的时候根据自己的dubbo版本来!

这里只提供了基本的思路和方向,具体实现请参考:Dubbo自定义ExceptionFilter实现业务异常透传

QQ群Ⅰ: 686430774 (已满)

QQ群Ⅱ: 718410762 (已满)

QQ群Ⅲ: 638620451 (已满)

QQ群Ⅳ: 474195684 (已满)

QQ群Ⅴ: 463034360 (已满)

QQ群Ⅵ: 879266502 (已满)

QQ群Ⅶ: 627786015 (已满)

工作5分钟,吹逼2小时: 855525339 (娱乐消遣,广告狗勿进)

如果文章有帮到你,可以考虑请博主喝杯咖啡!

分享到:

欢迎分享本文,转载请注明出处!

作者:不忘初心

发布时间:2020-05-13

永久地址:https://www.jiweichengzhu.com/article/ec0cf0de08854c5aa65273f04f49997b

评论