如何使用
Spring提供了简便的参数校验注解,不需要像以前一样if else去判断了,下面记录一下如何使用注解实现参数的校验
导入坐标
要使用各种注解完成参数的校验,需要导入hibernate-validator坐标以实现
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
常用注解如下(本人并没有都使用过,粘贴的)
@Null 检查该字段为空
@NotNull 不能为null
@NotBlank 不能为空,常用于检查空字符串
@NotEmpty 不能为空,多用于检测list是否size是0
@Max 该字段的值只能小于或等于该值
@Min 该字段的值只能大于或等于该值
@Past 检查该字段的日期是在过去
@Future 检查该字段的日期是否是属于将来的日期
@Email 检查是否是一个有效的email地址
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Size(min=, max=) 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
@Length(min=,max=) 检查所属的字段的长度是否在min和max之间,只能用于字符串
@AssertTrue 用于boolean字段,该字段只能为true
@AssertFalse 该字段的值只能为false
注解说明
我们使用到的验证参数注解共有两个@Valid与@Validated。
@Validated:可以用在类型、方法和方法参数上,支持分组校验。但是不能用在成员属性上,不支持嵌套检测;
@Valid:可以用在方法、构造方法、方法参数和成员属性上,支持嵌套检测。
验证注解时可能抛出的异常说明
在使用注解校验参数的过程中,可能由于参数不合法会抛出各种异常,但是大体就只有下面三种
BindException:表单提交时,如果参数未通过注解校验抛出该异常,对于以json格式提交将不会抛出该异常
MethodArgumentNotValidException:前端以JSON格式提交参数,后端使用requestBody接收时,如果参数未通过注解校验则抛出该异常。需要说明的是,该异常是BindException的子类
ConstraintViolationException :1. 接口参数上加@RequestParam、@PathVariable或者没加任何注解(按形参名赋值)
2. 接口参数加@NotBlank、@NotNull、@Length等校验注解
当1和2都成立时,也就是说当直接在接口的参数中对基本类型进行校验时,若校验未通过则抛出该异常
简单的使用
1. 创建接收参数的对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
@NotNull(groups = Group1.g12.class,message = "学号不能为空")
@Length(min = 2,max = 3, groups = Group1.g12.class,message = "学号长度错误,需要在2~3位之间")
private String no;
@NotNull(groups = Group1.g11.class,message = "姓名不存在!")
private String name;
@Max(12)
private Integer age;
private String address;
}
2.创建实现分组校验的接口
public interface Group1 {
//命名比较随意,看看就行
public interface g11{};
public interface g12{};
public interface g13{};
public interface g14{};
}
3.写几个接口测试测试
@PostMapping("v1")
public void v1(@Validated(Group1.g12.class) Student student){ //bindException
System.out.println(student.toString());
}
@GetMapping("v2/{name}")
public void v2(@PathVariable @Length(min = 2,max = 5,message = "name长度需要在2~5之间") String name){ //ConstraintViolationException
System.out.println(name);
}
@PostMapping("v3")
public void v3(@RequestBody @Validated Student student){ //MethodArgumentNotValidException
System.out.println("业务执行了...");
System.out.println(student.toString());
}
可以看到,我写了三个接口,v1是验证Student(加入了分组校验),v2是直接在接口参数中对基本类型String进行校验,v3是接收JSON参数进行校验
接下来我们一个一个测试:
v1
v1接口很明显,需要对Student进行校验,并且指定了分组Group1.g12.class,而通过观察Student对象可以看到,只有学号no携带了该分组,所以只要学号字段验证通过就没问题。但是当学号为空时则控制台打印Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors<EOL>Field error in object 'student' on field 'no': rejected value [null]; codes [NotNull.student.no,NotNull.no,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.no,no]; arguments []; default message [no]]; default message [学号不能为空]<EOL>Field error in object 'student' on field 'no': rejected value [null]; codes [NotNull.student.no,NotNull.no,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.no,no]; arguments []; default message [no]]; default message [学号不能为空]]
当学号超过3位长度时则控制台打印:Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<EOL>Field error in object 'student' on field 'no': rejected value [1200]; codes [Length.student.no,Length.no,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.no,no]; arguments []; default message [no],3,2]; default message [学号长度错误,需要在2~3位之间]]
v2(提示下,直接在接口中对基本类型进行校验需要在controller上打@Validated注解)
v2就是在接口参数中直接对对基本类型进行校验,长度需要在2~5之间,所以我们直接发送一个不符合条件的请求,http://localhost:8080/v2/HelloWorld,控制台打印:
v3
v3就是接收参数的形式变成了JSON,并且进行了参数校验(不带任何分组),所以只是对age进行了校验,age不能超过12,我们试着发送一个不合法的请求,JSON参数为{“name”:“zhangsan”,“age”:15},结果控制台打印:Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.validation.controller.VController.v3(com.example.validation.entity.Student): [Field error in object 'student' on field 'age': rejected value [15]; codes [Max.student.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.age,age]; arguments []; default message [age],12]; default message [最大不能超过12]] ]
4.关于分组校验
分组校验的情况下,对于没有添加分组校验的注解将不会生效
没有分组校验情况下,只会校验不带分组校验的校验注解
5.分组校验的全局异常处理
在第三步中,我们测试完接口发现报出了不同的异常信息,那么全局异常处理是必不可少的,需要将不合法的提示正确的返回给用户。
经过测试,对于报出的异常共有三类
- 对于直接在接口中进行校验的基本类型参数(包含String),抛出ConstraintViolationException
- 对于JSON请求映射的参数,抛出MethodArgumentNotValidException
- 对于接口参数为对象时,抛出BindException
其中,MethodArgumentNotValidException是BindException的子类
那么,我们只需要拦截BindException和ConstraintViolationException进行处理即可,如下:
//拦截BindException和MethodArgumentNotValidException
@ExceptionHandler(BindException.class)
public Map<String, String> MANVE(BindException e){
Map<String, String> res = new LinkedHashMap();
BindingResult bindingResult = e.getBindingResult();
List<ObjectError> allErrors = bindingResult.getAllErrors();
for (ObjectError item:allErrors) {
if (item instanceof FieldError){
String field = ((FieldError) item).getField();
String defaultMessage = ((FieldError) item).getDefaultMessage();
res.put(field,defaultMessage);
}
}
return res;
}
//拦截ConstraintViolationException
@ExceptionHandler(ConstraintViolationException.class)
public Map<String,String> CVE(ConstraintViolationException e){
Map<String,String> res = new LinkedHashMap<>();
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
for (ConstraintViolation item: constraintViolations) {
String messageTemplate = item.getMessageTemplate();
PathImpl propertyPath = (PathImpl) item.getPropertyPath();
NodeImpl leafNode = propertyPath.getLeafNode();
res.put(leafNode.asString(),messageTemplate);
}
return res;
}
可以把代码中的返回报错信息改为你所需要的形式