SpringBoot使用@Valid注解+Exception全局處理器優(yōu)雅處理參數(shù)驗(yàn)證
目錄
一、為什么使用 @Valid 來驗(yàn)證參數(shù)
二、@Valid 注解的作用
三、@Valid 的相關(guān)注解
四、使用 @Valid 進(jìn)行參數(shù)校驗(yàn)步驟
實(shí)體類中添加 @Valid 相關(guān)注解
接口類中添加 @Valid 注解
全局異常處理類中處理 @Valid 拋出的異常
五、SpringBoot 中使用 @Valid 示例
Maven 引入相關(guān)依賴
自定義個(gè)異常類
自定義響應(yīng)枚舉類
自定義響應(yīng)對(duì)象類
自定義實(shí)體類中添加 @Valid 相關(guān)注解
Controller 中添加 @Valid 注解
全局異常處理
啟動(dòng)類
示例測試
相關(guān)地址:
Spring Servlet 文檔:
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet示例項(xiàng)目 Github:https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-filter-example系統(tǒng)環(huán)境:
Jdk 版本:jdk 8
SpringBoot 版本:2.2.1.RELEASE
一、為什么使用 @Valid 來驗(yàn)證參數(shù)
在平常通過 Spring 框架寫代碼時(shí)候,會(huì)經(jīng)常寫接口類,相信大家對(duì)該類的寫法非常熟悉。在寫接口時(shí)經(jīng)常要寫效驗(yàn)請(qǐng)求參數(shù)邏輯,這時(shí)候我們會(huì)常用做法是寫大量的 if 與 if else 類似這樣的代碼來做判斷,如下:

這樣的代碼如果按正常代碼邏輯來說,是沒有什么問題的,不過按優(yōu)雅來說,簡直糟糕透了。不僅不優(yōu)雅,而且如果存在大量的驗(yàn)證邏輯,這會(huì)使代碼看起來亂糟糟,大大降低代碼可讀性,那么有沒有更好的方法能夠簡化這個(gè)過程呢?
答案當(dāng)然是有,推薦的是使用 @Valid 注解來幫助我們簡化驗(yàn)證邏輯。
二、@Valid 注解的作用
注解 @Valid 的主要作用是用于數(shù)據(jù)校驗(yàn),可以在定義的實(shí)體中的屬性上,添加不同的注解來完成不同的校驗(yàn)規(guī)則,而在接口類中的接收數(shù)據(jù)參數(shù)中添加 @valid 注解,這時(shí)你的實(shí)體將會(huì)開啟一個(gè)校驗(yàn)的功能。
三、@Valid 的相關(guān)注解
下面是 @Valid 相關(guān)的注解,在實(shí)體類中不同的屬性上添加不同的注解,就能實(shí)現(xiàn)不同數(shù)據(jù)的效驗(yàn)功能。

四、使用 @Valid 進(jìn)行參數(shù)校驗(yàn)步驟
整個(gè)過程如下圖所示,用戶訪問接口,然后進(jìn)行參數(shù)效驗(yàn),因?yàn)?@Valid 不支持平面的參數(shù)效驗(yàn)(直接寫在參數(shù)中字段的效驗(yàn))所以基于 GET 請(qǐng)求的參數(shù)還是按照原先方式進(jìn)行效驗(yàn),而 POST 則可以以實(shí)體對(duì)象為參數(shù),可以使用 @Valid 方式進(jìn)行效驗(yàn)。如果效驗(yàn)通過,則進(jìn)入業(yè)務(wù)邏輯,否則拋出異常,交由全局異常處理器進(jìn)行處理。

1、實(shí)體類中添加 @Valid 相關(guān)注解
使用 @Valid 相關(guān)注解非常簡單,只需要在參數(shù)的實(shí)體類中屬性上面添加如 @NotBlank、@Max、@Min 等注解來對(duì)該字段進(jìn)限制,如下:
User:

如果是嵌套的實(shí)體對(duì)象,則需要在最外層屬性上添加 @Valid 注解:
User:

UserInfo:

2、接口類中添加 @Valid 注解
在 Controller 類中添加接口,POST 方法中接收設(shè)置了 @Valid 相關(guān)注解的實(shí)體對(duì)象,然后在參數(shù)中添加 @Valid 注解來開啟效驗(yàn)功能,需要注意的是, @Valid 對(duì) Get 請(qǐng)求中接收的平面參數(shù)請(qǐng)求無效,稍微略顯遺憾。

3、全局異常處理類中處理 @Valid 拋出的異常
最后,我們寫一個(gè)全局異常處理類,然后對(duì)接口中拋出的異常進(jìn)行處理,而 @Valid 配合 Spring 會(huì)拋出
MethodArgumentNotValidException 異常,這里我們需要對(duì)該異常進(jìn)行處理即可。

五、SpringBoot 中使用 @Valid 示例
1、Maven 引入相關(guān)依賴
Maven 引入 SpringBoot 相關(guān)依賴,這里引入了 Lombok 包來簡化開發(fā)過程。

2、自定義個(gè)異常類
自定義個(gè)異常類,方便我們處理 GET 請(qǐng)求(GET 請(qǐng)求參數(shù)中一般是沒有實(shí)體對(duì)象的,所以不能使用 @Valid),當(dāng)請(qǐng)求驗(yàn)證失敗時(shí),手動(dòng)拋出自定義異常,交由全局異常處理。

3、自定義響應(yīng)枚舉類
定義一個(gè)返回信息的枚舉類,方便我們快速響應(yīng)信息,不必每次都寫返回消息和響應(yīng)碼。

4、自定義響應(yīng)對(duì)象類
創(chuàng)建用于返回調(diào)用方的響應(yīng)信息的實(shí)體類。

5、自定義實(shí)體類中添加 @Valid 相關(guān)注解
下面將創(chuàng)建用于 POST 方法接收參數(shù)的實(shí)體對(duì)象,里面添加 @Valid 相關(guān)驗(yàn)證注解,并在注解中添加出錯(cuò)時(shí)的響應(yīng)消息。
User

UserInfo

6、Controller 中添加 @Valid 注解
接口類中添加 GET 和 POST 方法的兩個(gè)接口用于測試,其中 POST 方法以上面創(chuàng)建的 Uer 實(shí)體對(duì)象接收參數(shù),并使用 @Valid,而 GET 請(qǐng)求一般接收參數(shù)較少,所以使用正常判斷邏輯進(jìn)行參數(shù)效驗(yàn)。

7、全局異常處理
這里創(chuàng)建一個(gè)全局異常處理類,方便統(tǒng)一處理異常錯(cuò)誤信息。里面添加了不同異常處理的方法,專門用于處理接口中拋出的異常信。
import club.mydlq.valid.entity.ResponseResult;import club.mydlq.valid.enums.ResultEnum;import club.mydlq.valid.exception.ParamaErrorException;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.util.StringUtils;import org.springframework.validation.BindingResult;import org.springframework.validation.FieldError;import org.springframework.validation.ObjectError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.MissingServletRequestParameterException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.List;@Slf4j@RestControllerAdvice("club.mydlq.valid")public class GlobalExceptionHandler { /**
* 忽略參數(shù)異常處理器
*
* @param e 忽略參數(shù)異常
* @return ResponseResult
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error("", e); return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "請(qǐng)求參數(shù) " + e.getParameterName() + " 不能為空");
} /**
* 缺少請(qǐng)求體異常處理器
*
* @param e 缺少請(qǐng)求體異常
* @return ResponseResult
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error("", e); return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "參數(shù)體不能為空");
} /**
* 參數(shù)效驗(yàn)異常處理器
*
* @param e 參數(shù)驗(yàn)證異常
* @return ResponseInfo
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseResult parameterExceptionHandler(MethodArgumentNotValidException e) {
log.error("", e); // 獲取異常信息
BindingResult exceptions = e.getBindingResult(); // 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors(); if (!errors.isEmpty()) { // 這里列出了全部錯(cuò)誤參數(shù),按正常邏輯,只需要第一條錯(cuò)誤即可
FieldError fieldError = (FieldError) errors.get(0); return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
}
} return new ResponseResult(ResultEnum.PARAMETER_ERROR);
} /**
* 自定義參數(shù)錯(cuò)誤異常處理器
*
* @param e 自定義參數(shù)
* @return ResponseInfo
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ParamaErrorException.class})
public ResponseResult paramExceptionHandler(ParamaErrorException e) {
log.error("", e); // 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
if (!StringUtils.isEmpty(e.getMessage())) { return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), e.getMessage());
} return new ResponseResult(ResultEnum.PARAMETER_ERROR);
}
}8、啟動(dòng)類

9、示例測試
下面將針對(duì)上面示例中設(shè)置的兩種接口進(jìn)行測試,分別來驗(yàn)證參數(shù)效驗(yàn)功能。
|| - 測試接口 /user/{username}
使用 GET 方法請(qǐng)求地址
http://localhost:8080/user?username=test 時(shí),返回信息:
{
"code": 1000,
"msg": "請(qǐng)求成功"}當(dāng)不輸入?yún)?shù),輸入地址
http://localhost:8080/user 時(shí),返回信息:
{
"code": 1001,
"msg": "請(qǐng)求參數(shù) username 不能為空"}可以看到在執(zhí)行 GET 請(qǐng)求,能夠正常按我們?nèi)之惓L幚砥髦械脑O(shè)置處理異常信息。
|| - 測試接口 /user
(1)、使用 POST 方法發(fā)起請(qǐng)求,首先進(jìn)行不加 JSON 請(qǐng)求體來對(duì)
http://localhost:8080/user 地址進(jìn)行請(qǐng)求,返回信息:
{
"code": 1001,
"msg": "參數(shù)體不能為空"}(2)、輸入部分參數(shù)進(jìn)行測試。
請(qǐng)求內(nèi)容:
{
"username":"test",
"password":"123"}返回信息:
{
"code": 1001,
"msg": "userinfo不能為空"}(3)、輸入完整參數(shù),且設(shè)置 age > 18 時(shí),進(jìn)行測試。
{
"username":"111",
"password":"sa",
"userInfo":{
"age":19,
"gender":"男" }
}返回信息:
{
"code": 1001,
"msg": "不能超過18歲"}可以看到在執(zhí)行 POST 請(qǐng)求,也能正常按我們?nèi)之惓L幚砥髦械脑O(shè)置處理異常信息,且提示信息為我們設(shè)置在實(shí)體類中的 Message。
