一、前言
在上一篇 SpringBoot 參數(shù)校驗(yàn) 中我們對(duì)參數(shù)校驗(yàn)添加了異常處理,但是還是有不規(guī)范的地方,沒有用統(tǒng)一響應(yīng)體進(jìn)行返回,在這篇文章中介紹如何封裝統(tǒng)一響應(yīng)體。
關(guān)于統(tǒng)一響應(yīng)體的封裝,沒有一個(gè)標(biāo)準(zhǔn)答案,我在各種技術(shù)社區(qū)看了一遍,匯總了一個(gè)復(fù)用性比較好的方案。
二、添加結(jié)果類枚舉
在項(xiàng)目目錄下面建一個(gè) responseEntity
的 package,然后在里面建一個(gè) ResultEnum
枚舉類,添加如下代碼:
這邊介紹一下枚舉類的用法。枚舉類的作用實(shí)際上就是定義常量,如果不使用枚舉類,通常采用靜態(tài)常量來表示:
public static final Integer OK_CODE = 200;
public static final String OK_MESSAGE = "成功";
public static final Integer BAD_REQUEST_CODE = 400;
public static final String BAD_REQUEST_MESSAGE = "參數(shù)錯(cuò)誤";
這樣的話存在一些問題,一是字段表意不明,特別是看別人的代碼時(shí),會(huì)很懵逼;第二當(dāng)業(yè)務(wù)規(guī)模增大之后,可能要維護(hù)成百上千的靜態(tài)常量,如果都寫在一個(gè)文件里面,容易造成命名混淆,閱讀也比較麻煩。
然后使用枚舉類定義常量就比較方便,相當(dāng)于一個(gè)接口,使用時(shí)只需要封裝內(nèi)部的數(shù)據(jù)類型,并且限定數(shù)據(jù)域。而且對(duì)于不同的枚舉變量,可以調(diào)用不同的處理方法(實(shí)現(xiàn)枚舉類的抽象方法可以做到這一點(diǎn))。關(guān)于枚舉類的一些知識(shí)點(diǎn)匯總?cè)缦拢?/p>
- 使用enum定義的枚舉類默認(rèn)繼承了java.lang.Enum,實(shí)現(xiàn)了java.lang.Comparable接口,且不能繼承其他類,也不可以被繼承。但枚舉類可以實(shí)現(xiàn)一個(gè)或多個(gè)接口;
- 枚舉類的所有實(shí)例必須放在第一行顯示,不需使用new,不需顯示調(diào)用構(gòu)造方法,每個(gè)變量都是public static final修飾的,最終以分號(hào)結(jié)束。在之后的反編譯中,我們就可以理解枚舉類其實(shí)也是顆語法糖;
- 枚舉類的構(gòu)造方法是私有的,默認(rèn)的就是 private,所以不用再添加 private;
枚舉類內(nèi)部常用的方法:
valueOf()
:返回當(dāng)前枚舉類的name屬性,如果沒有,則throw new java.lang.IllegalArgumentException();values()
:是編譯器自動(dòng)生成的方法,Enum中并沒有該方法,返回包括所有枚舉變量的數(shù)組;toString()
和name()
:兩個(gè)方法一樣,返回當(dāng)前枚舉類變量的name屬性,如果覺得不夠用,可以覆蓋默認(rèn)的toString
,結(jié)合SWITCH CASE
來靈活的實(shí)現(xiàn)toString()
方法;ordinal()
:枚舉類會(huì)給所有的枚舉變量一個(gè)默認(rèn)的次序,該次序從0開始,是根據(jù)我們定義的次序來排序的。而ordinal()方法就是獲取這個(gè)次序(或者說下標(biāo));compareTo()
:比較的是兩個(gè)枚舉變量的次序,返回兩個(gè)次序相減后的結(jié)果;
定義了枚舉類之后,在類的上面添加 lombok 的 @Getter
注解,給對(duì)象的每個(gè)屬性添加 getter 方法,方便后面獲取常量。例如要獲取 OK
的狀態(tài)碼,就可以這樣寫:
ResultEnum.OK.getCode()
這邊再解釋下 @Data
、@Getter
和 @Setter
的區(qū)別:
@Data
:注解在類上;提供類所有屬性的 getter 和 setter 方法,此外還提供了equals、canEqual、hashCode、toString 方@Getter
:注解在屬性上:為屬性提供 getter 方法;注解再類上表示當(dāng)前類中所有屬性都生成getter方法@Setter
:注解在屬性上:為屬性提供 setter 方法;注解再類上表示當(dāng)前類中所有屬性都生成setter方法
三、添加統(tǒng)一結(jié)果類
還是在 responseEntity
目錄下面,建一個(gè) ServerResponse
類,添加如下代碼:
@Data
public class ServerResponse {
private Boolean success;
private Integer code;
private String message;
private Object data;
// 構(gòu)造方法設(shè)為私有
private ServerResponse() {}
public static ServerResponse ok(Object params) {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.OK.getSuccess());
serverResponse.setCode(ResultEnum.OK.getCode());
serverResponse.setMessage(ResultEnum.OK.getMessage()); // 成功展示默認(rèn)提示信息
serverResponse.setData(params); // 返回傳入的參數(shù)
return serverResponse;
}
public static ServerResponse badRequest(@Nullable String message) {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.BAD_REQUEST.getSuccess());
serverResponse.setCode(ResultEnum.BAD_REQUEST.getCode());
serverResponse.setMessage(message != null ? message : ResultEnum.BAD_REQUEST.getMessage()); // 校驗(yàn)失敗傳入指定的提示信息
serverResponse.setData(null); // 校驗(yàn)失敗不返回參數(shù)
return serverResponse;
}
}
在上面的代碼中,成員變量和構(gòu)造方法都是私有的,只有靜態(tài)方法向外暴露。然后處理成功的方法,message
展示默認(rèn)提示信息,即定義在枚舉類里面的常量,data
是需要傳給前端的 JSON 參數(shù);處理參數(shù)錯(cuò)誤的方法,message
展示傳進(jìn)去的錯(cuò)誤信息,如果傳的是 null
,則展示默認(rèn)提示信息,即定義在枚舉類里面的常量,data
是傳給前端的參數(shù),但是在參數(shù)錯(cuò)誤的情況下就不需要傳了,因此是 null
。
這邊有一個(gè)問題,暫時(shí)不清楚 Java 是否支持函數(shù)參數(shù)可選,本人測(cè)試發(fā)現(xiàn)定義函數(shù)的時(shí)候有參數(shù),但是調(diào)用的時(shí)候不傳,IDE 會(huì)給錯(cuò)誤提示,因此這里通過傳 null 來解決
四、控制層返回
在定義了統(tǒng)一結(jié)果類之后,就可以在接口中使用了。還是用之前那個(gè)方法,通過 POST 請(qǐng)求獲取用戶信息,再原封不動(dòng)返回過去:
@PostMapping("validateUser")
public ServerResponse userValidate(@RequestBody @Validated UserDTO userDTO) {
return ServerResponse.ok(null);
}
這邊先給參數(shù)傳 null
,不給前端進(jìn)行返回,看一下響應(yīng)的結(jié)果:
然后傳遞參數(shù):
@PostMapping("validateUser")
public ServerResponse userValidate(@RequestBody @Validated UserDTO userDTO) {
return ServerResponse.ok(userDTO);
}
看一下響應(yīng)的結(jié)果:
五、異常處理類使用統(tǒng)一響應(yīng)體
然后我們給異常處理的方法也添加統(tǒng)一響應(yīng)體:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ServerResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
// 注意傳進(jìn)去的表達(dá)式有可能是 null
// 因此在 ServerResponse 對(duì) message 是否為 null 進(jìn)行了判斷
// 如果是 null 就展示默認(rèn)的提示內(nèi)容
return ServerResponse.badRequest(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
}
// 其他異常處理方法
}
然后我們模擬一下參數(shù)異常的情況:
這樣看起來是正常了,但是存在一個(gè)問題,后端判斷參數(shù)異常的時(shí)候,因?yàn)槲覀儾东@了異常,所以返回給前端的狀態(tài)碼還是 200 ,如何讓狀態(tài)碼改為 400 呢?在 SpringBoot 中指定 HTTP 狀態(tài)碼主要有三種方式:
HttpServletResponse
@ResponseStatus
ResponseEntity
這邊使用第三種方式,具體的用法看一下代碼應(yīng)該就明白了。我們把剛才統(tǒng)一結(jié)果類的方法修改下:
public static ResponseEntity<ServerResponse> badRequest(@Nullable String message) {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.BAD_REQUEST.getSuccess());
serverResponse.setCode(ResultEnum.BAD_REQUEST.getCode());
serverResponse.setMessage(message != null ? message : ResultEnum.BAD_REQUEST.getMessage()); // 校驗(yàn)失敗傳入指定的提示信息
serverResponse.setData(null); // 校驗(yàn)失敗不返回參數(shù)
return new ResponseEntity<>(serverResponse, HttpStatus.BAD_REQUEST); // 使用 ResponseEntity 對(duì)象設(shè)置響應(yīng)狀態(tài)碼
}
可以看到我們用一個(gè) ResponseEntity
對(duì)象包裹了我們封裝的響應(yīng)體,然后返回了這個(gè)對(duì)象。其中第二個(gè)參數(shù)就是狀態(tài)碼,HttpStatus.BAD_REQUEST
就代表 400 。然后我們還要修改下異常處理類的返回類型:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<ServerResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return ServerResponse.badRequest(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
}
// 其他異常處理方法
}
再來調(diào)試一下,這下狀態(tài)碼正常了:
在統(tǒng)一結(jié)果類中所有的方法都根據(jù)上面的示例進(jìn)行修改即可,我這邊添加了幾個(gè)方法,給各位參考下,具體可以根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行添加:
@Data
public class ServerResponse {
private Boolean success;
private Integer code;
private String message;
private Object data;
// 構(gòu)造方法設(shè)為私有
private ServerResponse() {}
/**
* 200 請(qǐng)求成功
* @param params 傳給前端的參數(shù)
* @return ResponseEntity<ServerResponse>
*/
public static ResponseEntity<ServerResponse> ok(Object params) {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.OK.getSuccess());
serverResponse.setCode(ResultEnum.OK.getCode());
serverResponse.setMessage(ResultEnum.OK.getMessage()); // 成功展示默認(rèn)提示信息
serverResponse.setData(params); // 返回傳入的參數(shù)
return new ResponseEntity<>(serverResponse, HttpStatus.OK);
}
/**
* 201 創(chuàng)建成功
* @return ResponseEntity<ServerResponse>
*/
public static ResponseEntity<ServerResponse> created() {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.CREATED.getSuccess());
serverResponse.setCode(ResultEnum.CREATED.getCode());
serverResponse.setMessage(ResultEnum.CREATED.getMessage());
// serverResponse.setData(null);
return new ResponseEntity<>(serverResponse, HttpStatus.CREATED);
}
/**
* 204 請(qǐng)求成功,沒有響應(yīng)體
* @return ResponseEntity<ServerResponse>
*/
public static ResponseEntity<ServerResponse> noContent() {
return new ResponseEntity<>(null, HttpStatus.NO_CONTENT);
}
/**
* 400 參數(shù)錯(cuò)誤
* @param message 自定義錯(cuò)誤信息
* @return ResponseEntity<ServerResponse>
*/
public static ResponseEntity<ServerResponse> badRequest(@Nullable String message) {
ServerResponse serverResponse = new ServerResponse();
serverResponse.setSuccess(ResultEnum.BAD_REQUEST.getSuccess());
serverResponse.setCode(ResultEnum.BAD_REQUEST.getCode());
serverResponse.setMessage(message != null ? message : ResultEnum.BAD_REQUEST.getMessage()); // 校驗(yàn)失敗傳入指定的提示信息
// serverResponse.setData(null); // 校驗(yàn)失敗不返回參數(shù)
return new ResponseEntity<>(serverResponse, HttpStatus.BAD_REQUEST); // 使用 ResponseEntity 對(duì)象設(shè)置響應(yīng)狀態(tài)碼
}
}
此外,@ResponseStatus
也是一種設(shè)置狀態(tài)碼常用的方法,只需要在 Controller 方法中加一個(gè)注解就可以:
@PostMapping("validateUser")
@ResponseStatus(code=HttpStatus.BAD_REQUEST, reason="參數(shù)異常")
public ServerResponse userValidate(@RequestBody @Validated UserDTO userDTO) {
return ServerResponse.ok(userDTO);
}
到此這篇關(guān)于 SpringBoot 框架是如何封裝統(tǒng)一響應(yīng)體的文章就介紹到這了,想要了解更多相關(guān) SpringBoot 封裝統(tǒng)一響應(yīng)體的其他內(nèi)容請(qǐng)搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持我們!