目录
- 问题现象
- 问题原因
- 解决方案
- 验证
问题现象
我们项目是通过dubbo框架来进行服务注册和发现的,Service层抛出自定义BaseException异常,而在Controller层捕获到的竟然是RuntimeException异常,不符合预期。
问题原因
想要知道问题出在什么地,只能了解dubbo是怎么处理异常的了。dubbo异常处理由filter链中org.apache.dubbo.rpc.filter.ExceptionFilter来处理的,咱们直接看源码吧。
@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter extends ListenableFilter {
public ExceptionFilter() {
super.listener = new ExceptionListener();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
static class ExceptionListener implements Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionListener.class);
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
// GenericService接口的实现类发生了异常,直接抛出
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// 检查型异常,直接抛出
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// 方法签名上异常,直接抛出
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.
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;
}
// jdk异常,直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// dubbo自定义异常RpcException,直接抛出
if (exception instanceof RpcException) {
return;
}
// 其他异常都会被包装成RuntimeException异常抛出
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;
} 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;
}
}
}
......
}
}
再梳理下异常处理逻辑,抛出的异常都会被包装成RuntimeException异常处理,除了以下6种情况:
- GenericService接口的实现类
- 检查型异常(除RuntimeException和Error以及子类以外的异常)
- 方法签名上异常(public void method() throws 任意异常)
- 自定义异常类和接口类在同一个jar包
- jdk异常(异常类以java./javax.开头的)
- RpcException异常 那我们自定义BaseException异常属于以上6种情况吗?肯定不属于啊,所以被包裹成RuntimeException异常处理了,我们该如何处理下达到我们的预期?
解决方案
重写dubbo的ExceptionFilter,我这在原来基础上简单处理了下,大家可根据自己项目实际情况处理,代码如下:
@Activate(group = CommonConstants.PROVIDER)
public class DubboExceptionFilter extends ListenableFilter {
public DubboExceptionFilter() {
super.listener = new ExceptionListener();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
static class ExceptionListener implements Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionListener.class);
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// 自定义BaseException异常,直接抛出
if (exception instanceof BaseException) {
return;
}
//源码......
}
//源码......
}
}
配置如下:
- 删除dubbo自带异常处理ExceptionFilter:dubbo.provider.filter:-exception
- 添加自定义异常处理Filter:在META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件中,添加dubboExceptionFilter=类路径.DubboExceptionFilter
验证
调整后,再次跑下,符合预期。