您好,登錄后才能下訂單哦!
這篇文章主要講解了“SpringBoot如何統一后端返回格式”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“SpringBoot如何統一后端返回格式”吧!
為什么要對SpringBoot返回統一的標準格式
第一種:返回 String
第二種:返回自定義對象
第三種:接口異常
定義返回標準格式
高級實現方式
接口異常問題
SpringBoot為什么需要全局異常處理器
體驗效果
全局異常接入返回的標準格式
今天我們來聊一聊在基于SpringBoot前后端分離開發模式下,如何友好的返回統一的標準格式以及如何優雅的處理全局異常。
首先我們來看看為什么要返回統一的標準格式?
在默認情況下,SpringBoot的返回格式常見的有三種:
@GetMapping("/hello") public String getStr(){ return "hello,javadaily"; }
此時調用接口獲取到的返回值是這樣:
hello,javadaily
@GetMapping("/aniaml") public Aniaml getAniaml(){ Aniaml aniaml = new Aniaml(1,"pig"); return aniaml; }
此時調用接口獲取到的返回值是這樣:
{ "id": 1, "name": "pig" }
@GetMapping("/error") public int error(){ int i = 9/0; return i; }
此時調用接口獲取到的返回值是這樣:
{ "timestamp": "2021-07-08T08:05:15.423+00:00", "status": 500, "error": "Internal Server Error", "path": "/wrong" }
基于以上種種情況,如果你和前端開發人員聯調接口她們就會很懵逼,由于我們沒有給他一個統一的格式,前端人員不知道如何處理返回值。
還有甚者,有的同學比如小張喜歡對結果進行封裝,他使用了Result對象,小王也喜歡對結果進行包裝,但是他卻使用的是Response對象,當出現這種情況時我相信前端人員一定會抓狂的。
所以我們項目中是需要定義一個統一的標準返回格式的。
一個標準的返回格式至少包含3部分:
status 狀態值:由后端統一定義各種返回結果的狀態碼
message 描述:本次接口調用的結果描述
data 數據:本次返回的數據
{ "status":"100", "message":"操作成功", "data":"hello,javadaily" }
當然也可以按需加入其他擴展值,比如我們就在返回對象中添加了接口調用時間
timestamp: 接口調用時間
定義返回對象
@Data public class ResultData<t> { /** 結果狀態 ,具體狀態碼參見ResultData.java*/ private int status; private String message; private T data; private long timestamp ; public ResultData (){ this.timestamp = System.currentTimeMillis(); } public static <t> ResultData<t> success(T data) { ResultData<t> resultData = new ResultData<>(); resultData.setStatus(ReturnCode.RC100.getCode()); resultData.setMessage(ReturnCode.RC100.getMessage()); resultData.setData(data); return resultData; } public static <t> ResultData<t> fail(int code, String message) { ResultData<t> resultData = new ResultData<>(); resultData.setStatus(code); resultData.setMessage(message); return resultData; } }
定義狀態碼
public enum ReturnCode { /**操作成功**/ RC100(100,"操作成功"), /**操作失敗**/ RC999(999,"操作失敗"), /**服務限流**/ RC200(200,"服務開啟限流保護,請稍后再試!"), /**服務降級**/ RC201(201,"服務開啟降級保護,請稍后再試!"), /**熱點參數限流**/ RC202(202,"熱點參數限流,請稍后再試!"), /**系統規則不滿足**/ RC203(203,"系統規則不滿足要求,請稍后再試!"), /**授權規則不通過**/ RC204(204,"授權規則不通過,請稍后再試!"), /**access_denied**/ RC403(403,"無訪問權限,請聯系管理員授予權限"), /**access_denied**/ RC401(401,"匿名用戶訪問無權限資源時的異常"), /**服務異常**/ RC500(500,"系統異常,請稍后重試"), INVALID_TOKEN(2001,"訪問令牌不合法"), ACCESS_DENIED(2003,"沒有權限訪問該資源"), CLIENT_AUTHENTICATION_FAILED(1001,"客戶端認證失敗"), USERNAME_OR_PASSWORD_ERROR(1002,"用戶名或密碼錯誤"), UNSUPPORTED_GRANT_TYPE(1003, "不支持的認證模式"); /**自定義狀態碼**/ private final int code; /**自定義描述**/ private final String message; ReturnCode(int code, String message){ this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } }
統一返回格式
@GetMapping("/hello") public ResultData<string> getStr(){ return ResultData.success("hello,javadaily"); }
此時調用接口獲取到的返回值是這樣:
{ "status": 100, "message": "hello,javadaily", "data": null, "timestamp": 1625736481648, "httpStatus": 0 }
這樣確實已經實現了我們想要的結果,我在很多項目中看到的都是這種寫法,在Controller層通過ResultData.success()
對返回結果進行包裝后返回給前端。
看到這里我們不妨停下來想想,這樣做有什么弊端呢?
最大的弊端就是我們后面每寫一個接口都需要調用ResultData.success()
這行代碼對結果進行包裝,重復勞動,浪費體力;而且還很容易被其他老鳥給嘲笑。
所以呢我們需要對代碼進行優化,目標就是不要每個接口都手工制定ResultData
返回值。
要優化這段代碼很簡單,我們只需要借助SpringBoot提供的ResponseBodyAdvice
即可。
ResponseBodyAdvice的作用:攔截Controller方法的返回值,統一處理返回值/響應體,一般用來統一返回格式,加解密,簽名等等。
先來看下ResponseBodyAdvice
的源碼:
public interface ResponseBodyAdvice<t> { /** * 是否支持advice功能 * true 支持,false 不支持 */ boolean supports(MethodParameter var1, Class<!--? extends HttpMessageConverter<?-->> var2); /** * 對返回的數據進行處理 */ @Nullable T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<!--? extends HttpMessageConverter<?-->> var4, ServerHttpRequest var5, ServerHttpResponse var6); }
我們只需要編寫一個具體實現類即可
/** * @author jam * @date 2021/7/8 10:10 上午 */ @RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<object> { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter methodParameter, Class<!--? extends HttpMessageConverter<?-->> aClass) { return true; } @SneakyThrows @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<!--? extends HttpMessageConverter<?-->> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if(o instanceof String){ return objectMapper.writeValueAsString(ResultData.success(o)); } return ResultData.success(o); } }
需要注意兩個地方:
@RestControllerAdvice
注解
@RestControllerAdvice
是@RestController
注解的增強,可以實現三個方面的功能:
全局異常處理
全局數據綁定全
局數據預處理
String類型判斷
if(o instanceof String){ return objectMapper.writeValueAsString(ResultData.success(o)); }
這段代碼一定要加,如果Controller直接返回String的話,SpringBoot是直接返回,故我們需要手動轉換成json。
經過上面的處理我們就再也不需要通過ResultData.success()
來進行轉換了,直接返回原始數據格式,SpringBoot自動幫我們實現包裝類的封裝。
@GetMapping("/hello") public String getStr(){ return "hello,javadaily"; }
此時我們調用接口返回的數據結果為:
@GetMapping("/hello") public String getStr(){ return "hello,javadaily"; }
是不是感覺很完美,別急,還有個問題在等著你呢。
此時有個問題,由于我們沒對Controller的異常進行處理,當我們調用的方法一旦出現異常,就會出現問題,比如下面這個接口
@GetMapping("/wrong") public int error(){ int i = 9/0; return i; }
返回的結果為:
這顯然不是我們想要的結果,接口都報錯了還返回操作成功的響應碼,前端看了會打人的。
別急,接下來我們進入第二個議題,如何優雅的處理全局異常。
不用手寫try...catch,由全局異常處理器統一捕獲
使用全局異常處理器最大的便利就是程序員在寫代碼時不再需要手寫try...catch
了,前面我們講過,默認情況下SpringBoot出現異常時返回的結果是這樣:
{ "timestamp": "2021-07-08T08:05:15.423+00:00", "status": 500, "error": "Internal Server Error", "path": "/wrong" }
這種數據格式返回給前端,前端是看不懂的,所以這時候我們一般通過try...catch
來處理異常
@GetMapping("/wrong") public int error(){ int i; try{ i = 9/0; }catch (Exception e){ log.error("error:{}",e); i = 0; } return i; }
我們追求的目標肯定是不需要再手動寫try...catch
了,而是希望由全局異常處理器處理。
對于自定義異常,只能通過全局異常處理器來處理
@GetMapping("error1") public void empty(){ throw new RuntimeException("自定義異常"); }
當我們引入Validator參數校驗器的時候,參數校驗不通過會拋出異常,此時是無法用try...catch
捕獲的,只能使用全局異常處理器。
SpringBoot集成參數校驗請參考這篇文章SpringBoot開發秘籍 - 集成參數校驗及高階技巧
如何實現全局異常處理器
@Slf4j @RestControllerAdvice public class RestExceptionHandler { /** * 默認全局異常處理。 * @param e the e * @return ResultData */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResultData<string> exception(Exception e) { log.error("全局異常信息 ex={}", e.getMessage(), e); return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage()); } }
有三個細節需要說明一下:
@RestControllerAdvice
,RestController的增強類,可用于實現全局異常處理器
@ExceptionHandler
,統一處理某一類異常,從而減少代碼重復率和復雜度,比如要獲取自定義異常可以@ExceptionHandler(BusinessException.class)
@ResponseStatus
指定客戶端收到的http狀態碼
這時候我們調用如下接口:
@GetMapping("error1") public void empty(){ throw new RuntimeException("自定義異常"); }
返回的結果如下:
{ "status": 500, "message": "自定義異常", "data": null, "timestamp": 1625795902556 }
基本滿足我們的需求了。
但是當我們同時啟用統一標準格式封裝功能ResponseAdvice
和RestExceptionHandler
全局異常處理器時又出現了新的問題:
{ "status": 100, "message": "操作成功", "data": { "status": 500, "message": "自定義異常", "data": null, "timestamp": 1625796167986 }, "timestamp": 1625796168008 }
此時返回的結果是這樣,統一格式增強功能會給返回的異常結果再次封裝,所以接下來我們需要解決這個問題。
要讓全局異常接入標準格式很簡單,因為全局異常處理器已經幫我們封裝好了標準格式,我們只需要直接返回給客戶端即可。
@SneakyThrows @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<!--? extends HttpMessageConverter<?-->> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if(o instanceof String){ return objectMapper.writeValueAsString(ResultData.success(o)); } if(o instanceof ResultData){ return o; } return ResultData.success(o); }
關鍵代碼:
if(o instanceof ResultData){ return o; }
如果返回的結果是ResultData對象,直接返回即可。
這時候我們再調用上面的錯誤方法,返回的結果就符合我們的要求了。
{ "status": 500, "message": "自定義異常", "data": null, "timestamp": 1625796580778 }
好了,今天的文章就到這里了,希望通過這篇文章你能掌握如何在你項目中友好實現統一標準格式到返回并且可以優雅的處理全局異常。
github地址:https://github.com/jianzh6/cloud-blog/
感謝各位的閱讀,以上就是“SpringBoot如何統一后端返回格式”的內容了,經過本文的學習后,相信大家對SpringBoot如何統一后端返回格式這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。