自定义dubbo异常过滤器

  • 一、前置问题介绍:
  • 问题一
  • 问题二
  • 二、Dubbo的异常过滤器源码如下:
  • 三、实现方案 - 重写Dubbo的Filter异常过滤器
  • 至此,Dubbo自定义异常过滤器已完结!


一、前置问题介绍:

问题一

在dubbo框架中,由于一些 interface 接口未显示的声明抛出异常,导致dubbo在捕获异常时发现抛出的异常为非声明的异常,其也不属于jdk的异常,则dubbo框架会自动封装成 new RuntimeException(StringUtils.toString(exception))) 异常抛出;

问题二

针对一些自定义的异常,不希望被Dubbo框架捕获,只希望逐级向上抛出,直到业务的最顶层在捕获异常的code和message,并返回给前端显示code(这种情况适用于系统内部有自己定义的一套异常码,通过前端显示的code值能清晰的知道异常的问题是什么)

二、Dubbo的异常过滤器源码如下:

package com.alibaba.dubbo.rpc.filter;

import java.lang.reflect.Method;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.service.GenericService;

/**
 * ExceptionInvokerFilter
 * <p>
 * 功能:
 * <ol>
 * <li>不期望的异常打ERROR日志(Provider端)<br>
 *     不期望的日志即是,没有的接口上声明的Unchecked异常。
 * <li>异常不在API包中,则Wrap一层RuntimeException。<br>
 *     RPC对于第一层异常会直接序列化传输(Cause异常会String化),避免异常在Client出不能反序列化问题。
 * </ol>
 * 
 */
@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;
    }
    
    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();

                    // 如果是checked异常,直接抛出
                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }
                    // 在方法签名上有声明,直接抛出
                    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日志
                    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包里,直接抛出
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
                        return result;
                    }
                    // 是JDK自带的异常,直接抛出
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // 是Dubbo本身的异常,直接抛出
                    if (exception instanceof RpcException) {
                        return result;
                    }

                    // 否则,包装成RuntimeException抛给客户端
                    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;
        }
    }

}

三、实现方案 - 重写Dubbo的Filter异常过滤器

1、将上述 Dubbo 源码中 ExceptionFilter 复制到我们的项目改名为 DubboExceptionFilter

可直接粘贴本代码片段到IDEA中,只需将内部 start - end 中间的代码替换成自己实际的业务异常处理逻辑;

import java.lang.reflect.Method;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.service.GenericService;

/**
 * @author : gaogao
 * @version 1.0
 * @date : 2024-03-14 10:08
 *
 * 由于dubbo异常过滤器源码的问题,如果自定义的异常未在方法上声明,导致异常无法正常抛出,而是被过滤器包装成RuntimeException抛给客户端
 * 此工具类为重写dubbo异常拦截器,支持抛出自定义的异常!!!
 *
 * 如果使用此工具类需要注意几点:
 * 1、拦截器只拦截provider端,consumer端不需要拦截器
 * 2、需要在resources目录下创建 META-INF/dubbo目录,并创建 com.alibaba.dubbo.rpc.Filter 文件(注意文件层级:resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter),
 * 文件内容为:dubboExceptionFilter=com.bosssoft.cloud.coreservice.base.config.DubboExceptionFilter
 * 解释说明:dubboExceptionFilter:即你给这个过滤器取的名称,com.bosssoft.cloud.coreservice.base.config.DubboExceptionFilter:表示过滤器对应的完整路径
 *
 * 3、在ProviderConfig【此处为java代码示例,若为.xml配置文件可使用<dubbo:provider filter="dubboExceptionFilter,-exception"/>】 的配置中设置 :
 *   providerConfig.setFilter("dubboExceptionFilter,-exception");
 *   解释说明:dubboExceptionFilter为自己定义的过滤器名称;额外追加的 -exception ,是因为如果不加上 -exception 我们自己重写的过滤器确实生效了,但Dubbo自身默认的 ExceptionFilte 任然在工作
 *   (一个异常抛出会依次进入两个过滤器)所以我们需要 禁用 掉Dubbo默认的过滤器;同理,如果想禁用其它的过滤器,都可以通过 -过滤器名称来实现;
 *
 *  如果想更加深入了解dubbo的过滤器源码,可翻看阿里巴巴的com.alibaba.dubbo.rpc.filter.ExceptionFilter
 *
 */

@Activate(group = Constants.PROVIDER)
public class DubboExceptionFilter implements Filter {

    private final Logger logger;

    public DubboExceptionFilter() {
        this(LoggerFactory.getLogger(DubboExceptionFilter.class));
    }

    public DubboExceptionFilter(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();

                    // 如果是checked异常,直接抛出
                    if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }

                    String className = exception.getClass().getName();
                    
					//-----------------------------start--------------------------------------------
					//    在此可针对捕获到的异常,实现自己本身系统内部的异常业务逻辑
					
                    // 由于自身业务系统的业务,在此捕获自定义的异常直接返回,向上抛出;避免后续包装成RuntimeException抛给客户端
                    if (className.contains("com.cloud.component.exception.XXXXXXException")
                        || className.contains("com.cloud.component.exception.XXXXXXException")) {
                        return result;
                    }
					//-------------------------------end--------------------------------------------
					
                    // 在方法签名上有声明,直接抛出
                    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日志
                    logger.error("未在方法签名上定义的异常: " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: "
                        + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                    // 异常类和接口类在同一jar包里,直接抛出
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                        return result;
                    }
                    // 是JDK自带的异常,直接抛出
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // 是Dubbo本身的异常,直接抛出
                    if (exception instanceof RpcException) {
                        return result;
                    }

                    // 否则,包装成RuntimeException抛给客户端
                    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;
        }
    }
}

2、在resources目录下添加纯文本文件 META-INF/dubbo/com.alibaba.dubbo.rpc.Filter 并在文件中添加如下内容:

dubboExceptionFilter=com.cloud.util.dubbo.DubboExceptionFilter

请将 = 后边的内容换成 DubboExceptionFilter 自定义过滤器的实际类路径

如下图:

dubbo设置过滤器_spring

3、修改dubbo 的配置文件,将 DubboExceptionFilter 加载进去并且去掉自身的 ExceptionFilter

1)若采用 .xml配置文件,则补充如下内容:

<dubbo:providerfilter="dubboExceptionFilter,-exception"/>

2)若采用 java代码,使用@Bean装配时,请在 DubboProviderConfig dubbo的提供者中补充如下内容:

@Bean
 public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setId("proload");
        providerConfig.setPayload(52428800);
        providerConfig.setFilter("dubboExceptionFilter,-exception");
        return providerConfig;
 }

至此,Dubbo自定义异常过滤器已完结!

如果流程中哪里不懂,请仔细认真阅读 3.1章节 中的 DubboExceptionFilter类注释 ~~~