使用 @Valid 注解实现各种字段的格式校验
- 【一】准备实体类
- 【二】通过普通的if-else实现字段校验
- 【三】使用@Valid注解实现字段校验
- (1)引入依赖
- (2)改造Controller
- (3)改造实体类
- 【四】常用的校验注解
- 【五】@Pattern的用法和常用的正则表达式
- 【六】待补充
- 【七】@Vaild无法验证递归验证List
- (1)方法一(实测有效)
- (2)方法二
【一】准备实体类
首先新建一个学生类
public class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生年龄
*/
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
【二】通过普通的if-else实现字段校验
假如此时要求学生姓名不能为空且长度不能超过10,那么我们按照以前的校验方式(if-else)来写就是这样子的
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 模拟学生新增的过程
**/
@RestController
@RequestMapping("/student")
public class StudentController {
@GetMapping("/add")
public String add(Student student){
String name = student.getName();
if(name == null || name.trim().length() == 0){
return "学生姓名不能为空!";
}else if (name.trim().length() > 10){
return "学生姓名长度超过限制!";
}
return "添加成功";
}
}
要求年龄字段也是非空字段且范围在1-100之间
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 模拟学生新增的过程
**/
@RestController
@RequestMapping("/student")
public class StudentController {
@GetMapping("/add")
public String add(Student student){
String name = student.getName();
if(name == null || name.trim().length() == 0){
return "学生姓名不能为空!";
}else if (name.trim().length() > 10){
return "学生姓名长度超过限制!";
}
Integer age = student.getAge();
if(age == null){
return "学生年龄不能为空!";
}else if (age < 1 || age > 100){
return "学生年龄有误,请重新核对!";
}
return "添加成功";
}
}
通过if-else的判断来实现字段校验,可以实现需求。但是对业务代码入侵比较严重,如果此时需要校验的字段越来越多,业务代码里校验的代码就会越来越多,导致业务代码丧失可读性。
【三】使用@Valid注解实现字段校验
(1)引入依赖
在使用 @Valid 注解之前,要先引入依赖(如果是 Spring Boot 项目的话,就不需要专门引入依赖了,spring-boot-starter-web 已经帮我们引入好了)
<dependency>
<groupId></groupId>
<artifactId>validation-api</artifactId>
<version>1.1.</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
(2)改造Controller
(1)给学生对象前增加上 @Valid 注解,就表示了我们需要对这个对象中的属性进行验证,验证的内容就是在学生类中增加的注解中的内容。
(2)BindingResult 可以理解为 @Valid 注解的“老搭档”,BindingResult 作用就是在实体类校验信息后存储校验结果。
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* 模拟学生新增的过程
**/
@RestController
@RequestMapping("/student")
public class StudentController {
@GetMapping("/add")
public String add(@Valid Student student, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return bindingResult.getAllErrors().get(0).getDefaultMessage();
}
return "添加成功";
}
}
(3)改造实体类
package com.example.shiro.controller;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
/**
* 学生类
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-16 11:12
**/
public class Student {
/**
* 学生姓名
*/
@NotNull(message = "Valid校验:请输入学生姓名!")
@Length(message = "名称不能超过个 {max} 字符", max = 10)
private String name;
/**
* 学生年龄
*/
@NotNull(message = "Valid校验:请输入学生年龄")
@Range(message = "年龄范围为 {min} 到 {max} 之间", min = 1, max = 100)
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
【四】常用的校验注解
- @Null:被注释的元素必须为null
- @NotNull:被注释的元素不能为null
- @AssertTrue:该字段只能为true
- @AssertFalse:该字段的值只能为false
- @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @DecimalMin(“value”):被注释的元素必须是一个数字,验证小数的最小值
- @DecimalMax(“value”):被注释的元素必须是一个数字,验证小数的最大值
- @Size(max,min):查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
- @Past:被注释的元素必须是一个过去的日期
- @Future:被注释的元素必须是一个将来的日期
- @Pattern(regexp =“[abc]”):被注释的元素必须符合指定的正则表达式。
- @Email:被注释的元素必须是电子邮件地址
- @Length(max=5,min=1,message=“长度在1~5”):检查所属的字段的长度是否在min和max之间,只能用于字符串
- @NotEmpty:被注释的字符串必须非空
- @Range:被注释的元素必须在合适的范围内
- @NotBlank:不能为空,检查时会将空格忽略
- @NotEmpty:不能为空,这里的空是指空字符串
【五】@Pattern的用法和常用的正则表达式
关于注解中需要传的参数:一般默认就填入正则表达式即可,但是java中字符串需要转义,这个需要注意一下。
常用的正则表达式:
1 匹配首尾空格的正则表达式:(^\s*)|(\s*$)
2 整数或者小数:^[0-9]+\.{0,1}[0-9]{0,2}$
3 只能输入数字:"^[0-9]*$"。
4 只能输入n位的数字:"^\d{n}$"。
5 只能输入至少n位的数字:"^\d{n,}$"。
6 只能输入m~n位的数字:。"^\d{m,n}$"
7 只能输入零和非零开头的数字:"^(0|[1-9][0-9]*)$"。
8 只能输入有两位小数的正实数:"^[0-9]+(.[0-9]{2})?$"。
9 只能输入有1~3位小数的正实数:"^[0-9]+(.[0-9]{1,3})?$"。
10 只能输入非零的正整数:"^\+?[1-9][0-9]*$"。
11 只能输入非零的负整数:"^\-[1-9][]0-9"*$。
12 只能输入长度为3的字符:"^.{3}$"。
13 只能输入由26个英文字母组成的字符串:"^[A-Za-z]+$"。
14 只能输入由26个大写英文字母组成的字符串:"^[A-Z]+$"。
15 只能输入由26个小写英文字母组成的字符串:"^[a-z]+$"。
16 只能输入由数字和26个英文字母组成的字符串:"^[A-Za-z0-9]+$"。
17 只能输入由数字、26个英文字母或者下划线组成的字符串:"^\w+$"。
18 验证用户密码:"^[a-zA-Z]\w{5,17}$"正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。
19 验证是否含有^%&',;=?$\"等字符:"[^%&',;=?$\x22]+"。
20 只能输入汉字:"^[\u4e00-\u9fa5]{0,}$"
21 验证Email地址:"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"。
22 验证InternetURL:"^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"。
23 验证电话号码:"^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$"正确格式为:"XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX"。
24 验证身份证号(15位或18位数字):"^\d{15}|\d{18}$"。
25 验证一年的12个月:"^(0?[1-9]|1[0-2])$"正确格式为:"01"~"09"和"1"~"12"。
26 验证一个月的31天:"^((0?[1-9])|((1|2)[0-9])|30|31)$"正确格式为;"01"~"09"和"1"~"31"。
27 匹配中文字符的正则表达式: [\u4e00-\u9fa5]
28 匹配双字节字符(包括汉字在内):[^\x00-\xff]
29 应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
30 String.prototype.len=function(){return this.replace(/[^\x00-\xff]/g,"aa").length;}
31 匹配空行的正则表达式:\n[\s| ]*\r
32 匹配html标签的正则表达式:<(.*)>(.*)<\/(.*)>|<(.*)\/>
【六】待补充
(1)@Valid和@Validated的区别
(2)分组校验
(3)嵌套校验
(4)配合全局异常捕获
【七】@Vaild无法验证递归验证List
@NotBlank注解是用来验证字符串类型参数是否为空或者空格。然而,在List类型的参数上使用@NotBlank注解是不生效的,因为@NotBlank注解只能用在字符串类型的参数上。
@PostMapping
public String doIt(@RequestBody @Valid List<User> users) {
return "SUCCESS";
}
User类里加@NotBlank等注解都是不会生效的
(1)方法一(实测有效)
原因是,(ArrayList)内部通过持有一个数组来保存对象们,而作为Java官方的类,内部肯定不会在数组上声明@Valid,所以内部的对象们没有得到应有的递归校验。
所以,考虑使用一种新的实现,来变相的达到列表校验的效果。
新增下面这个工具类
import lombok.Data;
import javax.validation.Valid;
import java.util.*;
/**
*
* @param <E>
* Author zkn
*/
@Data
public class ValidableList<E> implements List<E> {
@Valid
private List<E> list = new LinkedList<>();
@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);
}
}
然后把接收参数的List换成ValidableList
public ResResult saveGuardianeApp(@Valid @RequestBody ValidableList<GuardianAddReq> guardianAddReqs,
BindingResult br) {}
(2)方法二
如果你想要验证List中的每个元素是否为空或空格,你可以使用@Valid注解结合自定义的校验注解来实现。首先,你需要创建一个自定义的校验注解,例如@NotEmptyString:
@Documented
@Constraint(validatedBy = NotEmptyStringValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@NotBlank(message = "String must not be empty or blank")
public @interface NotEmptyString {
String message() default "String must not be empty or blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
然后,你需要创建一个对应的校验器类NotEmptyStringValidator来实现具体的校验逻辑,例如:
public class NotEmptyStringValidator implements ConstraintValidator<NotEmptyString, String> {
@Override
public void initialize(NotEmptyString constraintAnnotation) {
// 初始化操作
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && !value.trim().isEmpty();
}
}
接下来,你可以在ParamDictCreateAndUpdateRequest类中使用@NotEmptyString注解来验证参数:
public class ParamDictCreateAndUpdateRequest {
@NotEmptyString
private String param;
// 其他字段...
}
最后,你需要在Controller方法中使用@Valid注解来触发参数校验:
@PostMapping("/your/api/path")
public void yourApiMethodName(@RequestBody @Valid List<ParamDictCreateAndUpdateRequest> requestList) {
// 处理请求
}
这样,当你传入的List中的param字段为空或者空格时,将会触发校验失败并返回错误信息。