Java?Controller實現(xiàn)參數(shù)驗證與統(tǒng)一異常處理流程詳細講解
最近開發(fā)了比較多的接口,因為沒有可參考的案例,所以一開始一直按照我的理解進行開發(fā)。開發(fā)多了發(fā)現(xiàn)自己每個結(jié)果都寫了相同的代碼:try() {} catch() {}, 和關于參數(shù)判空的:StringUtils.empty(xxx)。開發(fā)結(jié)束后自然想下次更加優(yōu)雅的開發(fā)。因此,使用了springboot的參數(shù)驗證和統(tǒng)一異常處理。
一,前期數(shù)據(jù)及類準備
1.1 統(tǒng)一狀態(tài)碼
對于不同的返回類型,我們應該要有不同對應的狀態(tài)碼。接口的返回類型在統(tǒng)一狀態(tài)碼中必須存在。
package com.lmc.common.enums; /** * @Description: TODO 接口API返回狀態(tài)碼枚舉 * @version: 1.0 */ public enum ResultCodeEnum { SUCCESS(1000, "請求成功"), FAILURE(1001, "請求失敗"), VALIDATE_PARAMS_ERROR(1002, "參數(shù)校驗失敗"); private int code; private String msg; ResultCodeEnum(int code, String msg) { this.code = code; this.msg = msg; } /** * 獲取code * @return */ public int getCode() { return code; } /** * 獲取信息 * @return */ public String getMsg() { return msg; } }
1.2 統(tǒng)一返回格式
統(tǒng)一狀態(tài)碼完成后,還需要定義統(tǒng)一返回格式,為了前端的方便調(diào)用
package com.lmc.common.vo; import com.fasterxml.jackson.annotation.JsonFormat; import com.lmc.common.enums.ResultCodeEnum; import lombok.Data; import java.util.Date; /** * @Description: TODO 接口返回結(jié)果類型 * @version: 1.0 */ @Data public class ResultVo { /** * 狀態(tài)碼 */ private int code; /** * 狀態(tài)碼信息 */ private String msg; /** * 返回描述信息(預備為調(diào)用失敗的情況下提供詳細的失敗原因) */ private String desc; /** * 返回數(shù)據(jù) */ private Object data; /** * 接口調(diào)用結(jié)束時間 */ @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss") private Date searchTime; public ResultVo(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; this.searchTime = new Date(); } /** * 調(diào)用成功時返回 * @param data * @return */ public static ResultVo success(Object data) { return new ResultVo(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), data); } /** * 調(diào)用失敗時返回 * @param data * @return */ public static ResultVo fail(Object data) { return new ResultVo(ResultCodeEnum.FAILURE.getCode(), ResultCodeEnum.FAILURE.getMsg(), data); } /** * 調(diào)用時指定狀態(tài)碼 * @param enums * @param data * @return */ public static ResultVo result(ResultCodeEnum enums, Object data) { return new ResultVo(enums.getCode(), enums.getMsg(), data); } public ResultVo withDesc(String desc) { this.desc = desc; return this; } }
1.3 自定義接口API異常類
然后再自定義接口的異常類,當然也可以不用,看個人喜好
package pers.lmc.tools2.provider.exception; import com.lmc.common.enums.ResultCodeEnum; /** * @Description: TODO API異常類 * @version: 1.0 */ public class ApiException extends RuntimeException{ private int code; private String msg; public ApiException(String msg) { super(msg); this.code = ResultCodeEnum.FAILURE.getCode(); this.msg = ResultCodeEnum.FAILURE.getMsg(); } public ApiException(ResultCodeEnum enums, String msg) { super(msg); this.code = enums.getCode(); this.msg = enums.getMsg(); } }
1.4 參數(shù)封裝類
為了調(diào)試參數(shù)驗證,還需要自定義一個參數(shù)的封裝類
package pers.lmc.tools2.provider.vo; import lombok.Data; /** * @Description: TODO * @version: 1.0 */ @Data public class Param01Vo { private String name; private Integer age; private Short sex; }
二,參數(shù)驗證
參數(shù)驗證需要用到springboot的validation依賴
2.1 pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- 關于校驗 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency>
2.2 修改參數(shù)封裝類
package pers.lmc.tools2.provider.vo; import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; /** * @Description: TODO * @version: 1.0 */ @Data public class Param01Vo { @NotNull(message = "名稱不能為空") @Size(min = 1, max = 50, message = "名稱name長度必須是1-50個字符") private String name; @NotNull(message = "年齡age不能為空") @Min(value = 10, message = "年齡age不能低于10歲") @Max(value = 25, message = "年齡age不能超過25歲") private Integer age; @Min(value = 0, message = "性別sex只能是0和1,0=女1=男") @Max(value = 1, message = "性別sex只能是0和1,0=女1=男") private Short sex; }
在這里對該封裝類的三個參數(shù)都做了限制
2.3 controller
在controller中對參數(shù)做驗證時,需要在類上使用注解@Validated,同時在接口的該參數(shù)也使用注解@Valid
package pers.lmc.tools2.provider.controller; import com.lmc.common.vo.ResultVo; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import pers.lmc.tools2.provider.vo.Param01Vo; import javax.validation.Valid; /* * @Description: TODO * @version: 1.0 */ @RestController @Validated @RequestMapping("/valicate") @Slf4j public class ValicateController { @PostMapping("/add") public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) { log.info("執(zhí)行add()方法,參數(shù):" + param01Vo.toString()); return ResultVo.success(param01Vo); } }
2.4 測試
開發(fā)完成,準備測試,到APIPost上 訪問 http://localhost:9003/provider/valicate/add,帶上參數(shù):
{ "name":"lmc", "age": 22, "sex": 1 }
訪問成功,返回結(jié)果如下:
{ "code": 1000, "msg": "請求成功", "desc": null, "data": { "name": "lmc", "age": 22, "sex": 1 }, "searchTime": "2022-06-26 19:59:55" }
如果參數(shù)輸入不正確,例如:
{ "name":"", "age": 220, "sex": 2 }
得到結(jié)果如下:
{ "timestamp": "2022-06-26T12:02:21.748+00:00", "status": 400, "error": "Bad Request", "message": "", "path": "/provider/valicate/add" }
日志是這樣的:
2022-06-26 20:02:21 [http-nio-9003-exec-1] WARN o.s.w.s.m.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.lmc.common.vo.ResultVo pers.lmc.tools2.provider.controller.ValicateController.addParam01(pers.lmc.tools2.provider.vo.Param01Vo) with 3 errors: [Field error in object 'param01Vo' on field 'sex': rejected value [2]; codes [Max.param01Vo.sex,Max.sex,Max.java.lang.Short,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.sex,sex]; arguments []; default message [sex],1]; default message [性別sex只能是0和1,0=女1=男]] [Field error in object 'param01Vo' on field 'age': rejected value [220]; codes [Max.param01Vo.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.age,age]; arguments []; default message [age],25]; default message [年齡age不能超過25歲]] [Field error in object 'param01Vo' on field 'name': rejected value []; codes [Size.param01Vo.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.name,name]; arguments []; default message [name],50,1]; default message [名稱name長度必須是1-50個字符]] ]
拋出了MethodArgumentNotValidException異常。
雖然參數(shù)錯誤時確實被攔截了,但格式已經(jīng)和我們想要返回的不一致了。這個時候,就需要用到統(tǒng)一異常處理了。
三,統(tǒng)一異常處理
3.1 方法參數(shù)驗證異常處理
通過以上的問題,我們可以設置controller的統(tǒng)一異常處理,當出現(xiàn)參數(shù)驗證錯誤時,就捕獲MethodArgumentNotValidException異常,然后我們自己做處理。
package pers.lmc.tools2.provider.aop; import com.lmc.common.enums.ResultCodeEnum; import com.lmc.common.vo.ResultVo; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import pers.lmc.tools2.provider.exception.ApiException; import java.util.List; import java.util.stream.Collectors; /** * @Description: TODO * @version: 1.0 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 處理所有校驗失敗的異常(MethodArgumentNotValidException異常) * @param e * @return */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public ResultVo handleBindGetException(MethodArgumentNotValidException e) { // 獲取所有異常參數(shù) List<String> errors = e.getBindingResult() .getFieldErrors() .stream() .map(x -> x.getDefaultMessage()) .collect(Collectors.toList()); return ResultVo.result(ResultCodeEnum.VALIDATE_PARAMS_ERROR, null).withDesc("參數(shù)校驗失敗:" + errors); } /** * 處理自定義APIException異常 * @param e * @return */ @ExceptionHandler(value = ApiException.class) public ResultVo handleApiException(ApiException e) { return ResultVo.fail(null).withDesc(e.getMessage()); } /** * 處理其他異常 * @param e * @return */ @ExceptionHandler(value = Exception.class) public ResultVo handleException(Exception e) { log.info("執(zhí)行到統(tǒng)一處理方法..."); return ResultVo.fail(null).withDesc(e.getMessage()); } }
通過以上配置,再次以非法參數(shù)傳輸時,會報出以下錯誤:
{ "code": 1002, "msg": "參數(shù)校驗失敗", "desc": "參數(shù)校驗失敗:[性別sex只能是0和1,0=女1=男, 名稱name長度必須是1-50個字符, 年齡age不能超過25歲]", "data": null, "searchTime": "2022-06-26 20:08:22" }
這個時候格式已經(jīng)我們想要的返回格式了。
3.2 其他異常處理
剛剛我們嘗試的是方法的參數(shù)驗證異常的處理,對于程序還可能出現(xiàn)的錯誤,配置統(tǒng)一異常處理后也不需要使用try{} catch() {},因為我們已經(jīng)在全局異常處理類中配置了:
/** * 處理其他異常 * @param e * @return */ @ExceptionHandler(value = Exception.class) public ResultVo handleException(Exception e) { log.info("執(zhí)行到統(tǒng)一處理方法..."); return ResultVo.fail(null).withDesc(e.getMessage()); }
這個時候在程序中拋出其他異常,就會執(zhí)行到這里的代碼,同樣返回我們想要的格式。舉例如下
修改controller接口:
@PostMapping("/add") public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) { log.info("執(zhí)行add()方法,參數(shù):" + param01Vo.toString()); int k = 1/0; // 調(diào)用該接口時執(zhí)行到這里會拋出異常 return ResultVo.success(param01Vo); }
調(diào)用接口返回結(jié)果:
{ "code": 1001, "msg": "請求失敗", "desc": "/ by zero", "data": null, "searchTime": "2022-06-26 20:13:51" }
到此這篇關于Java Controller實現(xiàn)參數(shù)驗證與統(tǒng)一異常處理流程詳細講解的文章就介紹到這了,更多相關Java Controller參數(shù)驗證與異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何開發(fā)基于Netty的HTTP/HTTPS應用程序
HTTP/HTTPS是最常見的協(xié)議套件之一,并且隨著智能手機的成功,它的應用也日益廣泛,因為對于任何公司來說,擁有一個可以被移動設備訪問的網(wǎng)站幾乎是必須的。下面就來看看如何開發(fā)基于Netty的HTTP/HTTPS應用程序2021-06-06