今天同事的代碼中出現一個問題,讓我幫忙排查一下。原代碼大體以下java
dubbo服務消費者:安全
1 @Resource 2 private IPayWayService payWayService; 3 4 @RequestMapping(value = "/add", method = RequestMethod.POST) 5 @ApiResponses(value = {@ApiResponse(code = 200, message = "請求成功")}) 6 @ApiOperation(value = "/add", notes = "新增通道") 7 public Result<Boolean> addPayWay( @RequestBody PayWayDto payWayDto) { 8 logger.info("請求新增通道接口 payWayDto:{}",payWayDto); 9 try{ 10 TransactionResult<Boolean> result =payWayService.addPayWay(payWayDto); 11 return new Result<>(result.getCode(),result.getMessage()); 12 }catch (PaymentException pe){ 13 logger.info("請求新增通道接口異常:error:{}",pe); 14 return new Result<>(ResultCode.C500.code,pe.getMessage()); 15 }catch (RuntimeException re){ 16 logger.info("error:",re.getMessage()); 17 return new Result<>(ResultCode.C500.code,re.getMessage()); 18 }catch (Exception e){ 19 logger.info("error:",e.getMessage()); 20 return new Result<>(ResultCode.C500.code,e.getMessage()); 21 } 22 }
dubbo服務提供者:服務器
1 @Override 2 @Transactional(rollbackFor = Exception.class) 3 public TransactionResult<Boolean> addPayWay(PayWayDto payWayDto){ 4 logger.info("新增通道表信息 payWayDto:{}",payWayDto); 5 6 try{ 7 doSomething(); 8 return TransactionResult.newSuccess(Boolean.TRUE); 9 }catch (Exception e){ 10 if(e instanceof DuplicateKeyException){ 11 logger.info("新增通道失敗,惟一主機衝突 error:{}",e.getMessage()); 12 throw new PaymentException(ResultCode.C500.getCode(),"新增通道失敗!通道已經存在"); 13 } 14 throw new PaymentException(ResultCode.C500.getCode(),e.getMessage()); 15 } 16 }
問了同事的意圖,他但願若是提供方拋出PaymentException的時候,服務方可以捕獲到對應PaymentException。然而,在上面的代碼中,消費者捕獲不到PaymentException,只能捕獲到RuntimeException。看到這個問題,由於沒有這方面的經驗,第一時間也是懵逼。不過問題不大,畢竟遇到這種狀況,不懂的問題慢慢查就好。app
本身寫了個測試,發現個人代碼竟然能正常捕獲到PaymentException。一時間也沒發現有啥不一樣,就開啓debug模式,反正遇到RPC的問題,第一時間懷疑dubbo就對了。開始翻閱代碼,直到翻閱到 ExceptionFilter 這個類,發現了問題所在。ide
1 public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { 2 try { 3 Result result = invoker.invoke(invocation); 4 if (result.hasException() && GenericService.class != invoker.getInterface()) { 5 try { 6 Throwable exception = result.getException(); 7 8 // 若是是checked異常,直接拋出 9 if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) { 10 return result; 11 } 12 // 在方法簽名上有聲明,直接拋出 13 try { 14 Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); 15 Class<?>[] exceptionClassses = method.getExceptionTypes(); 16 for (Class<?> exceptionClass : exceptionClassses) { 17 if (exception.getClass().equals(exceptionClass)) { 18 return result; 19 } 20 } 21 } catch (NoSuchMethodException e) { 22 return result; 23 } 24 25 // 未在方法簽名上定義的異常,在服務器端打印ERROR日誌 26 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() 27 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() 28 + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); 29 30 // 異常類和接口類在同一jar包裏,直接拋出 31 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); 32 String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); 33 if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){ 34 return result; 35 } 36 // 是JDK自帶的異常,直接拋出 37 String className = exception.getClass().getName(); 38 if (className.startsWith("java.") || className.startsWith("javax.")) { 39 return result; 40 } 41 // 是Dubbo自己的異常,直接拋出 42 if (exception instanceof RpcException) { 43 return result; 44 } 45 46 // 不然,包裝成RuntimeException拋給客戶端 47 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); 48 } catch (Throwable e) { 49 logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() 50 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() 51 + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); 52 return result; 53 } 54 } 55 return result; 56 } catch (RuntimeException e) { 57 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() 58 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() 59 + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); 60 throw e; 61 } 62 }
這裏能夠看到,dubbo服務方處理本身拋出異常的時候會進行區別對待,checked 異常、方法上有拋出的異常、異常類和接口在同一個jar包裏的、JDK自帶的、dubbo自帶的異常 都是直接拋出,剩餘的異常全都封裝爲Runtime拋出。同事的代碼裏,異常類和接口類沒有放在同一個jar包,因此dubbo會將其封裝爲RunTimeException拋出。 測試
問題很快就找到了,迅速解決問題後,dubbo這麼寫的緣由是什麼呢?或者說dubbo不怎麼寫會怎麼樣呢?spa
ExceptionFilter 類是在dubbo提供者中執行的,用於對處理服務方內部異常。先假設dubbo不這麼處理,會發生什麼呢? 當提供者拋出異常的時候,若是消費者不能識別該異常,將沒法進行正常的反序列化,致使程序錯誤。因此上面特殊處理的多種異常都是服務提供者能肯定消費者可以正常反序列化的前提下才將該異常拋出,不然都包裝成RunTimeException拋出。debug
所以dubbo上述代碼是考慮到消費者沒法識別異常的狀況下,作的一項安全處理。日誌