注:该文是本博主记录学习之用,没有太多详细的讲解,敬请谅解!
一、问题
Dubbo服务调用过程中抛出的自定义异常捕获不到,总是抛出了一个RuntimeException包装了自定义异常,catch自定义异常捕获不到。
二、代码模块
以下是代码示例,略有简单,敬请谅解!
-
项目目录
说明:这里目录只是个示例,只是为了达到效果
1、自定义异常和provider在springboot-rocketmq-account里面
2、consumer在springboot-rocketmq-order里面 -
自定义异常代码
-
业务处理异常抛出
注:@SneakyThrows(ValidationException.class) 原理这里就不讲解,其实编译后是throw ValidationException -
duubo服务的调用
-
异常输出
由此异常的输出可见用ValidationException捕获不了异常,看到这是不是很纳闷,明明抛出了ValidationException,却捕获不到。好,我们打个断点看下它抛出了什么 -
断点输出
我们查看断点异常会发现它在外层包了个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;
}
}
最后做个总结:
- 如果是检查异常,则直接return出去,我的自定义异常ValidationException的是RuntimeException异常,不满足
- 如果方法签名上有声明这个异常则return出去,不作处理,这里也不满足
- 有上述的项目结构说明来看,这里接口和异常不在同一jar包,也不满足
- 如果是JDK自带的异常也直接return出去,很明显ValidationException是自定义的异常,不满足
- Dubbo本身的异常,直接return出去,ValidationException是自定义的异常,不满足
- 为什么捕获不到自定义的ValidationException异常,很明显走到了这一步,封装成一个RuntimeException异常抛出(重点)
三、问题解决
为什么捕获不到ValidationException异常的原因我们也清楚了,所以该如何处理心里也有个数了,这里简单的做个解决说明。
-
在方法上声明下ValidationException
void testException() throws ValidationException;
-
将异常和接口放到同一jar下,即放到上述项目结构下的springboot-rocketmq-common下