SpringBoot統(tǒng)一接口返回及全局異常處理高級(jí)用法
前言
現(xiàn)在大多數(shù)公司項(xiàng)目框架,基本都是屬于前后端分離模式,這種模式會(huì)涉及到一個(gè)前后端對(duì)接問(wèn)題,無(wú)論是對(duì)前端或者是后臺(tái)服務(wù),維護(hù)一套完善且規(guī)范的接口是非常有必要的,這樣不僅能夠提高對(duì)接效率,也可以讓我的代碼看起來(lái)更加簡(jiǎn)潔優(yōu)雅。
修改前后最大的區(qū)別是我們不用在每個(gè)接口單獨(dú)捕獲異常,也不用在每個(gè)接口都要組裝一遍返回參數(shù),可以參考下面這張對(duì)比圖:
一、SpringBoot不使用統(tǒng)一返回格式
默認(rèn)情況下,SpringBoot會(huì)有如下三種返回情況。
1.1 使用字符串返回
@GetMapping("/getUserName") public String getUserName(){ return "HuaGe"; }
調(diào)用接口返回結(jié)果:
HuaGe
1.2 使用實(shí)體類(lèi)返回
@GetMapping("/getUserName") public User getUserName(){ return new User("HuaGe",18,"男"); }
調(diào)用接口返回結(jié)果:
{ "name": "HuaGe", "age": "18", "性別": "男", }
1.3 異常情況下返回
@GetMapping("/getUserName") public static String getUserName(){ HashMap hashMap = Maps.newHashMap(); return hashMap.get(0).toString(); }
模擬一個(gè)空指針異常,在不做任何異常處理的情況下,可以看下SpringBoot的默認(rèn)返回結(jié)果:
{ "timestamp": "2021-08-09T06:56:41.524+00:00", "status": 500, "error": "Internal Server Error", "path": "/sysUser/getUserName" }
對(duì)于上面這幾種情況,如果整個(gè)項(xiàng)目沒(méi)有定義統(tǒng)一的返回格式,五個(gè)后臺(tái)開(kāi)發(fā)人員定義五種返回格式,這樣不僅代碼臃腫,前后端對(duì)接效率低,而且還會(huì)有一些意向不到的情況發(fā)生,比如前端直接顯示異常詳情等,這給用戶(hù)的體驗(yàn)是非常差的。
二、基礎(chǔ)玩法
項(xiàng)目中最常見(jiàn)到的是封裝一個(gè)工具類(lèi),類(lèi)中定義需要返回的字段信息,把需要返回前端的接口信息,通過(guò)該類(lèi)進(jìn)行封裝,這樣就可以解決返回格式不統(tǒng)一的現(xiàn)象了。
2.1 參數(shù)說(shuō)明
- code: 狀態(tài)碼,后臺(tái)可以維護(hù)一套統(tǒng)一的狀態(tài)碼;
- message: 描述信息,接口調(diào)用成功/失敗的提示信息;
- data: 返回?cái)?shù)據(jù)。
2.2 流程說(shuō)明
- 新建Result類(lèi)
public class Result<T> { private int code; private String message; private T data;? public Result() {} public Result(int code, String message) { this.code = code; this.message = message; } /** * 成功 */ public static <T> Result<T> success(T data) { Result<T> result = new Result<T>(); result.setCode(ResultMsgEnum.SUCCESS.getCode()); result.setMessage(ResultMsgEnum.SUCCESS.getMessage()); result.setData(data); return result; } /** * 失敗 */ public static <T> Result<T> error(int code, String message) { return new Result(code, message); } }
定義返回狀態(tài)碼
public enum ResultMsgEnum { SUCCESS(0, "成功"), FAIL(-1, "失敗"), AUTH_ERROR(502, "授權(quán)失敗!"), SERVER_BUSY(503, "服務(wù)器正忙,請(qǐng)稍后再試!"), DATABASE_OPERATION_FAILED(504, "數(shù)據(jù)庫(kù)操作失敗"); private int code; private String message; ? ResultMsgEnum(int code, String message) { this.code = code; this.message = message; } public int getCode() { return this.code; } public String getMessage() { return this.message; } }
- 使用方式
上面兩步定義了數(shù)據(jù)返回格式
和狀態(tài)碼
,接下來(lái)就要看下在接口中如何使用了。
@GetMapping("/getUserName") public Result getUserName(){ return Result.success("huage"); }
調(diào)用結(jié)果如下,可以看到是我們?cè)赗esult中定義的參數(shù)類(lèi)型。
{ "code": 0, "message": "成功", "data": "huage" }
這樣寫(xiě)雖然能夠滿足日常需求,而且我相信很多小伙伴也是這么用的,但是如果我們有大量的接口,然后在每一個(gè)接口中都使用Result.success
來(lái)包裝返回信息,會(huì)新增很多重復(fù)代碼,顯得不夠優(yōu)雅,甚至都不好意思拿出去顯擺。 肯定會(huì)有一種方式能夠再一次提高代碼逼格,實(shí)現(xiàn)最優(yōu)解。
三、進(jìn)階用法
基本用法學(xué)會(huì)后,接下來(lái)看點(diǎn)究極版本,主要用到如下兩個(gè)知識(shí)點(diǎn),用法簡(jiǎn)單,無(wú)論是拿出來(lái)教學(xué)妹,還是指點(diǎn)小姐姐,都是必備技能。
3.1 類(lèi)介紹
- ResponseBodyAdvice: 該接口是SpringMVC 4.1提供的,它允許在 執(zhí)行
@ResponseBody
后自定義返回?cái)?shù)據(jù),用來(lái)封裝統(tǒng)一數(shù)據(jù)格式返回; - @RestControllerAdvice: 該注解是對(duì)Controller進(jìn)行增強(qiáng)的,可以全局捕獲拋出的異常。
3.2 用法說(shuō)明
- 新建
ResponseAdvice
類(lèi); - 實(shí)現(xiàn)
ResponseBodyAdvice
接口,實(shí)現(xiàn)supports
、beforeBodyWrite
方法; - 該類(lèi)用于統(tǒng)一封裝controller中接口的返回結(jié)果。
@RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Autowired private ObjectMapper objectMapper; ? /** * 是否開(kāi)啟功能 true:是 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return true; } ? /** * 處理返回結(jié)果 */ @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //處理字符串類(lèi)型數(shù)據(jù) if(o instanceof String){ try { return objectMapper.writeValueAsString(Result.success(o)); } catch (JsonProcessingException e) { e.printStackTrace(); } } return Result.success(o); } } ?
我們可以通過(guò)getUserName
接口測(cè)試一下,會(huì)發(fā)現(xiàn)和直接使用Result
返回的結(jié)果是一致的。
不過(guò),細(xì)心的小伙伴們肯定注意到了,在ResponseAdvice
我們?nèi)渴褂昧?code>Result.success(o)來(lái)處理結(jié)果,對(duì)于error類(lèi)型的結(jié)果未做處理。我們來(lái)看下,發(fā)生異常情況時(shí),返回結(jié)果是什么樣呢?繼續(xù)使用上面HashMap空指針異常的代碼,測(cè)試結(jié)果如下:
{ "code": 0, "message": "成功", "data": { "timestamp": "2021-08-09T09:33:26.805+00:00", "status": 405, "error": "Method Not Allowed", "path": "/sysUser/getUserName" } }
雖然格式上沒(méi)有毛病,但是在code、data字段的具體數(shù)據(jù)上是不友好或不正確的。不處理好這些事情,會(huì)嚴(yán)重影響自己在前端妹妹心中的高大形象的,這是決不能容忍的。
3.3 全局異常處理器
以前我們遇到異常時(shí),第一時(shí)間想到的應(yīng)該是try..catch..finnal吧,不過(guò)這種方式會(huì)導(dǎo)致大量代碼重復(fù),維護(hù)困難,邏輯臃腫等問(wèn)題,這不是我們想要的結(jié)果。
今天我們要用的全局異常處理方式,用起來(lái)是比較簡(jiǎn)單的。首先新增一個(gè)類(lèi),增加@RestControllerAdvice
注解,該注解的作用花哥上面已經(jīng)介紹過(guò),就不再?lài)Z叨了。
@RestControllerAdvice public class CustomerExceptionHandler { }
如果我們有想要攔截的異常類(lèi)型,就新增一個(gè)方法,使用@ExceptionHandler
注解修飾,注解參數(shù)為目標(biāo)異常類(lèi)型。
例如:controller中接口發(fā)生Exception異常時(shí),就會(huì)進(jìn)入到Execption
方法中進(jìn)行捕獲,將雜亂的異常信息,轉(zhuǎn)換成指定格式后交給ResponseAdvice
方法進(jìn)行統(tǒng)一格式封裝并返回給前端小伙伴。
@RestControllerAdvice @Slf4j public class CustomerExceptionHandler { @ExceptionHandler(AuthException.class) public String ErrorHandler(AuthorizationException e) { log.error("沒(méi)有通過(guò)權(quán)限驗(yàn)證!", e); return "沒(méi)有通過(guò)權(quán)限驗(yàn)證!"; } ? @ExceptionHandler(Exception.class) public Result Execption(Exception e) { log.error("未知異常!", e); return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage()); } }
再次調(diào)用接口getUserName
查看返回結(jié)果,會(huì)發(fā)現(xiàn)還是有一些問(wèn)題,因?yàn)槲覀冊(cè)?code>CustomerExceptionHandler中已經(jīng)將接口返回結(jié)果封裝成Result
類(lèi)型,而代碼執(zhí)行到統(tǒng)一結(jié)果返回類(lèi)ResponseAdvice
時(shí),又會(huì)結(jié)果再次封裝,就出現(xiàn)了如下問(wèn)題。
{ "code": 0, "message": "成功", "data": { "code": 503, "message": "服務(wù)器正忙,請(qǐng)稍后再試!", "data": null } }
3.4 統(tǒng)一返回結(jié)果處理類(lèi)最終版
解決上述問(wèn)題非常簡(jiǎn)單,只要在beforeBodyWrite
中增加一條判斷即可。
@RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Autowired private ObjectMapper objectMapper; ? /** * 是否開(kāi)啟功能 true:開(kāi)啟 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return true; } ? /** * 處理返回結(jié)果 */ @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //處理字符串類(lèi)型數(shù)據(jù) if(o instanceof String){ try { return objectMapper.writeValueAsString(Result.success(o)); } catch (JsonProcessingException e) { e.printStackTrace(); } } //返回類(lèi)型是否已經(jīng)封裝 if(o instanceof Result){ return o; } return Result.success(o); } }
至此,本章的任務(wù)就全部講完,上述代碼可以直接引用,不需要其他的配置項(xiàng),非常推薦引用到自己的項(xiàng)目中。
四、總結(jié)
本章講解的內(nèi)容不是很多,主要就兩個(gè)類(lèi)的配置,即處理統(tǒng)一返回結(jié)果的類(lèi)ResponseAdvice
和異常處理類(lèi)CustomerExceptionHandler
。全文圍繞RestControllerAdvice
、ResponseBodyAdvice
的使用,通過(guò)一步步的迭代,最終構(gòu)建一套通用的代碼代碼返回格式,更多關(guān)于SpringBoot接口返回異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Spring?Boot的幾種統(tǒng)一處理方式梳理小結(jié)
- SpringBoot返回結(jié)果統(tǒng)一處理實(shí)例詳解
- SpringBoot實(shí)現(xiàn)接口統(tǒng)一前綴
- Springboot中@Async異步,實(shí)現(xiàn)異步結(jié)果合并統(tǒng)一返回方式
- SpringBoot統(tǒng)一返回處理出現(xiàn)cannot?be?cast?to?java.lang.String異常解決
- SpringBoot使用AOP實(shí)現(xiàn)統(tǒng)一角色權(quán)限校驗(yàn)
- Spring?Boot項(xiàng)目完美大一統(tǒng)(結(jié)果異常日志統(tǒng)一)
相關(guān)文章
springMvc請(qǐng)求的跳轉(zhuǎn)和傳值的方法
本篇文章主要介紹了springMvc請(qǐng)求的跳轉(zhuǎn)和傳值的方法,這里整理了幾種跳轉(zhuǎn)方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02基于servlet的執(zhí)行原理與生命周期(全面解析)
下面小編就為大家分享一篇servlet的執(zhí)行原理與生命周期全面解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12Java對(duì)文件的隨機(jī)讀寫(xiě)以及壓縮處理操作
這篇文章主要介紹了Java對(duì)文件的隨機(jī)讀寫(xiě)以及壓縮處理操作,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10java 中模擬UDP傳輸?shù)陌l(fā)送端和接收端實(shí)例詳解
這篇文章主要介紹了java 中模擬UDP傳輸?shù)陌l(fā)送端和接收端實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03淺談java 增強(qiáng)型的for循環(huán) for each
下面小編就為大家?guī)?lái)一篇淺談java 增強(qiáng)型的for循環(huán) for each。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10SpringBoot2 參數(shù)管理實(shí)踐之入?yún)⒊鰠⑴c校驗(yàn)的方式
這篇文章主要介紹了SpringBoot2 參數(shù)管理實(shí)踐,入?yún)⒊鰠⑴c校驗(yàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-06-06