基于spring boot實(shí)現(xiàn)一個(gè)全局異常處理器
還記得前面我們的實(shí)現(xiàn)邏輯,在service層如果有校驗(yàn)失敗我們會拋出異常,而controller中我們對其并沒有處理,實(shí)際上spring boot有自己全局的錯(cuò)誤處理形式。我們可以基于spring boot提供的切面特性,來很輕松的實(shí)現(xiàn)全局異常的處理,現(xiàn)在我們約定,只要后臺邏輯處理失敗的情況,我們都將拋出異常,包括controller中的處理邏輯。
實(shí)現(xiàn)全局異常處理器
現(xiàn)在我們就來編寫全局異常處理器。
package com.xiaojuan.boot.common.web.support;
import ...
import static com.xiaojuan.boot.common.enums.BusinessError.*;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/** 維護(hù)一個(gè)錯(cuò)誤碼與http狀態(tài)碼的映射 */
private Map<Integer, HttpStatus> httpStatusMap;
@PostConstruct
private void init() {
httpStatusMap = new HashMap<>();
httpStatusMap.put(NO_LOGIN.getValue(), HttpStatus.UNAUTHORIZED);
httpStatusMap.put(HAS_NO_ROLE.getValue(), HttpStatus.FORBIDDEN);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Response<?>> handleException(BusinessException ex) {
log.error(ex.getMessage(), ex);
// 默認(rèn)服務(wù)器端錯(cuò)誤,返回500狀態(tài)碼
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
if (!StringUtils.isEmpty(ex.getErrCode()) && httpStatusMap.containsKey(ex.getErrCode())) {
status = httpStatusMap.get(ex.getErrCode());
}
return ResponseEntity.status(status).body(Response.fail(ex.getMessage(), ex.getErrCode(), ex.getData()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Response<?>> handleException(Exception ex) {
log.error(ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Response.fail("小卷生鮮電商系統(tǒng)異常:" + ex.getMessage()));
}
}代碼詳解
這里我們用了@RestControllerAdvice注解對所有的@RestController修飾的controller都進(jìn)行攔截,只要拋出了異常,就交給這個(gè)處理器來處理。
這里要說下響應(yīng)的http狀態(tài)碼,一般都返回200,某些情況下我們將返回一個(gè)可以讓前端特別處理的狀態(tài)碼,比如用戶未登錄的請求被攔截了我們可以返回401,用戶登錄了卻沒有權(quán)限的操作,我們返回403,這樣前端對這塊直接從http狀態(tài)就能識別請求結(jié)果的狀態(tài)。而對于我們業(yè)務(wù)開發(fā)來說,關(guān)注的是業(yè)務(wù)處理的錯(cuò)誤碼,在這里我們和請求http狀態(tài)碼之間做了一個(gè)映射轉(zhuǎn)換,維護(hù)在httpStatusMap成員變量中。
接下來,我們寫了兩個(gè)異常處理方法,它們都必須用@ExceptionHandler注解來指定我們要處理的異常的類型,這里我們只考慮兩種異常:我們自定義的業(yè)務(wù)異常BusinessException和最大的Exception。
對于BusinessException,我們要考慮HttpStatus的判斷邏輯,如果我們將某些業(yè)務(wù)錯(cuò)誤碼和http狀態(tài)碼做了映射,那么對于這些業(yè)務(wù)錯(cuò)誤碼我們就取映射到的http狀態(tài)碼來返回,否則我們返回服務(wù)器端錯(cuò)誤的500狀態(tài)碼。
注意處理方法最后的返回結(jié)果,我們使用的是ResponseEntity<Response<?>>,spring web模塊提供的ResponseEntity可以幫助我們按照指定的http狀態(tài)碼并解析指定格式的內(nèi)容體(Spring Boot默認(rèn)配置json形式)進(jìn)行前端響應(yīng)。而要響應(yīng)的對象就是我們之前定義的Response,只不過我們將字段errCode的類型從原來的String改成了Integer,因?yàn)闃I(yè)務(wù)錯(cuò)誤碼我們就定義為數(shù)值型,同樣的還有BusinessException類中的errCode字段類型的調(diào)整。
除了BusinessException,我們只處理最大的Exception,http狀態(tài)碼固定為500,錯(cuò)誤消息也統(tǒng)一以固定的形式開頭。
錯(cuò)誤碼枚舉類
將應(yīng)用中業(yè)務(wù)錯(cuò)誤碼我們定義在一個(gè)枚舉中進(jìn)行維護(hù):
package com.xiaojuan.boot.common.enums;
import ...
@Getter
@AllArgsConstructor
public enum BusinessError {
PARAM_INVALID("參數(shù)校驗(yàn)失敗", 10001),
RECORD_EXISTS("后臺記錄已存在", 10002),
HAS_NO_ROLE("用戶未授權(quán),不能訪問", 403),
NO_LOGIN("請先登錄再操作", 401);
private final String label;
private final Integer value;
}這里我們給出默認(rèn)的說明,一般在拋出業(yè)務(wù)異常時(shí)我們可以指定更具體的錯(cuò)誤,而不會使用這里默認(rèn)的錯(cuò)誤消息。
service層拋出異常調(diào)整
現(xiàn)在我們對service層拋出異常做下調(diào)整,一般我們只要傳入錯(cuò)誤信息來構(gòu)造BusinessException。有些時(shí)候我們還可以傳入錯(cuò)誤碼來進(jìn)一步區(qū)分這些錯(cuò)誤。


實(shí)現(xiàn)自己的Assert工具
既然我們先前定義了自己的BusinessException,并且在其中維護(hù)了業(yè)務(wù)錯(cuò)誤碼errCode,就沒必要用spring的Assert工具了,因?yàn)樗鼟伋鰜淼氖?code>IllegalStateException異常,最終被我們?nèi)之惓L幚砥饕宰畲蟮?code>Exception的處理邏輯進(jìn)行包裝響應(yīng)結(jié)果,自然“參數(shù)校驗(yàn)失敗”的錯(cuò)誤碼類型就丟了,因此這里我們基于spring對Assert的實(shí)現(xiàn),我們包裝為自己的Assert:
package com.xiaojuan.boot.util;
import ...
public class Assert {
public static void hasText(@Nullable String text, String message) {
if (!StringUtils.hasText(text)) {
throw new BusinessException(message, BusinessError.PARAM_INVALID.getValue());
}
}
}controller層拋出異常
最后我們將原來預(yù)留todo注釋的地方改為拋出異常的形式:

最后,我們再基于test.http對拋出異常的測試場景進(jìn)行測試,看是否達(dá)到預(yù)期的http狀態(tài)碼和json結(jié)構(gòu),這個(gè)大家自行測試。
到此這篇關(guān)于基于spring boot實(shí)現(xiàn)一個(gè)全局異常處理器的文章就介紹到這了,更多相關(guān)spring boot全局異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
學(xué)習(xí)Java之如何正確地跳出循環(huán)結(jié)構(gòu)
我們在利用循環(huán)執(zhí)行重復(fù)操作的過程中,存在著一個(gè)需求:如何中止,或者說提前結(jié)束一個(gè)循環(huán),所以就給大家講解一下,如何在java代碼中返回一個(gè)結(jié)果,如何結(jié)束和跳出一個(gè)循環(huán),需要的朋友可以參考下2023-05-05
SpringBoot2 task scheduler 定時(shí)任務(wù)調(diào)度器四種方式
這篇文章主要介紹了SpringBoot2 task scheduler 定時(shí)任務(wù)調(diào)度器四種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
一篇文章徹底拆解Java?HashMap擴(kuò)容機(jī)制
在Java中HashMap是一個(gè)非常常用的數(shù)據(jù)結(jié)構(gòu),基于哈希表實(shí)現(xiàn),它通過鍵值對的形式存儲數(shù)據(jù),這篇文章主要介紹了Java HashMap擴(kuò)容機(jī)制的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-04-04
Java之jdbc連接mysql數(shù)據(jù)庫的方法步驟詳解
這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫的方法步驟詳解,JCBC技術(shù)是java開發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫,這是一個(gè)比較簡單的方法,有興趣的可以了解一下2020-07-07

