前言
在日常业务中参数校验是不可或缺的一部分,我们可以在业务代码中进行参数的规则校验(优雅的使用断言Assert
),也可以在接收参数的时候直接进行校验。由代码的执行入口顺序来看,我们最好是在入参的时候进行简单校验。
一、导入依赖
若springboot
版本低于2.3.x
,则spring-boot-starter-web
则会自动引入hibernate-validator
相关依赖,否则我们自己手动导入即可:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
二、常用校验注解
@NotBlank
:用于校验String类型,使得字符串不为null并且长度大于零,即.trim()的长度大于零
@NotEmpty
:一般用于校验集合不为空
@NotNull
:所校验的值不能为null
@Null
:被检验的对象要为null
@AssertTrue
:被校验的对象必须为true
(我测试的时候怎么填都是false,不知道怎么测试。。。)
@AssertFalse
:被校验的对象必须为false
@Min(value = val)
:被校验的对象必须是数字,而且大于等于val
@Max(value = val)
:被校验的对象必须是数字,而且小于等于val
@DecimalMin(value = "val")
:同@Min(value)
@DecimalMax(value = "val")
:同@Max(value)
@Size(min = min, max = max)
:验证注解的元素值的在min
和max
(包含)指定区间之内,如字符长度、集合大小(对于集合来说,null
和空字符串都是算长度的)
@Past
:被校验的对象(日期类型)比当前时间早
@Future
:被校验的对象(日期类型)比当前时间晚
@Length(min=min, max=max)
:被校验字符串长度在min
和max
区间内,包含边界
@Range(min=min, max=max)
:验证注解的元素值在最小值和最大值之内,包含边界,如数字类型
@Email
:验证邮箱,也可以通过regexp
自定义正则匹配
@Pattern(regexp = "")
:自定义正则匹配
三、@Validated和@Valid注意点
@Validated
:
- 可以用在类型、方法和方法参数上,但是不能用在成员属性(字段)上
- 不能对嵌套对象进行校验
- 可以进行分组校验
@Valid
:
- 可以用在方法、方法参数、构造函数、方法参数和成员属性(字段)上
- 可以进行嵌套,但是得在需要嵌套的字段上面加上注解
- 不能进行分组校验
所以大多情况下可以进行混合使用
四、实战以及问题
先写一下全局异常的处理
/**
* 方法参数校验
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException=======>{}", e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")));
return CommonResult.failed(e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")));
}
/**
* ConstraintViolationException
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public CommonResult<Object> handleConstraintViolationException(ConstraintViolationException e) {
log.error("ConstraintViolationException=======>{}", e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));
return CommonResult.failed(e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));
}
/**
* ValidationException
*/
@ResponseBody
@ExceptionHandler(ValidationException.class)
public CommonResult<Object> handleValidationException(ValidationException e) {
log.error("ValidationException=======>{}", e);
return CommonResult.failed(e.getMessage());
}
@ResponseBody
@ExceptionHandler(value = Exception.class)
public CommonResult<Object> serviceExceptionHandler(Exception e) {
log.error("GlobalExceptionHandler===========>{}", e);
return CommonResult.failed(e.getMessage());
}
4.1 嵌套对象使用
入参:
// 入参
@Data
public class UserVo implements Serializable {
private static final long serialVersionUID = 7965030945933727065L;
@NotBlank(message = "姓名不能为空")
private String name;
@NotBlank(message = "性别不能为空")
private String sex;
@NotNull(message = "爱好不能为空")
@Valid
private HobbyVo hobbyVo;
}
嵌套对象:
//嵌套对象
@Data
public class HobbyVo implements Serializable {
private static final long serialVersionUID = 7248588761842099248L;
@NotBlank(message = "爱好名不能为空")
private String name;
}
测试方法:
// 测试方法
@PostMapping("/testSingle")
public String test(@Validated @RequestBody UserVo userVo) {
return "ok";
}
结果:
不加内嵌对象,验证不通过:
加了内嵌对象字段,验证通过:
4.2 集合对象验证校验
测试方法:
@PostMapping("/testList")
public String test(@Validated @RequestBody List<UserVo> userVoList) {
return "ok";
}
结果:
可以看到,我们还有几个参数没填竟然还返回ok
了,可见我们的校验没起作用。
解决方案
- 先将
Spring DataBinder
配置为使用直接字段访问
@ControllerAdvice
public class CustomControllerAdvice {
@InitBinder
private void activateDirectFieldAccess(DataBinder dataBinder) {
dataBinder.initDirectFieldAccess();
}
}
- 利用
@Valid
可以加在字段上面的特性重写List
public class ValidList<E> implements List<E> {
@Valid
private List<E> list = new ArrayList<>();
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
return list.add(e);
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return list.addAll(index,c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public void clear() {
list.clear();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
return list.set(index,element);
}
@Override
public void add(int index, E element) {
list.add(index,element);
}
@Override
public E remove(int index) {
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
@Override
public ListIterator<E> listIterator() {
return list.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return list.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex,toIndex);
}
}
- 测试方法
@PostMapping("/testList")
public String test(@Validated @RequestBody ValidList<UserVo> userVoList) {
return "ok";
}
- 测试结果
4.3分组校验
在我们平时业务中,会遇到同一个Vo进行不同的操作,但是对于Vo的检验规则是不同的,比如新增方法,对于id
可为空,但是对于修改方法id
不可为空,对于这种场景,就需要好好利用我们的分组校验功能了
- 创建分组的接口(继承
javax.validation.groups.Default
接口)
public interface Save extends Default {
}
public interface Update extends Default {
}
- 实体类
@Data
public class ProduceVo implements Serializable {
private static final long serialVersionUID = -8921638716346141268L;
@JsonFormat(shape = JsonFormat.Shape.STRING)
@NotNull(groups = {Update.class}, message = "id不能为空")
private Long id;
}
- 测试方法
新增
@PostMapping("/testGroupInsert")
public String insert(@Validated(Save.class) @RequestBody ProduceVo produceVo) {
return "ok";
}
结果:
可以看到,没传参数也能成功
修改
@PostMapping("/testGroupUpdate")
public String update(@Validated(Update.class) @RequestBody ProduceVo produceVo) {
return "ok";
}
结果:
可以看到不传id校验不通过
4.4自定义校验
链接: 自定义参数校验注解的使用