记录一次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 ...
随机推荐
- Python生成Windows可执行exe文件
环境 python3.6.5 pyinstaller3.5 windows 10 下载地址 python:https://www.python.org/ftp/python/3.6.5/python- ...
- Android笔记之RoundedImageView
参考项目:GcsSloop/rclayout 实现1,利用Canvas.clipPath来实现,适用于任何View(无法去除锯齿效果) package com.bu_ish.blog; import ...
- Thread-per-Message 这个工作交给你了
Per是“每一”的意思,所以thread per message解释过来就是“每个消息一个线程”,message在这里可以看做是“命令”或“请求”的意思,对每隔命令或请求,分配一个线程,有这个线程执行 ...
- 02->交互式图形学--用glut库实现Sierpinski镂垫
Sierpinski图形是一类有趣的图形,本文结合交互式图形学教材,讲解Sierpinski镂垫图形生成,作为入门图形学编程的第一课.为了简便,本文就2D来进行讨论.实验环境见01 算法 主要是根据随 ...
- ubuntu自带截图工具
ubuntu自带的截图工具感觉能够满足基本的截图功能,可以不必安装另外的截图软件. 一般用到的截图类型有三种:全屏.当前活动窗口.自定义区域,其中自定义区域截图是最灵活也是我们用的最多的方式.在ubu ...
- shell 命令 链接文件 ln
1. 创建软链接文件 [ ln -s 源文件 链接文件] 类似于windows下的快捷方式 [ ln -s expr.sh expr ] 此时创建的链接文件路径是相对于现在所在的路径 ...
- linux 服务器安装mysql5.6
1.移除CentOS默认的mysql-libs: whereis mysql 2.为了避免冲突,先移除CenttOS上默认的mysql-libs: yum remove mysql-libs 3.然后 ...
- openwrt xfrp移植
对开源软件表示支持 https://github.com/KunTengRom/xfrp 上传编译,选择 cp .config xxx make 刷机 客户端配置文件: /tmp/etc# cat x ...
- 使用Intellij中的Spring Initializr来快速构建Spring Boot工程
本文将介绍嵌入的Intellij中的Spring Initializr工具,它同Web提供的创建功能一样,可以帮助我们快速的构建出一个基础的Spring Boot/Cloud工程. 1.菜单栏中选择F ...
- LUOGU P2564 [SCOI2009]生日礼物 (队列+模拟)
传送门 解题思路 还是比较好想的,用一个队列,然后把所有点放在一起排个序,依次入队.每次检查队头元素的种类是否为当前入队元素种类,是的话就一直\(pop\),每次更新答案即可. 代码 #include ...