一文帶你搞懂java中的統(tǒng)一異常處理
無論你的代碼寫得多么無懈可擊,也不可能完全避免意外發(fā)生。而我們能做的是,在意外發(fā)生以后將影響降到最低,使用更加溫和的方式將問題反饋出來,讓程序不至于直接崩潰。要達(dá)到這個(gè)目的,我們需要進(jìn)行異常處理。在進(jìn)行異常處理之前,我們需要對(duì)Java中的異常有一個(gè)簡(jiǎn)單的了解。
一 異常體系
簡(jiǎn)單來說,異常就是程序運(yùn)行時(shí)遇到的我們預(yù)想之外的情況,而這些意外情況可以按照其嚴(yán)重性及我們對(duì)意外的處理能力分成不同的類型。Java異常體系如圖所示。

Java中有非常完整的異常機(jī)制,所有的異常類型都有一個(gè)共同的“祖先”— —Throwable。由圖可以看出,Throwable下面有兩個(gè)分支:一個(gè)是Error,另一個(gè)是Exception。
Error:Error 屬 于 非 常 嚴(yán) 重 的 系 統(tǒng) 錯(cuò) 誤 , 如 OutOfMemoryError 和StackOverflowError,類似于現(xiàn)實(shí)世界中的地震、臺(tái)風(fēng)等不可抗力。一旦這類問題發(fā)生,我們基本上就束手無策了,能做的通常是預(yù)防和事后補(bǔ)救。
Exception:Exception 屬 于 我 們 能 夠 處 理 的 范 疇 , 如 NullPointerException 和FileNotFoundException 。 Exception 還 可 以 進(jìn) 一 步 細(xì) 分 為 受 檢 異 常(checked)和非受檢異常(unchecked)。
checked異常:checked異常指的是需要進(jìn)行顯式處理(try或throws)的異常,否則會(huì)發(fā)生編譯錯(cuò)誤,IDE中會(huì)有錯(cuò)誤提示。Java中的checked異常是一個(gè)龐大的家族,除RuntimeException和Error以外的類都屬于checked異常。
checked異常比Error更可控一些,雖然我們不能避免這類異常的發(fā)生,但是因?yàn)榫幾g器的強(qiáng)制要求,我們必須對(duì)這類異常進(jìn)行顯式處理,所以即使發(fā)生了checked異常,程序也不會(huì)因此崩潰。
好比下雨導(dǎo)致原定的室外活動(dòng)受到影響,但我們可以選擇雨停了再舉行,或者找一個(gè)合適的室內(nèi)場(chǎng)所來舉行。類似地,當(dāng)程序讀取一個(gè)文件時(shí),如果發(fā)現(xiàn)文件不存在,那么我們可以等一下再試(可能文件還沒生成),或者直接返回一個(gè)默認(rèn)的內(nèi)容。
unchecked異常
unchecked異常是最容易掌控的,甚至可以通過良好的編碼習(xí)慣來避免(沒錯(cuò) ,就是避免) , 比 如 , NullPointerException 、IndexOutOtBoundsException等。好比我們可以通過培養(yǎng)良好的習(xí)慣來避免生活中的很多不必要的麻煩,例如,我們可以提前出門,以避免因?yàn)槎萝嚩s不上飛機(jī)。同樣地,在使用一個(gè)對(duì)象前,先判斷該對(duì)象是否為null,就可以避免NullPointerException的發(fā)生。因?yàn)镋rror并不是我們能夠處理的,所以一般我們所說的異常指的是Exception,unchecked異常指的是RuntimeException及其子類,checked異常指的是Exception下的其他子類。
二 全局異常處理
現(xiàn)在我們從理論層面對(duì)異常有了很全面的了解,接下來動(dòng)手實(shí)踐一下全局異常處理。
全局異常捕獲
在Spring Boot中進(jìn)行全局異常捕獲非常簡(jiǎn)單,其核心就是一個(gè)注解— —@ ControllerAdvice/ @ RestControllerAdvice 。 兩 者 的 區(qū) 別 類 似 于 @Controller與@RestControllerAdvice,這里就不再贅述了。示例代碼如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<Boolean> globalException(Exception e) {
Result<Boolean> result = new Result<>();
result.setCode(MessageEnum.ERROR.getCode());
result.setMessage(e.getMessage() == null ? MessageEnum.ERROR.getMessage() : e.getMessage());
log.error(e.getMessage(),e);
return result;
}
@ExceptionHandler(ApiException.class)
public Result<Boolean> apiException(ApiException e) {
Result<Boolean> result = new Result<>();
result.setCode(e.getCode());
result.setMessage(e.getMessage());
log.error(e.getMessage(),e);
return result;
}
}
GlobalExceptionHandler的代碼很簡(jiǎn)單,其核心邏輯就是捕獲異常,然后將錯(cuò)誤信息封裝,最后以JSON格式返回給前端。這里我們只是粗略地對(duì)APIException和Exception進(jìn)行分別捕獲,在實(shí)際應(yīng)用中可以根據(jù)自己的情況定制更細(xì)化的方案,也就是多加上幾個(gè)對(duì)應(yīng)不同類型異常的方法而已。
整齊劃一的結(jié)構(gòu)
為了讓我們的代碼更加優(yōu)雅,我們需要添加兩個(gè)輔助類——MessageEnum和Result。
MessageEnum類
MessageEnum類封裝了錯(cuò)誤信息和錯(cuò)誤代碼,并將它們集中起來統(tǒng)一管理,以更好地應(yīng)對(duì)將來的變化。假如程序中有100處都使用了“操作成功!”這句話作為message,我們就要寫100遍。而如果有一天,產(chǎn)品經(jīng)理說“4個(gè)字太多了”,讓我們改成“成功!”,就需要將那100個(gè)“操作成功!”修改成“成功!”,太令人崩潰了。示例代碼如下:
import lombok.Getter;
@Getter
public enum MessageEnum {
ERROR(500, "系統(tǒng)錯(cuò)誤"),
SUCCESS(0, "操作成功!"),
;
private final Integer code;
private final String message;
MessageEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
集中管理的好處顯而易見,如果產(chǎn)品經(jīng)理提出上面的需求,那么我們只需要在這個(gè)類中去掉兩個(gè)字即可。
Result類
Result類的作用是讓接口返回值變得更加優(yōu)雅。無論什么接口返回值都是“三大件”— —code、message和data(業(yè)務(wù)數(shù)據(jù))。示例代碼如下:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
return new Result<>(MessageEnum.SUCCESS.getCode(), MessageEnum.SUCCESS.getMessage(), data);
}
public static <T> Result<T> error() {
return error(MessageEnum.ERROR);
}
public static <T> Result<T> error(MessageEnum messageEnum) {
return new Result<>(messageEnum.getCode(),messageEnum.getMessage(),null);
}
public static <T> Result<T> error(String message) {
return error(message, MessageEnum.ERROR.getCode());
}
protected static <T> Result<T> error(String message,Integer code) {
return new Result<>(code,message,null);
}
}
效果
統(tǒng)一結(jié)構(gòu)
先將原來的接口改造成統(tǒng)一結(jié)構(gòu)的返回值,即使用Result類封裝返回值:

改造前的返回值:

改造后的返回值:

經(jīng)過對(duì)比可知,改造前,接口返回值的外層結(jié)構(gòu)是隨著接口不同而不同的,而改造后,不管什么接口,返回值的外層永遠(yuǎn)都是固定的3個(gè)字段:code、message和data。
統(tǒng)一異常處理
做好上面的準(zhǔn)備工作后,我們寫一個(gè)拋出異常的接口:
import com.shuijing.boot.exception.common.ApiException;
import com.shuijing.boot.exception.common.MessageEnum;
import com.shuijing.boot.exception.common.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/exception")
@Api(value = "異常",tags = "異常")
public class ExceptionController {
@GetMapping("/runtimeexception")
public Result<Boolean> runtimeException() {
throw new RuntimeException();
}
}
沒有全局異常處理的錯(cuò)誤返回值:

開啟全局異常處理的返回值:

開啟全局異常處理以后,即使出現(xiàn)異常,接口返回值的結(jié)構(gòu)也是穩(wěn)定的,這樣對(duì)于調(diào)用方(前端、移動(dòng)端、其他系統(tǒng))來說是更加友好的。我們按照約定好的結(jié)構(gòu)處理數(shù)據(jù),可以大大地降低接口處理的復(fù)雜度
三 異常與意外
程序中的異常就像生活中的意外,有些我們無能為力,有些我們可以制定處理措施,有些則可以避免。人們總說“意外和明天,你永遠(yuǎn)不知道哪個(gè)會(huì)先來”,雖然我們無法左右誰先來,但我們能做的是:把握住自己能夠掌控的,盡力改善我們能影響的,坦然接受我們無能為力的。
以上就是一文帶你搞懂java中的統(tǒng)一異常處理的詳細(xì)內(nèi)容,更多關(guān)于java統(tǒng)一異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis入門_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了mybatis入門的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
基于java實(shí)現(xiàn)租車管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了基于java實(shí)現(xiàn)租車管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12
詳解SpringBoot實(shí)現(xiàn)ApplicationEvent事件的監(jiān)聽與發(fā)布
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)ApplicationEvent事件的監(jiān)聽與發(fā)布,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03

