異常在Java中有兩種分類:Error(OutOfMemoryError之類的咱們本身程序沒法處理的很是嚴重的錯誤,Java推薦不catch,讓程序隨之崩潰)、Excepiton(NullPointerException之類的並不致命的錯誤,Java以爲indicates conditions that a reasonable application might want to catch,推薦catch),本文如下內容涉及到的都是Exception。前端
本文會結合REST API與Spring的一些具體實踐來探討一下異常處理的套路。程序員
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。web
關於異常是拿來幹什麼的,不少人老程序員認爲就是拿來咱們Debug的時候排錯的,固然這一點確實是異常機制很是大的一個好處,但異常機制包含着更多的意義。數據庫
異常處理(又稱爲錯誤處理)功能提供了處理程序運行時出現的任何意外或異常狀況的方法。異常處理使用 try、catch 和 finally 關鍵字來嘗試可能未成功的操做,處理失敗,以及在過後清理資源。後端
我把異常根據意義成三種:業務、系統、代碼異常,不一樣的異常採用不一樣的處理方式。緩存
1 2 3 4 5 6 7 8 9 |
@GetMapping("/{id}") public ReservationDetail getDetail(@PathVariable String id) { ReservationDetail result = applicationService.getReservationDetail(id); if (result == null) { throw new InfoNotFoundExcepiton("reservation with id=" + id + " is not exist"); } return result; } |
以上代碼當沒有查到數據的時候拋出一個InfoNotFoundExcepiton異常,查詢一個信息但不存在,沒有任何系統級別的錯誤發生,而是數據確實不存在,此時屬於業務異常。這個例子比較侷限,其餘的場景可能有一個用戶想訪問某個API,可是沒有權限,此時能夠返回無權限的業務異常。網絡
將全部業務異常拋出,並經過Spring提供的接口進行統一處理,要注意的是,返回碼也是須要分別標示的,對於意義不一樣的業務異常,對應的錯誤返回碼也是須要被指定的:架構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@RestControllerAdvice public class ControllerAdvice { private static final Logger logger = LoggerFactory.getLogger(ControllerAdvice.class); @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResult handleOtherException(Throwable e) { RestControllerAdvice return new ErrorResult(ErrorCode.UNKNOWN, e.getMessage()); } @ExceptionHandler(ResourceAccessException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResult handleResourceNotFoundException(ResourceAccessException e) { logger.error(e.getMessage(), e); return new ErrorResult(ErrorCode.RESOURCE_NOT_FOUND, e.getMessage()); } } |
這種異常處理方式我的認爲在咱們代碼中越多越好,若是能在代碼中涵蓋業務中的不少邊界值,對於總體應用的健壯性提高有着很是大的幫助,而且對於前端來講,前端能夠根據此異常信息給予用戶更加明確友好的錯誤提示:app
1 2 3 4 5 |
攜帶錯誤碼爲500的錯誤請求: { "message": "cannot find pre inspection base info by order id: 1", "error_code": "SERVICE_REQUEST_ERROR" } |
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。框架
這種異常在調試時很是常見,要麼是某個服務掛掉了,或者超時這樣的狀況,跟業務沒有關係,也不是代碼中的BUG致使的,這個時候咱們必須設計好一個預案去cover這種風險。
在微服務架構中,這種狀況時有發生,如在我翻譯過的這篇文章中提到的,使用Netflix Hystrix解決,在Spring Cloud中已經攜帶該模塊。具體以下:
那麼問題來了,這些異常能夠與其餘異常分類統一格式返回給前端嗎?
1 |
@ExceptionHandler(Throwable.class) |
這行代碼捕捉了全部的異常,包括Error級別的,這是根據特定項目需求來肯定的,因此即便是Error也須要記錄下來,出錯以後方便錯誤的排查。
我把代碼中存在的BUG叫作代碼異常,與系統異常不一樣的是,這種異常只能儘可能避免與預防。好比程序員沒有考慮到的狀況致使空指針異常、SQL語句編寫錯誤致使SQLException。在線上環境是很是嚴重的錯誤,須要立馬開hotfix分支去修的,由於沒有編寫對應的業務處理方式,最嚴重的後果可能致使某個用戶扣了錢可是沒有顯示支付成功。
和系統異常同樣,這些異常因爲是Throwable異常類下的異常,因此會被返回給前端。
異常處理流程在微服務架構中可能會比直接向前端發送異常信息這個過程麻煩一些,如Service向BFF層級傳遞異常一級。
API Gateway (with Zuul) => BFF => 某服務
因爲BFF與服務之間是經過Feign鏈接,因此咱們須要本身統一一下錯誤格式成爲業務相關的格式返回給前端而不是直接將細化某個Java異常類的所有異常信息交給前端。
在這張圖中,在BFF中檢測參數是否匹配,在Service中檢測是否資源存在,若是在BFF中拋出異常,則將INVALID_PARAMETER異常返回給前端,若是在Service中拋出異常,則將SERVICE_REQUEST_ERROR返回給前端。也就是將異常作出簡單的分類:業務異常、非業務異常,非業務異常中能夠像上面分類同樣繼續分類。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
先後端統一錯誤格式,須要規定以下:
1 2 3 4 |
{ "message": "reservation details doesn't exist with id: xxx", "errorCode": "SERVICE_REQUEST_ERROR", } |