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. UVA 10382 Watering Grass 贪心+区间覆盖问题

    n sprinklers are installed in a horizontal strip of grass l meters long and w meters wide. Each spri ...

  2. JDBC_数据库连接池DRUID

    /** * @Description: TODO(这里用一句话描述这个类的作用) * @Author aikang * @Date 2019/8/26 20:12 */ /* 1.数据库连接池: 1. ...

  3. Flutter BottomNavigationBar切换会刷新当前页面解决方

    问题描述 BottomNavigationBar 是flutter 中最常用的UI组建,刚接触时发现在切换tab 的时候,会刷新当前的所有状态,每个页面都会重新刷新.于是乎,在这里先记录下解决方案. ...

  4. SVN Cannot merge into a working copy that has local modifications

    我尝试了 主支,分支都提交,但是依然无法合并. 最终,我在服务器上将分支删除,然后主支在拷贝过去. 一,打开服务器资源 二,删除分支 三,拷贝主支到分支 四,刷新分支,就能看到了. 然后在分支项目中, ...

  5. WIN10安装CUDA10 cuDNN

    文章目录 CPU和GPU 什么是CUDA 什么是cuDNN WIN10安装CUDA10 WIN10安装cuDNN CPU和GPU CPU和GPU是不一样的计算机设备,CPU作为计算机心脏一直被人们所认 ...

  6. dubbo重连机制会不会造成错误

    dubbo在调用服务不成功时,默认会重试2次. Dubbo的路由机制,会把超时的请求路由到其他机器上,而不是本机尝试,所以 dubbo的重试机器也能一定程度的保证服务的质量. 但是如果不合理的配置重试 ...

  7. 数据库的元数据抽取SQL

    一.数据库驱动类.端口.默认用户名密码 数据库 驱动 端口 用户名 密码 MySQL com.mysql.jdbc.Driver 3306 root root DB2 com.ibm.db2.jcc. ...

  8. socket 上传文件

    """ "" server.py """服务端 """import socketimpor ...

  9. 阿里云宣布 Serverless 容器服务 弹性容器实例 ECI 正式商业化

    摘要: 阿里云宣布弹性容器实例 ECI(Elastic Container Instance)正式商业化,ECI 是阿里云践行普惠的云计算理念,将 Serverless 和 Container 技术结 ...

  10. helm安装kubernetes的插件istio

    1.安装istio 要使用Helm自定义Istio安装,请使用--set <key>=<value>Helm命令中的选项覆盖一个或多个值 怎么使用选项配置请查看官网https: ...