dubbo服务提供者无法捕捉自定义异常的问题解决

1254 字
6 分钟
dubbo服务提供者无法捕捉自定义异常的问题解决

一、问题#

Dubbo服务调用过程中抛出的自定义异常捕获不到,总是抛出了一个RuntimeException包装了自定义异常,导致全局异常处理器捕捉不到自定义异常。

经过断点可知:

image-20211216143614030
image-20211216143614030

自定义异常确实被包装成了RuntimeException异常。

经过断点调试跳转到ExceptionFilter这个类,在org.apache.dubbo.rpc.filter下,贴出源码 版本2

7.8

package org.apache.dubbo.rpc.filter;
import org.apache.dubbo.common.constants.CommonConstants;
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.service.GenericService;
import java.lang.reflect.Method;
/**
* ExceptionInvokerFilter
* <p>
* Functions:
* <ol>
* <li>unexpected exception will be logged in ERROR level on provider side. Unexpected exception are unchecked
* exception not declared on the interface</li>
* <li>Wrap the exception not introduced in API package into RuntimeException. Framework will serialize the outer exception but stringnize its cause in order to avoid of possible serialization problem on client side</li>
* </ol>
*/
@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionFilter.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
// 检查是否是RuntimeException或者Exception 我的自定义异常继承自RuntimeException 不满足此条件
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<?>[] 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);
// directly throw if exception class and interface class are in the same jar file.
// 如果异常类和接口类在同一jar文件中,则直接抛出 接口和异常不在同一个jar包中 不满足
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
// 如果是JDK异常 直接抛出 我的是自定义异常 不满足
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// directly throw if it's dubbo exception
// 如果是dubbo的异常 直接抛出 我的自定义异常 不满足
if (exception instanceof RpcException) {
return;
}
// otherwise, wrap with RuntimeException and throw back to the client
// 这里就说明了,其它异常会包装成一个RuntimeException并返回给客户端
appResponse.setException(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);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
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);
}
// For test purpose
public void setLogger(Logger logger) {
this.logger = logger;
}
}

二、解决方案#

  1. 在方法上声明自定义异常 也就是throws 自定义异常
  2. 将异常和接口放到同一个包下
  3. 重写一个ExceptionFilter替代dubbo的ExceptionFilter

这里说下方案3 代码就是复制的ExceptionFilter的代码 需要在里面把我们的自定义异常处理一下 顺便把otherwise下面的包装成我们的自定义异常,不让它再包装成RuntimeException

import com.tianch.platform.common.log.exception.PException;
import com.tianch.platform.common.log.exception.enmus.PExceptionEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;
import java.lang.reflect.Method;
@Slf4j
@Activate(group = CommonConstants.PROVIDER)
public class DubboProviderFilter implements Filter, Filter.Listener {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws org.apache.dubbo.rpc.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<?>[] 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.
log.error("Got unchecked and undeclared exception which called by " + org.apache.dubbo.rpc.RpcContext.getContext().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 PException) {
return;
}
// otherwise, 包装成我们的自定义异常
String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY);
PException pException = new PException(application, PExceptionEnum.SYSTEM_EXCEPTION.getCode(), exception.getMessage());
appResponse.setException(pException);
} catch (Throwable e) {
log.warn("Fail to ExceptionFilter when called by " + org.apache.dubbo.rpc.RpcContext.getContext().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) {
log.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);
}
}

和dubbo一样,利用SPI机制加载我们的异常过滤器

image-20211216152335306
image-20211216152335306

仿照这个文件写一个把我们自己的异常过滤器声明出来

image-20211216152515977
image-20211216152515977

在配置里去掉dubbo的异常过滤器并加上我们自己的异常过滤器

image-20211216152639390
image-20211216152639390

注意-是排除

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

dubbo服务提供者无法捕捉自定义异常的问题解决
https://tianch.com.cn/posts/tutorials/dubbo-fu-wu-ti-gong-zhe-wu-fa-bu-zhuo-zi-ding-yi-yi-chang-de-wen-ti-jie-jue/
作者
田小晖
发布于
2021-12-16
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
dubbo微服务之间的隐式数据传递
Java在做全链路压测插件时,如何让流量标识在dubbo远程RPC调用时进行传递是一个需要解决的问题,就可以用dubbo的Attachment做隐式传递,服务A) -- (服务B -- )(服务C)等等.
2
分布式锁详解:Redis与ZooKeeper实现原理与对比
Java详解分布式锁的三大核心要求与实现原理,手写set nx ex原子加锁、Lua原子释放锁,讲解Redisson看门狗自动续期、可重入,以及ZooKeeper临时顺序节点实现,最后对比Redis、ZooKeeper、数据库三种方案选型。
3
Java调用JS/TS与JS/TS调用Java完全指南
Java详解Java与JavaScript/TypeScript相互调用的各种方式:Nashorn、GraalJS脚本引擎调用JS函数与对象,TypeScript编译产物加载,JS中通过Java.type调用Java类、Java.extend继承接口,以及Bindings注入Java对象,附完整可运行代码与数据转换、性能注意事项。
4
布隆过滤器详解:原理、误判率与实战应用
中间件深入讲解布隆过滤器的原理:位数组+多个哈希函数如何实现O(k)判断,推导误判率公式与最优参数,对比Bitmap/HyperLogLog/Cuckoo Filter,手写Java实现并用Guava、Redis、Redisson实战缓存穿透防护、URL去重、黑名单拦截等场景。
5
Redis缓存穿透、缓存击穿、缓存雪崩的区别和解决方案
中间件深入剖析Redis缓存穿透、缓存击穿、缓存雪崩三者的区别与产生原因,并给出缓存空值、布隆过滤器、互斥锁、逻辑过期、过期时间随机化、多级缓存、高可用集群等解决方案,附Java代码示例。
随机文章随机推荐
Profile Image of the Author
田小晖
记录技术成长,分享运维与开发经验。
公告
分类
标签
站点统计
文章
115
分类
8
标签
35
总字数
216,150
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.15.6
文章许可
CC BY-NC-SA 4.0