===================================
自定義異常類
===================================
稍具規模的項目, 一般都要自定義一組異常類, 這樣做的好處是:
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 層使用 validation 進行檢查, 可以在視圖函數的形參上檢查, 或者在視圖函數內部檢查.
3. Service 層, 使用 org.springframework.util.Assert 進行數據驗證, 比如 Assert.notNull(user, "user is not null.");
4. DAO 層, 不做任何數據驗證, 因為所有數據問題應該在Service層或Controller層做個驗證.
===================================
Spring 各層封裝的手法
===================================
1. DAO 層, 函數的形參最好以 DO 類做參數, 而不是傳入很多個字段參數. 這樣的好處是, 避免Table增刪字段, DAO層函數定義也要跟着修改, 上層的調用代碼也要修改.
2. DAO 層, insert 和 update 要獨立為兩個函數. 到底是新增還是更新, 應由 Service 層進行邏輯控制.
3. Service 層函數的形參, 到底是使用 DO 類, 還是簡單的屬性清單, 看具體情況吧.
===================================
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 日志輸出.
(4) Service 層一般的日志級別, 應該用 logger.debug() 記日志.
(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/