一、參數校驗的由來
校驗參數在項目中是很常見的,在java中,幾乎每個有入參的方法,在執行下一步操作之前,都要驗證參數的合法性,比如是入參否為空,數據格式是否正確等等,往常的寫法就是一大推的if-else
,既不美觀也不優雅,這個時候JCP
組織站出來了,並且制定了一個標准來規范校驗的操作,這個標准就是Java Validation API(JSR 303)
。
但是這個僅僅是一個標准而是,並沒有具體的實現,下面介紹兩種常用實現。
二、Java Validation API 的實現者
2.1、hibernate-validator
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.20.Final</version>
</dependency>
這個實現是有hibernate實現的,如果入參是一個對象,配合@Valid
注解即可,但是無法對單個參數應用@NotNull
、@Min
這類的注解
2.2、spring-boot-starter-validation
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
或者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
這兩個pom
是spring
對hibernate-validator
的一個封裝和擴展,同時提供了一個@Validated
的注解,該注解即可標記在類上,也可以跟@Valid
注解用法一樣,標記一個入參對象,更強大的是,該注解支持了單個參數的校驗,但是需要在類
上加@Validated
這個注解,可見該注解完全可以替代@Valid注解來使用。
從依賴依賴關系圖,可以看出starter-web
和starter-validation
都依賴於hibernate-validator
而hibernate-validator
依賴於validation-api
,而且項目中經用到的@NotBlank、@NotNull、@Min、@Valid
等注解都出自validation-api
包中,hibernate-validator
中的注解已不推薦使用,validation-api
的包路徑為 javax.validation.constraints
三、使用
3.1、需求
用戶注冊接口,名稱,年齡,郵箱、不能為空
用戶修改接口,名稱,年齡,郵箱,主鍵id,不能為空
用戶信息接口,入參為單個參數
3.2、代碼實現
注冊接口group
/**
* 添加時的驗證規則
* @author DUCHONG
* @since 2020-08-24 23:35:46
*/
public interface ValidAddRules {
}
修改接口group
/**
* 修改時的驗證規則
* @author DUCHONG
* @since 2020-08-24 23:35:46
**/
public interface ValidUpdateRules {
}
入參對象UserRequest
/**
* 入參對象
*
* @author DUCHONG
* @since 2020-08-24 23:33
**/
@Data
@Builder
public class UserRequest implements java.io.Serializable {
private static final long serialVersionUID = -2655536314774756670L;
/**
* 主鍵
*/
@NotNull(message = "id不能為空",groups = {ValidUpdateRules.class})
@Min(1)
private Long userId;
/**
* 年齡
*/
@NotNull(message = "年齡不能為空",groups = {ValidUpdateRules.class,ValidAddRules.class})
@Min(1)
private Integer age;
/**
* 企業類型名稱
*/
@NotBlank(message = "名稱不能為空",groups = {ValidUpdateRules.class,ValidAddRules.class})
private String name;
/**
* 郵箱
*/
@NotBlank(message = "郵箱不能為空",groups = {ValidUpdateRules.class,ValidAddRules.class})
@Email(message = "郵箱格式不正確")
private String email;
/**
* 昵稱
*/
private String nickName;
}
controller
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
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.RestController;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用戶控制器
*
* @author DUCHONG
* @since 2020-08-24 23:41
**/
@RestController
@Slf4j
@Validated
public class UserController {
/**
* 用戶注冊
* @param userRequest
* @param bindingResult
* @return
*/
@PostMapping("/user/register")
public String registerUser(@Validated(ValidAddRules.class) @RequestBody UserRequest userRequest, BindingResult bindingResult){
List<String> list=new ArrayList<>();
bindingResult.getFieldErrors().forEach(fieldError -> {
list.add(fieldError.getDefaultMessage());
});
return list.stream().collect(Collectors.joining(",")) ;
}
/**
* 用戶注冊
* @param userId
* @param bindingResult
* @return
*/
@PostMapping("/user/get")
public String getUser(@NotNull(message = "用戶id不能為空") @Min(1) Long userId, BindingResult bindingResult){
List<String> list=new ArrayList<>();
bindingResult.getFieldErrors().forEach(fieldError -> {
list.add(fieldError.getDefaultMessage());
});
return list.stream().collect(Collectors.joining(",")) ;
}
/**
* 用戶修改
* @param userRequest
* @param bindingResult
* @return
*/
@PostMapping("/user/update")
public String updateUser(@Validated(ValidUpdateRules.class) @RequestBody UserRequest userRequest, BindingResult bindingResult){
List<String> list=new ArrayList<>();
bindingResult.getFieldErrors().forEach(fieldError -> {
list.add(fieldError.getDefaultMessage());
});
return list.stream().collect(Collectors.joining(",")) ;
}
}
搞定!!!
但是你有木有發現,每個方法上有一個BindingResult
,存儲這報錯的信息,那是不是可以提供一個公用的方法,當校驗規則觸發時,能捕獲到異常信息,然后返回,它來了,它就是統一的異常處理入口,話不多少上代碼
四、校驗統一異常處理
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 統一的異常處理器
* @author DUCHONG
* @since 2020-08-25 00:57:40
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 參數合法性校驗異常
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception){
BaseResponse exceptionInfo = getErrorInfo(exception);
log.error("參數校驗異常---{}",exceptionInfo.getMsg());
return exceptionInfo;
}
/**
* 參數合法性校驗異常-類型不匹配
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public BaseResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception){
BaseResponse exceptionInfo = getErrorInfo(exception);
log.error("參數校驗異常---{}",exceptionInfo.getMsg());
return exceptionInfo;
}
/**
* 參數綁定異常
* @param exception
* @return
*/
@ExceptionHandler(value = BindException.class)
public BaseResponse handleBindException(BindException exception) {
BaseResponse exceptionInfo = getErrorInfo(exception);
log.error("參數校驗異常---{}",exceptionInfo.getMsg());
return exceptionInfo;
}
/**
* 違反約束異常 單個參數使用
* @param exception
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public BaseResponse handleConstraintViolationException(ConstraintViolationException exception) {
BaseResponse exceptionInfo = getErrorInfo(exception);
log.error("參數校驗異常---{}", exceptionInfo.getMsg());
return exceptionInfo;
}
/**
* 將List結果轉換成json格式
* @param exception
* @return
*/
public BaseResponse getErrorInfo(Exception exception) {
if(exception instanceof BindException){
return convertBindingResultToJson(((BindException) exception).getBindingResult());
}
if(exception instanceof MethodArgumentNotValidException){
return convertBindingResultToJson(((MethodArgumentNotValidException) exception).getBindingResult());
}
if(exception instanceof ConstraintViolationException){
return convertSetToJson(((ConstraintViolationException) exception).getConstraintViolations());
}
if(exception instanceof MethodArgumentTypeMismatchException){
String msg= exception.getMessage();
return new BaseResponse(500, msg, null);
}
//未定義的異常
return new BaseResponse(500, "未知錯誤", null);
}
/**
* 將單個參數實體校驗結果封裝
* @param constraintViolations
* @return
*/
public BaseResponse convertSetToJson(Set<? extends ConstraintViolation> constraintViolations) {
List<String> list=new ArrayList<>();
for (ConstraintViolation violation : constraintViolations) {
list.add(violation.getMessage());
}
return new BaseResponse(500,list.stream().collect(Collectors.joining(",")), null);
}
/**
* 將實體對象的校驗結果封裝
* @param result
* @return
*/
public BaseResponse convertBindingResultToJson(BindingResult result){
List<String> list=new ArrayList<>();
result.getFieldErrors().forEach(fieldError -> {
list.add(fieldError.getDefaultMessage());
});
return new BaseResponse(500,list.stream().collect(Collectors.joining(",")),null);
}
}
到此可以跟BindingResult
說拜拜了。