SpringBoot封裝響應(yīng)處理超詳細(xì)講解
背景
越來越多的項(xiàng)目開始基于前后端分離的模式進(jìn)行開發(fā),這對(duì)后端接口的報(bào)文格式便有了一定的要求。通常,我們會(huì)采用JSON格式作為前后端交換數(shù)據(jù)格式,從而減少溝通成本等。
報(bào)文基本格式
一般報(bào)文格式通常會(huì)包含狀態(tài)碼、狀態(tài)描述(或錯(cuò)誤提示信息)、業(yè)務(wù)數(shù)據(jù)等信息。 在此基礎(chǔ)上,不同的架構(gòu)師、項(xiàng)目搭建者可能會(huì)有所調(diào)整。 但從整體上來說,基本上都是大同小異。
在SpringBoot項(xiàng)目中,通常接口返回的報(bào)文中至少包含三個(gè)屬性:
code:請(qǐng)求接口的返回碼,成功或者異常等返回編碼,例如定義請(qǐng)求成功。
message:請(qǐng)求接口的描述,也就是對(duì)返回編碼的描述。
data:請(qǐng)求接口成功,返回的業(yè)務(wù)數(shù)據(jù)。
示例報(bào)文如下:
{ "code":200, "message":"SUCCESS", "data":{ "info":"測(cè)試成功" } }
在上述報(bào)文格式中,不同的設(shè)計(jì)者是會(huì)有一些分歧的,特別是code值的定義。如果完全基于RESTful API設(shè)計(jì)的話,code字段可能就不需要存在了,而是通過HTTP協(xié)議中提供的GET、POST、PUT、DELETE操作等來完成資源的訪問。
但在實(shí)踐中,不論是出于目前國(guó)內(nèi)大多數(shù)程序員的習(xí)慣,還是受限于HTTP協(xié)議提供的操作方法的局限性,很少完全遵照RESTful API方式進(jìn)行設(shè)計(jì)。通常都是通過自定義Code值的形式來賦予它業(yè)務(wù)意義或業(yè)務(wù)錯(cuò)誤編碼。
雖然可以不用完全遵守RESTful API風(fēng)格來定義Code,在Code值的自定義中,也存在兩種形式:遵循HTTP狀態(tài)碼和自主定義。
像上面的示例,用200表示返回成功,這就是遵循HTTP響應(yīng)狀態(tài)碼的形式來返回,比如還有其他的400、401、404、500等。當(dāng)然,還有完全自主定義的,比如用0表示成功,1表示失敗,然后再跟進(jìn)通用編碼、業(yè)務(wù)分類編碼等進(jìn)行定義。
在此,筆者暫不評(píng)論每種形式的好壞,只列舉了常規(guī)的幾種形式,大家了解對(duì)應(yīng)的情況,做到心中有數(shù),有所選擇即可。
創(chuàng)建枚舉類
用于定義返回的錯(cuò)誤碼:
public enum ErrorCode { SUCCESS(0, "ok", ""), FAIL(500, "failed",""), PARAMS_ERROR(40000, "請(qǐng)求參數(shù)錯(cuò)誤", ""), NULL_ERROR(40001, "請(qǐng)求數(shù)據(jù)為空", ""), NOT_LOGIN(40100, "未登錄", ""), NO_AUTH(40101, "無權(quán)限", ""), SYSTEM_ERROR(50000, "系統(tǒng)內(nèi)部異常", ""); private final int code; /** * 狀態(tài)碼信息 */ private final String message; /** * 狀態(tài)碼描述(詳情) */ private final String description; ErrorCode(int code, String message, String description) { this.code = code; this.message = message; this.description = description; } public int getCode() { return code; } public String getMessage() { return message; } public String getDescription() { return description; } }
定義統(tǒng)一返回結(jié)果實(shí)體類
@Data public class BaseResponse<T> implements Serializable { private int code; private T data; private String message; private String description; public BaseResponse(int code, T data, String message, String description) { this.code = code; this.data = data; this.message = message; this.description = description; } public BaseResponse(int code, T data, String message) { this(code, data, message, ""); } public BaseResponse(int code, T data) { this(code, data, "", ""); } public BaseResponse(ErrorCode errorCode) { this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription()); } }
在BaseResponse中運(yùn) 用了泛型和公共方法、構(gòu)造方法的封裝,方便在業(yè)務(wù)中使用。 示例中只提供了部分方法的封裝,根據(jù)自身業(yè)務(wù)場(chǎng)景和需要可進(jìn)一步封裝。
定義返回工具類
public class ResultUtils { /** * 成功 * * @param data * @param <T> * @return */ public static <T> BaseResponse<T> success(T data) { return new BaseResponse<>(ErrorCode.SUCCESS.getCode(), data, "ok"); } public static <T> BaseResponse<T> success(String message) { return new BaseResponse<>(ErrorCode.SUCCESS.getCode(), null, message); } /** * 失敗 * * @param errorCode * @return */ public static BaseResponse error(ErrorCode errorCode) { return new BaseResponse<>(errorCode); } /** * 失敗 * * @param code * @param message * @param description * @return */ public static BaseResponse error(int code, String message, String description) { return new BaseResponse(code, null, message, description); } /** * 失敗 * * @param errorCode * @return */ public static BaseResponse error(ErrorCode errorCode, String message, String description) { return new BaseResponse(errorCode.getCode(), null, message, description); } /** * 失敗 * * @param errorCode * @return */ public static BaseResponse error(ErrorCode errorCode, String description) { return new BaseResponse(errorCode.getCode(), errorCode.getMessage(), description); } public static BaseResponse error(String message) { return new BaseResponse(ErrorCode.FAIL.getCode(),message); } /** * 失敗 * * @param errorCode * @return */ public static BaseResponse error(HttpStatus errorCode, String description) { return new BaseResponse(errorCode.value(), errorCode.getReasonPhrase(), description); } }
統(tǒng)一報(bào)文封裝在接口中的使用
@RestController public class TestController { @RequestMapping("/calc") public ResponseInfo<String> calc(Integer id) { try { // 模擬異常業(yè)務(wù)代碼 int num = 1 / id; log.info("計(jì)算結(jié)果num={}", num); return ResultUtils.success(); } catch (Exception e) { return ResponseInfo.error("系統(tǒng)異常,請(qǐng)聯(lián)系管理員!"); } } }
統(tǒng)一異常處理
在上述實(shí)例中,我們通過try…catch的形式捕獲異常,并進(jìn)行處理。 在SpringBoot中,我們可以通過RestControllerAdvice注解來定義全局異常處理,這樣就無需每處都try…catch了。
/** * 全局異常處理器 * */ @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public BaseResponse businessExceptionHandler(BusinessException e) { log.error("businessException: " + e.getMessage(), e); return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription()); } @ExceptionHandler(RuntimeException.class) public BaseResponse runtimeExceptionHandler(RuntimeException e) { log.error("runtimeException", e); return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage(), ""); } /** * 參數(shù)格式異常處理 */ @ExceptionHandler({IllegalArgumentException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public BaseResponse badRequestException(IllegalArgumentException ex) { log.error("參數(shù)格式不合法:{}", ex.getMessage()); return ResultUtils.error(HttpStatus.BAD_REQUEST, "參數(shù)格式不符!"); } /** * 權(quán)限不足異常處理 */ @ExceptionHandler({AccessDeniedException.class}) @ResponseStatus(HttpStatus.FORBIDDEN) public BaseResponse badRequestException(AccessDeniedException ex) { return ResultUtils.error(HttpStatus.FORBIDDEN, ex.getMessage()); } /** * 參數(shù)缺失異常處理 */ @ExceptionHandler({MissingServletRequestParameterException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public BaseResponse badRequestException(Exception ex) { return ResultUtils.error(HttpStatus.BAD_REQUEST, "缺少必填參數(shù)!"); } /** * 空指針異常 */ @ExceptionHandler(NullPointerException.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public BaseResponse handleTypeMismatchException(NullPointerException ex) { log.error("空指針異常,{}", ex.getMessage()); return ResultUtils.error("空指針異常"); } @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public BaseResponse handleUnexpectedServer(Exception ex) { log.error("系統(tǒng)異常:", ex); return ResultUtils.error("系統(tǒng)發(fā)生異常,請(qǐng)聯(lián)系管理員"); } /** * 系統(tǒng)異常處理 */ @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public BaseResponse exception(Throwable throwable) { log.error("系統(tǒng)異常", throwable); return ResultUtils.error(HttpStatus.INTERNAL_SERVER_ERROR, "系統(tǒng)異常,請(qǐng)聯(lián)系管理員!"); } }
在上述方法中,對(duì)一些常見的異常進(jìn)行了統(tǒng)一處理。 通常情況下,根據(jù)業(yè)務(wù)需要還會(huì)定義業(yè)務(wù)異常,并對(duì)業(yè)務(wù)異常進(jìn)行處理,大家可以根據(jù)自己項(xiàng)目中異常的使用情況進(jìn)行拓展。
關(guān)于@RestControllerAdvice的幾點(diǎn)說明:
@RestControllerAdvice注解包含了@Component注解,會(huì)把被注解的類作為組件交給Spring來管理。
@RestControllerAdvice注解包含了@ResponseBody注解,異常處理完之后給調(diào)用方輸出一個(gè)JSON格式的封裝數(shù)據(jù)。
@RestControllerAdvice注解有一個(gè)basePackages屬性,該屬性用來攔截哪個(gè)包中的異常信息,一般不指定,攔截項(xiàng)目工程中的所有異常。
在方法上通過@ExceptionHandler注解來指定具體的異常,在方法中處理該異常信息,最后將結(jié)果通過統(tǒng)一的JSON結(jié)構(gòu)體返回給調(diào)用者。
重構(gòu)接口
@RestController public class TestController { @RequestMapping("/calc") public ResponseInfo<String> calc(Integer id) { // 模擬異常業(yè)務(wù)代碼 int num = 1 / id; log.info("計(jì)算結(jié)果num={}", num); return ResultUtils.success(); } }
在請(qǐng)求的時(shí)候,不傳遞id值,即在瀏覽器中訪問:
{ "code": 500, "data": "空指針異常", "message": "", "description": "" }
可以看到統(tǒng)一異常處理對(duì)空指針異常進(jìn)行了攔截處理,并返回了ExceptionHandlerAdvice中定義的統(tǒng)一報(bào)文格式。
小結(jié)
在使用SpringBoot或其他項(xiàng)目中,統(tǒng)一的報(bào)文格式和統(tǒng)一的異常處理都是必須的。 本篇文章介紹了基于SpringBoot的實(shí)現(xiàn),如果你的項(xiàng)目中采用了其他的技術(shù)棧,則可考慮對(duì)應(yīng)的處理方式。 同時(shí),日常中很多類似的功能都可以統(tǒng)一進(jìn)行處理,避免大量無效的硬編碼。
到此這篇關(guān)于SpringBoot封裝響應(yīng)處理超詳細(xì)講解的文章就介紹到這了,更多相關(guān)SpringBoot封裝響應(yīng)處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC實(shí)現(xiàn)文件上傳和下載功能
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)文件上傳和下載功能 ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Spring WebFlux實(shí)現(xiàn)參數(shù)校驗(yàn)的示例代碼
請(qǐng)求參數(shù)校驗(yàn),在實(shí)際的應(yīng)用中很常見,網(wǎng)上的文章大部分提供的使用注解的方式做參數(shù)校驗(yàn)。本文主要介紹 Spring Webflux Function Endpoint 使用 Spring Validation 來校驗(yàn)請(qǐng)求的參數(shù)。感興趣的可以了解一下2021-08-08springboot如何解決跨域后session獲取不到sessionId不一致
這篇文章主要介紹了springboot如何解決跨域后session獲取不到sessionId不一致問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Java中ShardingSphere 數(shù)據(jù)分片的實(shí)現(xiàn)
其實(shí)很多人對(duì)分庫分表多少都有點(diǎn)恐懼,我們今天用ShardingSphere 給大家演示數(shù)據(jù)分片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09基于spring實(shí)現(xiàn)websocket實(shí)時(shí)推送實(shí)例
這篇文章主要為大家詳細(xì)介紹了基于spring實(shí)現(xiàn)websocket實(shí)時(shí)推送實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03