SpringBoot優(yōu)雅地實(shí)現(xiàn)全局異常處理的方法詳解
前言
在前一節(jié)的學(xué)習(xí)中,慕歌帶大家使用了全局結(jié)果集返回,通過(guò)使用全局結(jié)果集配置,優(yōu)雅的返回后端數(shù)據(jù),為前端的數(shù)據(jù)拿取提供了非常好的參考。同時(shí)通過(guò)不同的狀態(tài)碼返回,我們能夠清晰的了解報(bào)錯(cuò)的位置,排除錯(cuò)誤。如果大家有需要,可以使用我提供的的同一結(jié)果集以及狀態(tài)碼,并且可以使用全局異常攔截,實(shí)現(xiàn)異常的標(biāo)準(zhǔn)返回。接下來(lái),我們一起來(lái)了解如何使用全局異常處理吧!
異常工具
先定義一個(gè)合適 的異常處理類(lèi),在之后的異常都會(huì)以這種格式返回前端,前端根據(jù)我們的異常進(jìn)行自己的返回,以一種優(yōu)雅的方式呈現(xiàn)錯(cuò)誤,優(yōu)化用戶(hù)體驗(yàn)。
異常結(jié)果集:
/**
* 返回結(jié)果封裝
*/
@Data
public class ResultVo {
// 狀態(tài)碼
private int code;
// 狀態(tài)信息
private String msg;
// 返回對(duì)象
private Object data;
// 手動(dòng)設(shè)置返回vo
public ResultVo(int code, String msg) {
this.code = code;
this.msg = msg;
}
// 手動(dòng)設(shè)置返回vo
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// 只返回狀態(tài)碼
public ResultVo(StatusCode statusCode) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
// 默認(rèn)返回成功狀態(tài)碼,數(shù)據(jù)對(duì)象
public ResultVo(Object data) {
this.code = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMsg();
this.data = data;
}
// 返回指定狀態(tài)碼,數(shù)據(jù)對(duì)象
public ResultVo(StatusCode statusCode, Object data) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = data;
}
public ResultVo(StatusCode statusCode,String msg, Object data) {
this.code = statusCode.getCode();
this.msg = msg;
this.data = data;
}
}
異常狀態(tài)碼,通過(guò)返回的狀態(tài)碼,以及狀態(tài)信息,能夠高效反映錯(cuò)誤,并且可以全局統(tǒng)一管理,方便快捷:
@Getter
public enum ExceptionCode implements StatusCode {
// 系統(tǒng)級(jí)別錯(cuò)誤碼
ERROR(-1, "操作異常"),
NOT_LOGIN(102, "請(qǐng)先登錄!"),
NO_Role(102,"無(wú)權(quán)限"),
NO_PERMISSION(102,"無(wú)權(quán)限"),
OUT_TIME(102,"登錄信息過(guò)期"),
DISABLE_ACCOUNT(102,"帳號(hào)已被禁用!"),
EMAIL_DISABLE_LOGIN(102,"該郵箱賬號(hào)已被管理員禁止登錄!"),
IP_REPEAT_SUBMIT(102,"訪問(wèn)次數(shù)過(guò)多,請(qǐng)稍后重試"),
ERROR_DEFAULT(105,"系統(tǒng)繁忙,請(qǐng)稍后重試");
//異常碼
private int code;
//異常信息
private String msg;
//自定義方法
ExceptionCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}當(dāng)我們對(duì)異常通過(guò)以上工具類(lèi)進(jìn)行封裝之后,所有異常將以一種固定的格式返回,不會(huì)導(dǎo)致錯(cuò)亂:
{3 items
"code":105
"msg":"系統(tǒng)繁忙,請(qǐng)稍后重試"
"data":NULL
}異常處理
在spring boot中需要使用異常攔截器,攔截全局的異常,不直接將異常返回,而是在我們進(jìn)行處理之后,以一種清晰可讀的方式返回。并且前端能夠清晰解讀我們的異常,呈現(xiàn)給用戶(hù)。
//捕獲校驗(yàn)器異常
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class})
public ResultVo ValidExceptionHandler(BindException e) {
// 從異常對(duì)象中拿到ObjectError對(duì)象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR.getCode(),objectError.getDefaultMessage());
}
}對(duì)特定異常進(jìn)行攔截,并包裝異常:
/**
* 對(duì)返回結(jié)果進(jìn)行包裝
*/
@RestControllerAdvice(basePackages = {"channel.cert"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response是ResultVo類(lèi)型,或者注釋了NotControllerResponseAdvice都不進(jìn)行包裝
return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String類(lèi)型不能直接包裝
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 將數(shù)據(jù)包裝在ResultVo里后轉(zhuǎn)換為json串進(jìn)行返回
return objectMapper.writeValueAsString(new ResultVo(data));
} catch (JsonProcessingException e) {
throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// 否則直接包裝成ResultVo返回
return new ResultVo(data);
}
}異常捕捉
自定義異常:
@Getter
public class APIException extends RuntimeException {
private int code;
private String msg;
//自定義異枚舉
public APIException(StatusCode statusCode){
super(statusCode.getMsg());
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
// 手動(dòng)設(shè)置異常
public APIException(StatusCode statusCode, String message) {
// message用于用戶(hù)設(shè)置拋出錯(cuò)誤詳情,例如:當(dāng)前價(jià)格-5,小于0
super(message);
// 狀態(tài)碼
this.code = statusCode.getCode();
// 狀態(tài)碼配套的msg
this.msg = statusCode.getMsg();
}
// 默認(rèn)異常使用APP_ERROR狀態(tài)碼
public APIException(String errorMsg) {
super(errorMsg);
this.code = ExceptionCode.ERROR_DEFAULT.getCode();
this.msg = ExceptionCode.ERROR_DEFAULT.getMsg();
}
//自定義參數(shù) 錯(cuò)誤碼 錯(cuò)誤信息
public APIException(int errorCode, String errorMsg) {
super(errorMsg);
this.code = errorCode;
this.msg = ExceptionCode.ERROR_DEFAULT.getMsg();
}
//自定義參數(shù) 錯(cuò)誤碼 錯(cuò)誤信息 異常
public APIException(int errorCode, String errorMsg, Throwable cause) {
super(errorMsg);
this.code = errorCode;
this.msg = errorMsg;
}
}對(duì)自定義異常進(jìn)行捕獲,通過(guò)定義好的異常的結(jié)果集返回。
/**
* 全局異常處理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
// Assert業(yè)務(wù)異常
@ExceptionHandler(IllegalArgumentException.class)
public ResultVo AssertExceptionHandler(IllegalArgumentException ex) {
log.error( " msg : " + ex.getMessage(), ex);
if(StringUtils.isBlank(ex.getLocalizedMessage())){
return new ResultVo(ExceptionCode.ERROR_DEFAULT);
}
return new ResultVo(ex.getMessage());
}
// 登錄失效異常
@ExceptionHandler(SaTokenException.class)
public ResultVo LoginOutExceptionHandler(SaTokenException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.OUT_TIME);
}
// 登錄異常
@ExceptionHandler(NotLoginException.class)
public ResultVo NotLoginExceptionHandler(NotLoginException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NOT_LOGIN);
}
// 權(quán)限異常
@ExceptionHandler(NotPermissionException.class)
public ResultVo NotPermissionExceptionHandler(NotPermissionException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NO_PERMISSION);
}
//角色異常
@ExceptionHandler(NotRoleException.class)
public ResultVo NotRoleExceptionHandler(NotRoleException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NO_Role);
}
//處理自定義異常
@ExceptionHandler(APIException.class)
public ResultVo APIExceptionHandler(APIException e) {
log.error(e.getMessage(), e);
return new ResultVo(e.getCode(), e.getMsg());
}
//處理運(yùn)行異常
@ExceptionHandler(RuntimeException.class)
public ResultVo RuntimeExceptionHandler(RuntimeException e) {
log.error(e.getMessage(), e);
return new ResultVo(ExceptionCode.ERROR_DEFAULT);
}
}通過(guò)以上自定義,我們就能在項(xiàng)目中,使用自定義異常,在我們認(rèn)為可能的報(bào)錯(cuò)處插入,當(dāng)發(fā)生錯(cuò)誤時(shí),我們更快定位是之前記錄的錯(cuò)誤點(diǎn)導(dǎo)致。
//查詢(xún)數(shù)字證書(shū)
@Override
public GroupUser searchCert(String certCode) {
try {
//查詢(xún)證書(shū)編號(hào)
GroupUser user = queryCert(certCode);
if(ObjectUtil.isNotNull(user)){
return user;
}
}catch (Exception e){
throw new APIException("區(qū)塊鏈調(diào)用失敗"+e);
}
return null;
}以上就是SpringBoot優(yōu)雅地實(shí)現(xiàn)異常處理的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot全局異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot導(dǎo)入導(dǎo)出數(shù)據(jù)實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringBoot導(dǎo)入導(dǎo)出數(shù)據(jù)實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12
通過(guò)spring注解開(kāi)發(fā),簡(jiǎn)單測(cè)試單例和多例區(qū)別
這篇文章主要介紹了通過(guò)spring注解開(kāi)發(fā),簡(jiǎn)單測(cè)試單例和多例區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
MyBatis-Plus雪花算法實(shí)現(xiàn)源碼解讀
雪花算法是一種用于生成唯一標(biāo)識(shí)符(ID)的分布式算法,雪花算法的設(shè)計(jì)目標(biāo)是在分布式系統(tǒng)中生成全局唯一的ID,同時(shí)保證ID的有序性和趨勢(shì)遞增,這篇文章主要介紹了MyBatis-Plus雪花算法實(shí)現(xiàn)源碼解析,需要的朋友可以參考下2023-12-12
java實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07
Java利用Dijkstra算法求解拓?fù)潢P(guān)系最短路徑
迪杰斯特拉算法(Dijkstra)是由荷蘭計(jì)算機(jī)科學(xué)迪家迪杰斯特拉于1959年提出的,因此又叫狄克斯特拉算法。本文將利用迪克斯特拉(Dijkstra)算法求拓?fù)潢P(guān)系最短路徑,感興趣的可以了解一下2022-07-07
一步步教你JAVA如何優(yōu)化Elastic?Search
想要榨干Java操作Elasticsearch的所有性能潛力?本指南將一步步教你如何優(yōu)化Java與Elasticsearch的交互!從此,提升ES查詢(xún)速度、降低資源消耗不再是難題,趕快一起來(lái)探索Java?Elasticsearch優(yōu)化的秘訣吧!2024-01-01
Java8深入學(xué)習(xí)系列(二)函數(shù)式編程
函數(shù)式編程,這個(gè)詞語(yǔ)由兩個(gè)名詞構(gòu)成,函數(shù),編程。編程這個(gè)詞我就不用解釋了,大家都是做這個(gè)的。函數(shù),其實(shí)單獨(dú)抽離出來(lái)這個(gè)詞語(yǔ),也并不陌生,那二者組合后的到底是什么呢,下面這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式編程的相關(guān)資料,需要的朋友可以參考下。2017-08-08
SpringMVC之RequestContextHolder詳細(xì)解析
這篇文章主要介紹了SpringMVC之RequestContextHolder詳細(xì)解析,正常來(lái)說(shuō)在service層是沒(méi)有request的,然而直接從controlller傳過(guò)來(lái)的話(huà)解決方法太粗暴,后來(lái)發(fā)現(xiàn)了SpringMVC提供的RequestContextHolder,需要的朋友可以參考下2023-11-11
java中LinkedBlockingQueue與ArrayBlockingQueue的異同
這篇文章主要介紹了java中LinkedBlockingQueue與ArrayBlockingQueue的異同,需要的朋友可以參考下2016-08-08

