1. 程式人生 > >SpringBoot系列: Spring專案異常處理最佳實踐

SpringBoot系列: Spring專案異常處理最佳實踐

===================================
自定義異常類
===================================
稍具規模的專案, 一般都要自定義一組異常類, 這樣做的好處是:
1. 可以充分利用異常的中斷特性, 簡化程式碼的邏輯控制.
2. 在自定義的異常類, 可以設定 BusinessErrorCode 和 error message, 有了統一的 BusinessErrorCode, 排查和聯調溝通就更容易了.

Java 異常的 Root 是 Throwable, 其下有 Error 和 Exception. Error 是 JVM 級的致命錯誤, 應用系統內部一般不用關心這類錯誤. Exception 是異常的父類, 其下分為兩類, 一類是 Runtime Exception, 一類是 Checked Exception. Checked Exception 是那些編譯器能檢查到的異常, 如果一個函式中丟擲了這類異常, 我們要麼 catch 它, 要麼在函式簽名上繼續丟擲去, 否則程式將不能編譯通過.

Runtime Exception 有, 包括 RuntimeException 和它的子類. 比如 ArrayIndexOutOfBoundsException/ClassCastException/被 0 除等.
Checked Exception 有: Exception 類和所有非 RuntimeException 類的異常都屬於 checked exception, 比如 IoException 等.

自定義異常類的基類如何選擇?
自定義異常類應該繼承自 RuntimeException 類, 原因有:
1. Spring 事務控制只支援 RuntimeException 類的異常.
2. 如果我們想要在函式加 throw Exception 簽名, Java 語言已經提供了非常豐富的選擇, 總能找到一個很合適的類, 而不需要再自定義一個.


===================================
自定義類的最佳實踐:
===================================
1. 先定義一個基類 BusinessException, 繼承自 RuntimeException.
2. 定義一套 BusinessErrorCode 列舉型別, 包含 BusinessErrorCode/HttpStatus/BusinessErrorMessage, 這裡的 BusinessErrorCode 不同於 HttpStatus, 它是業務上的錯誤程式碼 (int 型).
2. 在 BusinessException 基類上, 加上繫結 BusinessErrorCode 列舉型別的機制.
3. 基於 BusinessException 定義一組子類, 比如 UserNotLoginException/PermissionForbiddenException/DataNotFoundException 等等, 並將這些子類加入到一個 BusinessExceptionEnum 列舉中, 方便使用.


===================================
Spring 專案資料驗證最佳實踐
===================================
1. 針對 UI 輸入檢查, 如果 js 前端檢查有困難, 可以在 Controller 層使用 Pojo validation 手段做檢查, 然後前端使用 ajax 拿到校驗結果. 檢查過程沒有觸發 UI 完整渲染, 使用者體驗會很好.
2. Controller 層使用 Pojo validation 進行檢查.
3. Service 以下層, 使用 org.springframework.util.Assert 進行資料驗證, 比如 Assert.notNull(user, "user is not null.");


===================================
Spring 日誌和異常處理的最佳實踐
===================================
異常處理的基本思路是: 早丟擲, 晚捕獲. 日誌輸出的基本思路是, 詳盡但不冗餘.
落實到具體的專案中, 在不同分層中, 應採用不同的規則, 一般的分層有: DAO -> Service -> Controller -> 統一異常 Controller 層.

1. DAO 層:
   (1) 儘量不 catch 任何異常, 該向上拋就拋.
   (2) 不用記錄 log 日誌, 或者僅使用 logger.debug() 記錄
2. Service 層的做法:
   (1) @Transactional 註解應該加在 Service 層上.
   (2) 對於一些關鍵問題, 比如 Checked Exception 或者資料的問題, 應該及時 throw new BusinessException 異常, 以確保事務完整.
   (3) throw new BusinessException 時的日誌: 如果是單體應用, 為了避免日誌重複, 不需要 log 日誌輸出, 如果是微服務應用, 應加 logger.warning 日誌.
   (4) Service 層一般的日誌級別, 對於單體應用, 應該用 logger.debug() 記日誌; 對於微服務應用, 應該是 logger.info().
   (5) Service 層函式的返回值應該是 Optional 型別, 方便 Controller 做 null 判斷.
3. Controller 層:
   (1) Controller 層負責組裝 Service, 在關鍵步驟上應該加日誌輸出 (info 級別)
   (2) Controller 層不應再主動 throw 異常.
4. 統一異常 Controller 層:
   (1) 通過 json 或 UI 返回詳細的報錯資訊, 包括 HttpStatus 和詳盡的 BusinessErrorCode/BusinessErrorMessage 以及 DetailErrorMessage(來源於 exception.getMessage()), 甚至包括 exception 的 stack trace.
   (2) 對於 Exception 類的異常, 說明這是我們預料之外的報錯, 應該使用 logger.error() 級別記錄;
   (3) 對於 BusinessException 和子類的異常, 則說明我們的程式已經預料到了, 事物該回滾也已經回滾了, 所以應該以 logger.warn() 或 logger.info() 記錄日誌.
   (4) 對於 Spring Boot 預設的 /error 進行定製, 增加一些"系統主頁"和"返回"的連結, 改善使用者體驗.


===================================
參考:
===================================
https://blog.csdn.net/aiyaya_/article/details/78725755
https://blog.csdn.net/aiyaya_/article/details/78989226
https://blog.qinchuan.io/experience/2018/10/11/spring-boot-restful-api-error-handling-in-practice.html
https://zhuanlan.zhihu.com/p/38114882
http://tengj.top/2018/05/16/springboot13/