Dubbo自定义异常过滤器ExceptionFilter

831 阅读2分钟

说明

注:我的项目中使用的版本是dubbo 3.0.14

正常项目中的异常我们都是自定的异常如:ServiceException、BaseException等都是继承RuntimeException,然后配置全局统一捕获处理

@RestControllerAdvice
public class BaseExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(BaseExceptionHandler.class);


    @ExceptionHandler({ServiceException.class})
    public BaseResult handleServiceException(ServiceException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        logger.error("请求地址'{}',业务异常:{}", requestURI, e.getMessage());
        Integer code = e.getCode();
        String message = e.getMessage();
        return Objects.nonNull(code) ? BaseResult.err(String.valueOf(code), message) : BaseResult.err(message);
    }
}

问题

在A模块调用了B模块,B模块抛出了一个ServiceException,这时A模块接收到的不是ServiceException,而是一个RuntimeException,只是类字符描述信息了,很是杂乱,接口返回也很不容易看清楚异常信息

dubbo异常过滤器源码的问题,自定义的异常是被过滤器包装成RuntimeException抛给客户端。 如果想更加深入了解dubbo的过滤器源码,可以参考org.apache.dubbo.rpc.filter.ExceptionFilter

解决办法

自定义filter过滤器

编写DubboExceptionFilter

在B(生产者)模块中添加自定义dubbo异常过滤器


import com.alibaba.dubbo.common.Constants;
import com.mx.common.core.base.exception.BaseException;
import com.mx.common.core.base.exception.ServiceException;
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.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;

import java.lang.reflect.Method;

/**
 * 自定义dubbo异常过滤器
 *
 * @author de
 * @date 2024/8/5 13:36
 */
@Activate(group = Constants.PROVIDER)
public class DubboExceptionFilter implements Filter, Filter.Listener {
    private static final Logger logger = LoggerFactory.getLogger(DubboExceptionFilter.class);

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

    @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.getServiceContext().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;
                }

                // 返回自定义异常,我们项目中自定义的异常,根据自己的情况进行添加
                if (exception instanceof ServiceException
                        || exception instanceof BaseException) {
                    appResponse.setException(exception);
                    return;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            }
        }

    }

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

}

配置Dubbo的 SPI

Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置

/resources/META-INF/dubbo 下面创建文件 org.apache.dubbo.rpc.Filter

添加内容如下:

dubboExceptionFilter=com.xxx.config.DubboExceptionFilter

image.png

spring 配置文件

修改bootstrap.yml文件

# 自定义异常处理
dubbo:
  provider:
    filter: dubboExceptionFilter,-exception

接下来在调用B服务,B服务中的自定义异常都能正常显示在A服务中。 就是简单记录一下。