Spring Cloud中異常處理的套路

異常在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的時候排錯的,固然這一點確實是異常機制很是大的一個好處,但異常機制包含着更多的意義。數據庫

  • 關注業務實現。異常機制使得業務代碼與異常處理代碼能夠分開,你能夠將一些你調用數據庫操做的代碼寫在一個方法裏而只須要在方法上加上throw DB相關的異常。至於如何處理它,你能夠在調用該方法的時候處理或者甚至選擇不處理,而不是直接在該方法內部添加上if判斷若是數據庫操做錯誤該如何辦,這樣業務代碼會很是混亂。
  • 統一異常處理。與上一點有所聯繫。我當前所在項目的實踐是,自定義業務類異常,在Controller或Service中拋出,讓後使用Spring提供的異常接口統一處理咱們本身在內部拋出的異常。這樣一個異常處理架構就很是明瞭。
  • 程序的健壯性。若是沒有異常機制,那麼來了個對空對象的某方法調用怎麼辦呢?直接讓程序掛掉?這使人沒法接受,固然,咱們本身平時寫的一些小的東西確實是這樣,沒有處理它,讓後程序掛了。但在web框架中,能夠利用異常處理機制捕獲該異常並將錯誤信息傳遞給咱們而後繼續處理下個請求。因此異常對於健壯性是很是有幫助的。

異常處理(又稱爲錯誤處理)功能提供了處理程序運行時出現的任何意外或異常狀況的方法。異常處理使用 try、catch 和 finally 關鍵字來嘗試可能未成功的操做,處理失敗,以及在過後清理資源。後端

先分類(dian cai)吧

我把異常根據意義成三種:業務、系統、代碼異常,不一樣的異常採用不一樣的處理方式。緩存

業務異常

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大型互聯網技術的視頻免費分享給你們。

先後端統一錯誤格式,須要規定以下:

  • 返回格式:JSON
  • 返回請求狀態碼:根據不一樣請求對應的狀態碼意義返回
  • 返回具體格式以下
1
2
3
4
{
   "message": "reservation details doesn't exist with id: xxx",
   "errorCode": "SERVICE_REQUEST_ERROR",
}
相關文章
相關標籤/搜索