看看人家SpringBoot的全局異常處理多么優(yōu)雅...

作者:虛無境
https://www.cnblogs.com/xuwujing/p/10933082.html
SpringBoot全局異常準(zhǔn)備
說明:如果想直接獲取工程那么可以直接跳到底部,通過鏈接下載工程代碼。
開發(fā)準(zhǔn)備
環(huán)境要求JDK:1.8SpringBoot:1.5.17.RELEASE
首先還是Maven的相關(guān)依賴:
??
????????UTF-8
????????1.8
????????1.8
????????1.8
????
????
????????org.springframework.boot
????????spring-boot-starter-parent
????????1.5.17.RELEASE
????????
????
????
????????
????????
????????????org.springframework.boot
????????????spring-boot-starter-web
????????
????????
????????
????????????org.springframework.boot
????????????spring-boot-starter-test
????????????test
????????
????????
????????????com.alibaba
????????????fastjson
????????????1.2.41
????????
????
????
配置文件這塊基本不需要更改,全局異常的處理只需在代碼中實現(xiàn)即可。
代碼編寫
SpringBoot的項目已經(jīng)對有一定的異常處理了,但是對于我們開發(fā)者而言可能就不太合適了,因此我們需要對這些異常進(jìn)行統(tǒng)一的捕獲并處理。SpringBoot中有一個ControllerAdvice的注解,使用該注解表示開啟了全局異常的捕獲,我們只需在自定義一個方法使用ExceptionHandler注解然后定義捕獲異常的類型即可對這些捕獲的異常進(jìn)行統(tǒng)一的處理。
我們根據(jù)下面的這個示例來看該注解是如何使用吧。
示例代碼:
@ControllerAdvice
public?class?MyExceptionHandler?{
????@ExceptionHandler(value?=Exception.class)
?public?String?exceptionHandler(Exception?e){
??System.out.println("未知異常!原因是:"+e);
????????return?e.getMessage();
????}
}
上述的示例中,我們對捕獲的異常進(jìn)行簡單的二次處理,返回異常的信息,雖然這種能夠讓我們知道異常的原因,但是在很多的情況下來說,可能還是不夠人性化,不符合我們的要求。那么我們這里可以通過自定義的異常類以及枚舉類來實現(xiàn)我們想要的那種數(shù)據(jù)吧。
自定義基礎(chǔ)接口類
首先定義一個基礎(chǔ)的接口類,自定義的錯誤描述枚舉類需實現(xiàn)該接口。代碼如下:
public?interface?BaseErrorInfoInterface?{
????/**?錯誤碼*/
??String?getResultCode();
?
?/**?錯誤描述*/
??String?getResultMsg();
}
自定義枚舉類
然后我們這里在自定義一個枚舉類,并實現(xiàn)該接口。代碼如下:
public?enum?CommonEnum?implements?BaseErrorInfoInterface?{
?//?數(shù)據(jù)操作錯誤定義
?SUCCESS("200",?"成功!"),?
?BODY_NOT_MATCH("400","請求的數(shù)據(jù)格式不符!"),
?SIGNATURE_NOT_MATCH("401","請求的數(shù)字簽名不匹配!"),
?NOT_FOUND("404",?"未找到該資源!"),?
?INTERNAL_SERVER_ERROR("500",?"服務(wù)器內(nèi)部錯誤!"),
?SERVER_BUSY("503","服務(wù)器正忙,請稍后再試!")
?;
?/**?錯誤碼?*/
?private?String?resultCode;
?/**?錯誤描述?*/
?private?String?resultMsg;
?CommonEnum(String?resultCode,?String?resultMsg)?{
??this.resultCode?=?resultCode;
??this.resultMsg?=?resultMsg;
?}
?@Override
?public?String?getResultCode()?{
??return?resultCode;
?}
?@Override
?public?String?getResultMsg()?{
??return?resultMsg;
?}
}
自定義異常類
然后我們在來自定義一個異常類,用于處理我們發(fā)生的業(yè)務(wù)異常。代碼如下:
public?class?BizException?extends?RuntimeException?{
?private?static?final?long?serialVersionUID?=?1L;
?/**
??*?錯誤碼
??*/
?protected?String?errorCode;
?/**
??*?錯誤信息
??*/
?protected?String?errorMsg;
?public?BizException()?{
??super();
?}
?public?BizException(BaseErrorInfoInterface?errorInfoInterface)?{
??super(errorInfoInterface.getResultCode());
??this.errorCode?=?errorInfoInterface.getResultCode();
??this.errorMsg?=?errorInfoInterface.getResultMsg();
?}
?
?public?BizException(BaseErrorInfoInterface?errorInfoInterface,?Throwable?cause)?{
??super(errorInfoInterface.getResultCode(),?cause);
??this.errorCode?=?errorInfoInterface.getResultCode();
??this.errorMsg?=?errorInfoInterface.getResultMsg();
?}
?
?public?BizException(String?errorMsg)?{
??super(errorMsg);
??this.errorMsg?=?errorMsg;
?}
?
?public?BizException(String?errorCode,?String?errorMsg)?{
??super(errorCode);
??this.errorCode?=?errorCode;
??this.errorMsg?=?errorMsg;
?}
?public?BizException(String?errorCode,?String?errorMsg,?Throwable?cause)?{
??super(errorCode,?cause);
??this.errorCode?=?errorCode;
??this.errorMsg?=?errorMsg;
?}
?
?public?String?getErrorCode()?{
??return?errorCode;
?}
?public?void?setErrorCode(String?errorCode)?{
??this.errorCode?=?errorCode;
?}
?public?String?getErrorMsg()?{
??return?errorMsg;
?}
?public?void?setErrorMsg(String?errorMsg)?{
??this.errorMsg?=?errorMsg;
?}
?public?String?getMessage()?{
??return?errorMsg;
?}
?@Override
?public?Throwable?fillInStackTrace()?{
??return?this;
?}
}
自定義數(shù)據(jù)格式
順便這里我們定義一下數(shù)據(jù)的傳輸格式。代碼如下:
public?class?ResultBody?{
?/**
??*?響應(yīng)代碼
??*/
?private?String?code;
?/**
??*?響應(yīng)消息
??*/
?private?String?message;
?/**
??*?響應(yīng)結(jié)果
??*/
?private?Object?result;
?public?ResultBody()?{
?}
?public?ResultBody(BaseErrorInfoInterface?errorInfo)?{
??this.code?=?errorInfo.getResultCode();
??this.message?=?errorInfo.getResultMsg();
?}
?public?String?getCode()?{
??return?code;
?}
?public?void?setCode(String?code)?{
??this.code?=?code;
?}
?public?String?getMessage()?{
??return?message;
?}
?public?void?setMessage(String?message)?{
??this.message?=?message;
?}
?public?Object?getResult()?{
??return?result;
?}
?public?void?setResult(Object?result)?{
??this.result?=?result;
?}
?/**
??*?成功
??*?
??*?@return
??*/
?public?static?ResultBody?success()?{
??return?success(null);
?}
?/**
??*?成功
??*?@param?data
??*?@return
??*/
?public?static?ResultBody?success(Object?data)?{
??ResultBody?rb?=?new?ResultBody();
??rb.setCode(CommonEnum.SUCCESS.getResultCode());
??rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
??rb.setResult(data);
??return?rb;
?}
?/**
??*?失敗
??*/
?public?static?ResultBody?error(BaseErrorInfoInterface?errorInfo)?{
??ResultBody?rb?=?new?ResultBody();
??rb.setCode(errorInfo.getResultCode());
??rb.setMessage(errorInfo.getResultMsg());
??rb.setResult(null);
??return?rb;
?}
?/**
??*?失敗
??*/
?public?static?ResultBody?error(String?code,?String?message)?{
??ResultBody?rb?=?new?ResultBody();
??rb.setCode(code);
??rb.setMessage(message);
??rb.setResult(null);
??return?rb;
?}
?/**
??*?失敗
??*/
?public?static?ResultBody?error(?String?message)?{
??ResultBody?rb?=?new?ResultBody();
??rb.setCode("-1");
??rb.setMessage(message);
??rb.setResult(null);
??return?rb;
?}
?@Override
?public?String?toString()?{
??return?JSONObject.toJSONString(this);
?}
}
自定義全局異常處理類
最后我們在來編寫一個自定義全局異常處理的類。代碼如下:
@ControllerAdvice
public?class?GlobalExceptionHandler?{
?private?static?final?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);
?
?/**
??*?處理自定義的業(yè)務(wù)異常
??*?@param?req
??*?@param?e
??*?@return
??*/
????@ExceptionHandler(value?=?BizException.class)??
????@ResponseBody??
?public??ResultBody?bizExceptionHandler(HttpServletRequest?req,?BizException?e){
?????logger.error("發(fā)生業(yè)務(wù)異常!原因是:{}",e.getErrorMsg());
?????return?ResultBody.error(e.getErrorCode(),e.getErrorMsg());
????}
?/**
??*?處理空指針的異常
??*?@param?req
??*?@param?e
??*?@return
??*/
?@ExceptionHandler(value?=NullPointerException.class)
?@ResponseBody
?public?ResultBody?exceptionHandler(HttpServletRequest?req,?NullPointerException?e){
??logger.error("發(fā)生空指針異常!原因是:",e);
??return?ResultBody.error(CommonEnum.BODY_NOT_MATCH);
?}
????/**
????????*?處理其他異常
?????*?@param?req
?????*?@param?e
?????*?@return
?????*/
????@ExceptionHandler(value?=Exception.class)
?@ResponseBody
?public?ResultBody?exceptionHandler(HttpServletRequest?req,?Exception?e){
?????logger.error("未知異常!原因是:",e);
????????return?ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
????}
}
因為這里我們只是用于做全局異常處理的功能實現(xiàn)以及測試,所以這里我們只需在添加一個實體類和一個控制層類即可。
實體類
又是萬能的用戶表 (^▽^)
代碼如下:
public?class?User?implements?Serializable{
?private?static?final?long?serialVersionUID?=?1L;
?/**?編號?*/
??private?int?id;
??/**?姓名?*/
??private?String?name;
??/**?年齡?*/
??private?int?age;
??
??public?User(){
??}
?public?int?getId()?{
??return?id;
?}
?
?public?void?setId(int?id)?{
??this.id?=?id;
?}
?public?String?getName()?{
??return?name;
?}
?public?void?setName(String?name)?{
??this.name?=?name;
?}
?public?int?getAge()?{
??return?age;
?}
?public?void?setAge(int?age)?{
??this.age?=?age;
?}
?public?String?toString()?{
??return?JSONObject.toJSONString(this);
?}
}
Controller 控制層
控制層這邊也比較簡單,使用Restful風(fēng)格實現(xiàn)的CRUD功能,不同的是這里我故意弄出了一些異常,好讓這些異常被捕獲到然后處理。這些異常中,有自定義的異常拋出,也有空指針的異常拋出,當(dāng)然也有不可預(yù)知的異常拋出(這里我用類型轉(zhuǎn)換異常代替),那么我們在完成代碼編寫之后,看看這些異常是否能夠被捕獲處理成功吧!
代碼如下:
@RestController
@RequestMapping(value?=?"/api")
public?class?UserRestController?{
?@PostMapping("/user")
????public?boolean?insert(@RequestBody?User?user)?{
?????System.out.println("開始新增...");
?????//如果姓名為空就手動拋出一個自定義的異常!
????????if(user.getName()==null){
????????????throw??new?BizException("-1","用戶姓名不能為空!");
????????}
????????return?true;
????}
????@PutMapping("/user")
????public?boolean?update(@RequestBody?User?user)?{
?????System.out.println("開始更新...");
???????//這里故意造成一個空指針的異常,并且不進(jìn)行處理
????????String?str=null;
????????str.equals("111");
????????return?true;
????}
????@DeleteMapping("/user")
????public?boolean?delete(@RequestBody?User?user)??{
????????System.out.println("開始刪除...");
????????//這里故意造成一個異常,并且不進(jìn)行處理
????????Integer.parseInt("abc123");
????????return?true;
????}
????@GetMapping("/user")
????public?List?findByUser(User?user)?{
?????System.out.println("開始查詢...");
????????List?userList?=new?ArrayList<>();
????????User?user2=new?User();
????????user2.setId(1L);
????????user2.setName("xuwujing");
????????user2.setAge(18);
????????userList.add(user2);
????????return?userList;
????}
????
}
App 入口
和普通的SpringBoot項目基本一樣。
代碼如下:
@SpringBootApplication
public?class?App?
{
????public?static?void?main(?String[]?args?)
????{
??SpringApplication.run(App.class,?args);
??System.out.println("程序正在運(yùn)行...");
????}
}
功能測試
我們成功啟動該程序之后,使用Postman工具來進(jìn)行接口測試。
首先進(jìn)行查詢,查看程序正常運(yùn)行是否ok,使用GET 方式進(jìn)行請求。
“GET http://localhost:8181/api/user
”
返回參數(shù)為:
“{"id":1,"name":"xuwujing","age":18}
”
示例圖:
可以看到程序正常返回,并沒有因自定義的全局異常而影響。
然后我們再來測試下自定義的異常是否能夠被正確的捕獲并處理。
使用POST方式進(jìn)行請求
“POST http://localhost:8181/api/user
”
Body參數(shù)為:
“{"id":1,"age":18}
”
返回參數(shù)為:
“{"code":"-1","message":"用戶姓名不能為空!","result":null}
”
示例圖:
可以看出將我們拋出的異常進(jìn)行數(shù)據(jù)封裝,然后將異常返回出來。
然后我們再來測試下空指針異常是否能夠被正確的捕獲并處理。在自定義全局異常中,我們除了定義空指針的異常處理,也定義最高級別之一的Exception異常,那么這里發(fā)生了空指針異常之后,它是回優(yōu)先使用哪一個呢?這里我們來測試下。
使用PUT方式進(jìn)行請求。
“PUT http://localhost:8181/api/user
”
Body參數(shù)為:
“{"id":1,"age":18}
”
返回參數(shù)為:
“{"code":"400","message":"請求的數(shù)據(jù)格式不符!","result":null}
”
示例圖:
我們可以看到這里的的確是返回空指針的異常護(hù)理,可以得出全局異常處理優(yōu)先處理子類的異常。
那么我們在來試試未指定其異常的處理,看該異常是否能夠被捕獲。
使用DELETE方式進(jìn)行請求。
“DELETE http://localhost:8181/api/user
”
Body參數(shù)為:
“{"id":1}
”
返回參數(shù)為:
“{"code":"500","message":"服務(wù)器內(nèi)部錯誤!","result":null}
”
這里可以看到它使用了我們在自定義全局異常處理類中的Exception異常處理的方法。到這里,測試就結(jié)束了。順便再說一下,自義定全局異常處理除了可以處理上述的數(shù)據(jù)格式之外,也可以處理頁面的跳轉(zhuǎn),只需在新增的異常方法的返回處理上填寫該跳轉(zhuǎn)的路徑并不使用ResponseBody 注解即可。細(xì)心的同學(xué)也許發(fā)現(xiàn)了在GlobalExceptionHandler類中使用的是ControllerAdvice注解,而非RestControllerAdvice注解,如果是用的RestControllerAdvice注解,它會將數(shù)據(jù)自動轉(zhuǎn)換成JSON格式,這種于Controller和RestController類似,所以我們在使用全局異常處理的之后可以進(jìn)行靈活的選擇處理。
其它
關(guān)于SpringBoot優(yōu)雅的全局異常處理的文章就講解到這里了,如有不妥,歡迎指正!
項目地址
SpringBoot全局異常的處理項目工程地址: https://github.com/xuwujing/springBoot-study/tree/master/springboot-exceptionHandler

好文章,我在看
好文章,我在看

