一、参数校验的由来
校验参数在项目中是很常见的,在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
说拜拜了。