Dubbo服务引发捕获不到自定义异常的问题

注:该文是本博主记录学习之用,没有太多详细的讲解,敬请谅解!

一、问题

Dubbo服务调用过程中抛出的自定义异常捕获不到,总是抛出了一个RuntimeException包装了自定义异常,catch自定义异常捕获不到。

二、代码模块

以下是代码示例,略有简单,敬请谅解!

  1. 项目目录
    在这里插入图片描述
    说明:这里目录只是个示例,只是为了达到效果
    1、自定义异常和provider在springboot-rocketmq-account里面
    2、consumer在springboot-rocketmq-order里面

  2. 自定义异常代码
    在这里插入图片描述

  3. 业务处理异常抛出
    在这里插入图片描述
    注:@SneakyThrows(ValidationException.class) 原理这里就不讲解,其实编译后是throw ValidationException

  4. duubo服务的调用
    在这里插入图片描述

  5. 异常输出
    在这里插入图片描述
    由此异常的输出可见用ValidationException捕获不了异常,看到这是不是很纳闷,明明抛出了ValidationException,却捕获不到。好,我们打个断点看下它抛出了什么

  6. 断点输出
    在这里插入图片描述
    我们查看断点异常会发现它在外层包了个RuntimeException,是不是很疑惑,为什么会转成了RuntimeException异常呢,是不是服务处理过程中又处理了这个异常呢,好,我决定一探究竟。

从多个断点跳转来到了ExceptionFilter这个类,在org.apache.dubbo.rpc.filter包下,由此名字可见是dubbo的异常拦截器,我们来看看这个类的逻辑究竟做了些什么。。。

注:各个版本的源码都不一样,这里的源码版本是2.7.5

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.dubbo.rpc.filter;

import java.lang.reflect.Method;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
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.Filter.Listener;
import org.apache.dubbo.rpc.service.GenericService;

@Activate(
    group = {"provider"}
)
public class ExceptionFilter implements Filter, Listener {
    private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);

    public ExceptionFilter() {
    }

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

    public void onMessage(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = appResponse.getException();
                // 1、如果检查异常,非RuntimeException并且是Exception的话直接return出去,不作处理
                if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
                    return;
                }

                try {
                // 2、如果方法签名上有声明这个异常则return出去,不作处理
                    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    Class<?>[] exceptionClassses = method.getExceptionTypes();
                    Class[] var7 = exceptionClassses;
                    int var8 = exceptionClassses.length;

                    for(int var9 = 0; var9 < var8; ++var9) {
                        Class<?> exceptionClass = var7[var9];
                        if (exception.getClass().equals(exceptionClass)) {
                            return;
                        }
                    }
                } catch (NoSuchMethodException var11) {
                    return;
                }
				// 这里会打印出未处理的异常日志
                this.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);
                // 3、如果接口和异常在同一jar包下也return出去,不作处理
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
                    String className = exception.getClass().getName();
                    // 4、如果是JDK自带的异常也直接return出去
                    if (!className.startsWith("java.") && !className.startsWith("javax.")) {
                    	// 5、Dubbo本身的异常,直接return出去
                        if (exception instanceof RpcException) {
                            return;
                        }
						// 6、最后一步,如果前面的条件都不成立,则封装成一个RuntimeException异常抛出
                        appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
                        return;
                    }

                    return;
                }

                return;
            } catch (Throwable var12) {
                this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
            }
        }

    }

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

    public void setLogger(Logger logger) {
        this.logger = logger;
    }
}

最后做个总结:

  1. 如果是检查异常,则直接return出去,我的自定义异常ValidationException的是RuntimeException异常,不满足
  2. 如果方法签名上有声明这个异常则return出去,不作处理,这里也不满足
  3. 有上述的项目结构说明来看,这里接口和异常不在同一jar包,也不满足
  4. 如果是JDK自带的异常也直接return出去,很明显ValidationException是自定义的异常,不满足
  5. Dubbo本身的异常,直接return出去,ValidationException是自定义的异常,不满足
  6. 为什么捕获不到自定义的ValidationException异常,很明显走到了这一步,封装成一个RuntimeException异常抛出(重点)

三、问题解决

为什么捕获不到ValidationException异常的原因我们也清楚了,所以该如何处理心里也有个数了,这里简单的做个解决说明。

  1. 在方法上声明下ValidationException

    void testException() throws ValidationException;

  2. 将异常和接口放到同一jar下,即放到上述项目结构下的springboot-rocketmq-common下

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值