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不能正常抛出特定异常的更多相关文章

  1. 将Controller抛出的异常转到特定View

    <!-- 将Controller抛出的异常转到特定View --> <bean class="org.springframework.web.servlet.handler ...

  2. 使用visual studio 2015调用阿里云oss .net sdk 2.2的putobject接口抛出outofmemory异常

    问题描述: 使用阿里云oss .net sdk 2.2版本,使用putobject接口上传文件时,抛出outofmemory异常. 原因分析: 上传时,用于准备上传的数据缓冲区内存分配失败.与应用软件 ...

  3. 捕获Java线程池执行任务抛出的异常

    捕获Java线程池执行任务抛出的异常Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常, public interface Runnable { publi ...

  4. 为什么只有在用Visual Studio启动程序时会抛出InvalidOperationException异常

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:为什么只有在用Visual Studio启动程序时会抛出InvalidOperationExceptio ...

  5. java 检查抛出的异常是否是要捕获的检查性异常或运行时异常或错误

    /** * Return whether the given throwable is a checked exception: * that is, neither a RuntimeExcepti ...

  6. druid抛出的异常------javax.management.InstanceAlreadyExistsException引发的一系列探索

    最近项目中有个定时任务的需求,定时检查mysql数据与etcd数据的一致性,具体实现细节就不说了,今天要说的就是实现过程中遇到了druid抛出的异常,以及解决的过程 异常 异常详细信息 五月 05, ...

  7. 外部无法捕捉Realm的doGetAuthenticationInfo方法抛出的异常

    shiro权限框架,用户登录方法的subject.login(token)会进入自定义的UserNamePasswordRealm类的doGetAuthenticationInfo身份验证方法 通常情 ...

  8. JavaWeb项目中获取对Oracle操作时抛出的异常错误码

    最近在项目中碰到了这么一个需求,一个JavaWeb项目,数据库用的是Oracle.业务上有一个对一张表的操作功能,当时设置了两个字段联合的唯一约束.由于前断没有对重复字段的校验,需要在插入时如果碰到唯 ...

  9. 关于thinkphp5手动抛出Http异常时自定义404页面报错的问题

    在使用HttpException手动抛出异常时,希望跳转到自定义的错误页面,官方的文章中是这样描述的. 可以使用\think\exception\HttpException类来抛出异常 // 抛出 H ...

随机推荐

  1. mysql 实现批量导入,并解决中文乱码问题

    public static String url = "jdbc:mysql://ip/database?characterEncoding=UTF-8"; //在database ...

  2. USACO2004 cube stacking /// 带权并查集 oj1302

    题目大意: 以N ( 1 ≤ N ≤ 30,000 )个堆栈开始,每个堆栈包含一个单独的立方体.执行P(1≤ P ≤100,000)的操作. 有两种类型的操作:移动和计数. *在移动操作中,将 包含方 ...

  3. POJ1149_PIGS(网络流/EK)

    PIGS Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 15721   Accepted: 7021 Description ...

  4. css之页面三列布局之左右上下高度固定,中间自适应

    第一种,绝对定位 !DOCTYPE HTML> <html> <head> <meta charset="gb2312"> <tit ...

  5. mac os Catalina beta andriod studio crash

    点击菜单 Help - Edit Custom VM Options 添加下面一行-Dsun.font.layoutengine=icu 然后我自己重启了好几下,然后 在重新创建一个新的工程,就好了 ...

  6. mysql 复制原理详解

    http://www.cnblogs.com/kristain/articles/4142970.html

  7. AndroidStudio WiFi调试插件

    前言 此篇博客也是Android studio插件篇的一部分,后续有时间我会介绍更多AndroidStudio的插件方便开发. Android设备用WiFi调试在以前一般是通过adb连接的,但是这样的 ...

  8. css---动画封装

    animation-name 属性指定应用的一系列动画,每个名称代表一个由@keyframes定义的动画序列 值: none 特殊关键字,表示无关键帧. keyframename 标识动画的字符串 a ...

  9. 阿里云应用上边缘云解决方案助力互联网All in Cloud

    九月末的杭州因为一场云栖大会变得格外火热. 9月25日,吸引全球目光的2019杭州云栖大会如期开幕.20000平米的展区集结数百家企业,为数万名开发者带来了一场前沿科技的饕餮盛宴. 如同往年一样,位于 ...

  10. git 大型灾难现场

    由于某种原因,需要重建git仓库.因此删了所有分支,重建git仓库. 删除整个过程 删除所有分支(除master外) git branch -d {branch_name}  # 删除本地分支 git ...