记录一次dubbo不能正常抛出特定异常
BUG场景
今天同事的代码中出现一个问题,让我帮忙排查一下。原代码大致如下
dubbo服务消费者:
@Resource
private IPayWayService payWayService; @RequestMapping(value = "/add", method = RequestMethod.POST)
@ApiResponses(value = {@ApiResponse(code = 200, message = "请求成功")})
@ApiOperation(value = "/add", notes = "新增通道")
public Result<Boolean> addPayWay( @RequestBody PayWayDto payWayDto) {
logger.info("请求新增通道接口 payWayDto:{}",payWayDto);
try{
TransactionResult<Boolean> result =payWayService.addPayWay(payWayDto);
return new Result<>(result.getCode(),result.getMessage());
}catch (PaymentException pe){
logger.info("请求新增通道接口异常:error:{}",pe);
return new Result<>(ResultCode.C500.code,pe.getMessage());
}catch (RuntimeException re){
logger.info("error:",re.getMessage());
return new Result<>(ResultCode.C500.code,re.getMessage());
}catch (Exception e){
logger.info("error:",e.getMessage());
return new Result<>(ResultCode.C500.code,e.getMessage());
}
}
dubbo服务提供者:
@Override
@Transactional(rollbackFor = Exception.class)
public TransactionResult<Boolean> addPayWay(PayWayDto payWayDto){
logger.info("新增通道表信息 payWayDto:{}",payWayDto); try{
doSomething();
return TransactionResult.newSuccess(Boolean.TRUE);
}catch (Exception e){
if(e instanceof DuplicateKeyException){
logger.info("新增通道失败,唯一主机冲突 error:{}",e.getMessage());
throw new PaymentException(ResultCode.C500.getCode(),"新增通道失败!通道已经存在");
}
throw new PaymentException(ResultCode.C500.getCode(),e.getMessage());
}
}
问了同事的意图,他希望如果提供方抛出PaymentException的时候,服务方能够捕获到对应PaymentException。然而,在上面的代码中,消费者捕获不到PaymentException,只能捕获到RuntimeException。看到这个问题,因为没有这方面的经验,第一时间也是懵逼。不过问题不大,毕竟遇到这种情况,不懂的问题慢慢查就好。
BUG定位
自己写了个测试,发现我的代码居然能正常捕获到PaymentException。一时间也没发现有啥不同,就开启debug模式,反正遇到RPC的问题,第一时间怀疑dubbo就对了。开始翻阅代码,直到翻阅到 ExceptionFilter 这个类,发现了问题所在。
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服务方处理自己抛出异常的时候会进行区别对待,checked 异常、方法上有抛出的异常、异常类和接口在同一个jar包里的、JDK自带的、dubbo自带的异常 都是直接抛出,剩余的异常全都封装为Runtime抛出。同事的代码里,异常类和接口类没有放在同一个jar包,所以dubbo会将其封装为RunTimeException抛出。
思考总结
问题很快就找到了,迅速解决问题后,dubbo这么写的原因是什么呢?或者说dubbo不怎么写会怎么样呢?
ExceptionFilter 类是在dubbo提供者中执行的,用于对处理服务方内部异常。先假设dubbo不这么处理,会发生什么呢? 当提供者抛出异常的时候,如果消费者不能识别该异常,将无法进行正常的反序列化,导致程序错误。所以上面特殊处理的多种异常都是服务提供者能确定消费者能够正常反序列化的前提下才将该异常抛出,否则都包装成RunTimeException抛出。
因此dubbo上述代码是考虑到消费者无法识别异常的情况下,做的一项安全处理。
记录一次dubbo不能正常抛出特定异常的更多相关文章
- 将Controller抛出的异常转到特定View
<!-- 将Controller抛出的异常转到特定View --> <bean class="org.springframework.web.servlet.handler ...
- 使用visual studio 2015调用阿里云oss .net sdk 2.2的putobject接口抛出outofmemory异常
问题描述: 使用阿里云oss .net sdk 2.2版本,使用putobject接口上传文件时,抛出outofmemory异常. 原因分析: 上传时,用于准备上传的数据缓冲区内存分配失败.与应用软件 ...
- 捕获Java线程池执行任务抛出的异常
捕获Java线程池执行任务抛出的异常Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常, public interface Runnable { publi ...
- 为什么只有在用Visual Studio启动程序时会抛出InvalidOperationException异常
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:为什么只有在用Visual Studio启动程序时会抛出InvalidOperationExceptio ...
- java 检查抛出的异常是否是要捕获的检查性异常或运行时异常或错误
/** * Return whether the given throwable is a checked exception: * that is, neither a RuntimeExcepti ...
- druid抛出的异常------javax.management.InstanceAlreadyExistsException引发的一系列探索
最近项目中有个定时任务的需求,定时检查mysql数据与etcd数据的一致性,具体实现细节就不说了,今天要说的就是实现过程中遇到了druid抛出的异常,以及解决的过程 异常 异常详细信息 五月 05, ...
- 外部无法捕捉Realm的doGetAuthenticationInfo方法抛出的异常
shiro权限框架,用户登录方法的subject.login(token)会进入自定义的UserNamePasswordRealm类的doGetAuthenticationInfo身份验证方法 通常情 ...
- JavaWeb项目中获取对Oracle操作时抛出的异常错误码
最近在项目中碰到了这么一个需求,一个JavaWeb项目,数据库用的是Oracle.业务上有一个对一张表的操作功能,当时设置了两个字段联合的唯一约束.由于前断没有对重复字段的校验,需要在插入时如果碰到唯 ...
- 关于thinkphp5手动抛出Http异常时自定义404页面报错的问题
在使用HttpException手动抛出异常时,希望跳转到自定义的错误页面,官方的文章中是这样描述的. 可以使用\think\exception\HttpException类来抛出异常 // 抛出 H ...
随机推荐
- Memory barrier,
A memory barrier, also known as a membar, memory fence or fence instruction, 是一种屏障指令,它使中央处理单元(CPU)或编 ...
- 第一章:Lambda表达式入门概念
要点:将行为像数据一样传递. 一.几种形式 1.没有参数,用()表示 () ->System.out.println("Hello World"); 2.有且仅有一个参数,省 ...
- c++实现写一个函数,求2个整数的和,要求在函数体内不得使用+,-* /
#include <iostream> using namespace std; int add(int x, int y) { return x+y; } int addmove(int ...
- Cookie自动管理
如果想要对 cookies 干点什么就需要 HTTP Cookie 管理器了. 啥是 cookies 呢.储存在用户本地终端上的数据通常情况下,当用户结束浏览器会话 时,系统将终止所有的 Cookie ...
- selenium基础-跳过验证码
selenium基础-跳过验证码 一.方法 设置万能验证码或者屏蔽验证码(最常用的方法) 使用验证码识别工具识别验证码 通过selenium操作cookies 直接使用配置文件的webdriver 二 ...
- RDD运行原理
- boost相关函数
1.boost::scoped_ptr是一个比较简单的智能指针,它能保证在离开作用域之后它所管理对象能被自动释放 #include <iostream> #include <boos ...
- 查看网站pv
PV(page view),即页面浏览量,或点击量:通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标.简单来解释PV,那就是一个访问者在24小时(0点到24点)内到底看了你网站多少个页面.这 ...
- python库之xgboost
一.安装 https://www.zhihu.com/question/46377605
- ubuntu卸载node和npm
sudo apt-get remove --purge npm sudo apt-get remove --purge nodejs sudo apt-get remove --purge nodej ...