SpringBoot封裝響應(yīng)處理超詳細(xì)講解
背景
越來(lái)越多的項(xiàng)目開(kāi)始基于前后端分離的模式進(jìn)行開(kāi)發(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)整。 但從整體上來(lái)說(shuō),基本上都是大同小異。
在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字段可能就不需要存在了,而是通過(guò)HTTP協(xié)議中提供的GET、POST、PUT、DELETE操作等來(lái)完成資源的訪問(wèn)。
但在實(shí)踐中,不論是出于目前國(guó)內(nèi)大多數(shù)程序員的習(xí)慣,還是受限于HTTP協(xié)議提供的操作方法的局限性,很少完全遵照RESTful API方式進(jìn)行設(shè)計(jì)。通常都是通過(guò)自定義Code值的形式來(lái)賦予它業(yè)務(wù)意義或業(yè)務(wù)錯(cuò)誤編碼。
雖然可以不用完全遵守RESTful API風(fēng)格來(lái)定義Code,在Code值的自定義中,也存在兩種形式:遵循HTTP狀態(tài)碼和自主定義。
像上面的示例,用200表示返回成功,這就是遵循HTTP響應(yīng)狀態(tài)碼的形式來(lái)返回,比如還有其他的400、401、404、500等。當(dāng)然,還有完全自主定義的,比如用0表示成功,1表示失敗,然后再跟進(jìn)通用編碼、業(yè)務(wù)分類(lèi)編碼等進(jìn)行定義。
在此,筆者暫不評(píng)論每種形式的好壞,只列舉了常規(guī)的幾種形式,大家了解對(duì)應(yīng)的情況,做到心中有數(shù),有所選擇即可。
創(chuàng)建枚舉類(lèi)
用于定義返回的錯(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, "無(wú)權(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í)體類(lèi)
@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)一步封裝。
定義返回工具類(lèi)
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í)例中,我們通過(guò)try…catch的形式捕獲異常,并進(jìn)行處理。 在SpringBoot中,我們可以通過(guò)RestControllerAdvice注解來(lái)定義全局異常處理,這樣就無(wú)需每處都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ì)一些常見(jiàn)的異常進(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)說(shuō)明:
@RestControllerAdvice注解包含了@Component注解,會(huì)把被注解的類(lèi)作為組件交給Spring來(lái)管理。
@RestControllerAdvice注解包含了@ResponseBody注解,異常處理完之后給調(diào)用方輸出一個(gè)JSON格式的封裝數(shù)據(jù)。
@RestControllerAdvice注解有一個(gè)basePackages屬性,該屬性用來(lái)攔截哪個(gè)包中的異常信息,一般不指定,攔截項(xiàng)目工程中的所有異常。
在方法上通過(guò)@ExceptionHandler注解來(lái)指定具體的異常,在方法中處理該異常信息,最后將結(jié)果通過(guò)統(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值,即在瀏覽器中訪問(wèn):
{
"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í),日常中很多類(lèi)似的功能都可以統(tǒng)一進(jìn)行處理,避免大量無(wú)效的硬編碼。
到此這篇關(guān)于SpringBoot封裝響應(yīng)處理超詳細(xì)講解的文章就介紹到這了,更多相關(guān)SpringBoot封裝響應(yīng)處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java從控制臺(tái)接收一個(gè)數(shù)字的實(shí)例詳解
這篇文章主要介紹了java從控制臺(tái)接收一個(gè)數(shù)字的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例代碼,注釋說(shuō)明清晰,需要的朋友可以參考下2017-07-07
springboot中設(shè)置定時(shí)任務(wù)的三種方法小結(jié)
在我們開(kāi)發(fā)項(xiàng)目過(guò)程中,經(jīng)常需要定時(shí)任務(wù)來(lái)幫助我們來(lái)做一些內(nèi)容,本文介紹了springboot中設(shè)置定時(shí)任務(wù)的三種方法,主要包括@Scheduled注解,Quartz框架和xxl-job框架的實(shí)現(xiàn),感興趣的可以了解一下2023-12-12
解決Spring session(redis存儲(chǔ)方式)監(jiān)聽(tīng)導(dǎo)致創(chuàng)建大量redisMessageListenerConta
這篇文章主要介紹了解決Spring session(redis存儲(chǔ)方式)監(jiān)聽(tīng)導(dǎo)致創(chuàng)建大量redisMessageListenerContailner-X線程問(wèn)題,需要的朋友可以參考下2018-08-08
springboot中的pom文件?project報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了springboot中的pom文件?project報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java實(shí)戰(zhàn)個(gè)人博客系統(tǒng)的實(shí)現(xiàn)流程
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+springboot+mybatis+redis+vue+elementui+Mysql實(shí)現(xiàn)一個(gè)個(gè)人博客系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2022-01-01
mybatis-plus 查詢(xún)時(shí)排除字段方法的兩種方法
我們?cè)陂_(kāi)發(fā)應(yīng)用時(shí),在某些應(yīng)用場(chǎng)景下查詢(xún)有時(shí)需要排除某些字段,本文主要介紹了兩種方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
java 數(shù)據(jù)類(lèi)型有哪些取值范圍多少
這篇文章主要介紹了java 數(shù)據(jù)類(lèi)型有哪些取值范圍多少的相關(guān)資料,網(wǎng)上關(guān)于java 數(shù)據(jù)類(lèi)型的資料有很多,不夠全面,這里就整理下,需要的朋友可以參考下2017-01-01

