目录

Spring Boot - 参数校验

# 前言

数据的校验是网站一个不可或缺的功能,前端的 js 校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用 http 工具直接向后端请求一些违法数据,服务端的数据校验也是必要的。

所以我们可以在 Controller 进行参数校验,只有通过了校验,才能进入 Controller 的方法里。

# Controller 中方法参数校验示例

使用 @Valid 或 @Validated 注解。

# Maven 依赖

  • @Valid:新版本的 springBoot 需要手动引入下面的依赖,老版本只需引入 spring-boot-starter-web 即可,里面集成了 Hibernate-Validator
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.13.Final</version>
</dependency>
1
2
3
4
5
  • @Validated:是对 hibernate-validator 的封装,是 spring 提供的校验机制,只要引入 spring-context 依赖即可

# 实体类字段加上相关注解

public class User implements Serializable {

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 200, message = "年龄不合法")
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
	
	// setter,getter 方法省略......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • @NotBlank:只用于字符串,字符串不能为 null,并且去除两端空白字符后的长度大于 0,例:""" "
  • @NotEmpty:只用于字符串、集合、map、数组,且不能为 null,并且长度或者大小大于 1
  • @NotNull:适用于所有类型,且不能为 null
  • @AssertTrue:被注释的元素必须为 true
  • @AssertFalse:被注释的元素必须为 false
  • @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max,min):被注释的元素的大小必须在指定的范围内
  • @Email:被注释的元素必须是电子邮件地址
  • @Length:被注释的字符串的大小必须在指定的范围内
  • @Range:被注释的元素必须在合适的范围内

注意:如果是 String 类型,使用 @NotBlank 而不是 NotNull 或者 @NotEmpty,如果字符串是 "",两个后者无法判断为空。

# @Valid 或 @Validated 注解

在 Controller 方法参数加上 @Valid 或 @Validated 注解

@Controller
public class UserController {

    @RequestMapping(path = "validatorUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult validatorUser(@Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 用于获取相应字段上添加的 message 中的内容
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", user);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 多个参数校验

public Objet test(@Validated Object param1, BindingResult result1 ,@Validated Object param2, BindingResult Result2) {
	// ......
}
1
2
3

# @Validated 注解特有的功能

@Valid 不具备的功能,而注解 @Validated 注解特有的功能:分组校验。源码分别如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

    Class<?>[] value() default {};
}


@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 参数分组校验

当一个实体类需要多种验证方式时,例如:对于一个实体类的 id 来说,新增的时候是不需要的,对于更新时是必须的。可以通过 groups 对验证进行分组。

分组接口(空接口)

通过向 groups 分配不同类的 class 对象,达到分组目的

public interface UserServiceInsert {
}

public interface UserServiceUpdate {
}
1
2
3
4
5

实体类

public class UserInfo implements Serializable {

    @Null(message = "新增时id必须为空", groups = {UserServiceInsert.class})
    @NotNull(message = "更新时id不能为空", groups = {UserServiceUpdate.class})
    @NotNull(message = "删除时 id 不允许为空", groups = UserServiceDelete.class)
    private Integer id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 200, message = "年龄不合法")
    private Integer age;

    public UserInfo(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
	
	// setter,getter 方法省略......
    
    public interface UserServiceInsert {
    }

    public interface UserServiceUpdate {
    }
    
    public interface UserServiceDelete {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Controller 控制类

@Controller
public class UserController {

    @RequestMapping(path = "insertUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult insertUser(@Validated(value = UserServiceInsert.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", userInfo);
    }

    @RequestMapping(path = "updateUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult updateUser(@Validated(value = UserServiceUpdate.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", userInfo);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • @Validated 添加了 groups 属性时,其只会校验实体分组的属性。如只会校验 UserInfo 中的 id 属性,而不会校验 name,age 属性
  • 不加 groups 时,不会校验有 groups 的属性

# 嵌套校验

@Valid 加在方法参数时,不会自动进行嵌套验证,而是用在需要嵌套验证类内的相应字段上,来配合方法参数上 @Validated@Valid 来进行嵌套验证

实体类

public class Teacher implements Serializable {

    @NotEmpty(message = "Teacher 姓名不能为空")
    private String teacher_name;

    @NotNull(message = "Teacher 年龄不能为空")
    @Range(min = 0, max = 200, message = "Teacher 年龄不合法")
    private Integer teacher_age;

    @Valid
    @Size(min = 1, max = 10, message = "列表中的元素数量为1~10")
    private List<Student> students;

    public Teacher(String teacher_name, Integer teacher_age, List<Student> students) {
        this.teacher_name = teacher_name;
        this.teacher_age = teacher_age;
        this.students = students;
    }
	
	// setter,getter 方法省略......
}


public class Student implements Serializable {

    @Valid
    @NotEmpty(message = "Student 姓名不能为空")
    private String name;

    @Valid
    @NotNull(message = "Student 年龄不能为空")
    @Range(min = 0, max = 200, message = "Student 年龄不合法")
    private Integer age;

    public Student(String name, Integer age) {
        this.age = age;
        this.name = name;
    }

	// setter,getter 方法省略......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

Controller 控制类

@Controller
public class UserController {

    @RequestMapping(path = "nestValid", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult nestValid(@Validated @RequestBody Teacher teacher, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", teacher);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 对象集合校验

如果 Controller 的参数是一个 List 集合的对象,则该对象不会执行校验,如下:

@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody List<GenericCategory> genericCategory) {
    GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
    return HttpResult.okOrFail(category);
}
1
2
3
4
5

参数是 List<GenericCategory> genericCategory,而 GenericCategory 实体类里的校验是不会起效的,这是 List 导致的,那么如何解决呢?

既然 @Validated 只能对单个实体类校验,那么我们就自定义一个实体类,实现集合的效果,如下:

// @Data 和 @NoArgsConstructor 是 Lombok 带有的,自动生成 setter、getter 等方法,生成无参构造器
@Data
@NoArgsConstructor
public class ValidList<E> implements List<E> {
 
    @Valid
    private List<E> list = new LinkedList<>();
 
    public ValidList(List<E> paramList) {
        this.list = paramList;
    }
 
    @Override
    public int size() {
        return list.size();
    }
 
    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }
 
    @Override
    public boolean contains(Object o) {
        return list.contains(0);
    }
 
    @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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

ValidList 就是一个对 List 的封装类,封装了大部分常用方法,如果没有你需要的方法,重写就行。

使用:

@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody ValidList<GenericCategory> genericCategory) {
    GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
    return HttpResult.okOrFail(category);
}
1
2
3
4
5

将 List 改成 ValidList 即可。

# 控制分组校验顺序

@GroupSequence 它是 JSR 标准提供的注解,可以按指定的分组先后顺序进行验证;前面的分组校验不通过,后面的分组校验就不执行。

  • 如:@GroupSequence({One.class, Two.class, Three.class}) 先执行 One 分组校验,然后执行 Two 分组校验。如果 One 分组校验失败了,则不会进行 Two 分组的校验。即必须第一个组校验正确了,才执行第二组校验
@Data
@GroupSequence({One.class, Two.class, UserVO.class})
public class UserVO {
    
    @NotNull(message = "姓名不能为空", groups = {One.class})
    @Size(min = 1, max = 10, message = "姓名的长度在1-10之间", groups = {Two.class})
    private String name;

    @NotNull(message = "年龄不能为空", groups = {One.class})
    @Min(value = 1, message = "年龄不能小于1岁", groups = {Two.class})
    @Max(value = 200, message = "年龄不能大于200岁", groups = {Two.class})
    private Integer age;

    @NotNull(message = "性别不能为空", groups = {One.class})
    @Min(value = 0, message = "性别取值不能小于0", groups = {Two.class})
    @Max(value = 1, message = "性别取值不能大于1", groups = {Two.class})
    private Integer sex;
    
    public interface One {}
    
    public interface Two {}
    
    public interface UserVO {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

测试接口

@PostMapping("/test/group")
public String userVO(@RequestBody @Validated UserVO form) {
    return "success";
}
1
2
3
4

# @Validated 类和参数区别

@Validated 可以在 Controller 类上添加,也可以在方法的参数里添加,那么两者区别是什么?

  • 作用在方法上,用于校验实体类里的规则
  • 作用在类上,用于校验类的方法参数规则

注意:如果方法的参数是实体类,那么作用在类上,则不会校验,需要同时在方法里添加 @Validated。

那么 @Validated 作用在类上的作用是什么呢?

能够校验基本类型的参数,如:

@RestController
@Validated
public class GenericUserController {
    
    @PostMapping("/updateGenericUserRole")
    public Response updateGenericUserRole(@NotBlank(message = "无效的参数") String username, @NotNull(message = "无效的参数") Integer projectId, @NotBlank(message = "无效的参数") String roleCode) {
        boolean isSuccess = genericUserService.updateGenericUserRole(username, projectId, roleCode);
        if (!isSuccess) {
            return HttpResult.fail("更新角色失败");
        }
        return HttpResult.ok("更新角色成功");
    }   
}
1
2
3
4
5
6
7
8
9
10
11
12
13

此时 usernameprojectIdroleCode 的校验就会生效。

如果参数是实体类,则不会校验规则。

@RestController
@Validated
public class GenericUserController {
    @PostMapping("/test/group")
    public String userVO(@RequestBody UserVO form) {
        return "success";
    }
}
1
2
3
4
5
6
7
8

UserVO 里的规则不会起效果,所以我们需要单独加上 @Validated

@RestController
@Validated
public class GenericUserController {
    @PostMapping("/test/group")
    public String userVO(@Validated @RequestBody UserVO form) {
        return "success";
    }
}
1
2
3
4
5
6
7
8

# 自定义校验注解

业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验注解来满足我们的需求。

# 注解 1

如: 添加一个用于校验字符串不能包含空格的校验注解 @CanNotHaveBlank。

  1. 自定义校验注解,并且通过 validatedBy 指定了这个注解真正的验证类

    /**
    * 参考系统提供的校验注解可知,message,groups,payload 这 3 个属性必须提供
    * @Constraint(validatedBy = CanNotHaveBlankValidator.class) 指定该注解的校验类
    */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    //指定了当前注解使用哪个校验类来进行校验。
    @Constraint(validatedBy = {CanNotHaveBlankValidator.class})
    public @interface CanNotHaveBlank {
    
        // 默认错误消息
        String message() default "不能包含空格";
    
        // 分组
        Class<?>[] groups() default {};
    
        // 负载
        Class<? extends Payload>[] payload() default {};
    
        // 指定多个时使用
        @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        @interface List {
            CanNotHaveBlank[] value();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

注意: message 用于显示错误信息这个字段是必须的,groupspayload 也是必须的。

@Constraint(validatedBy = {HandsomeBoyValidator.class}) 用来指定处理这个注解逻辑的类。

  1. 编写验证类 CanNotHaveBlankValidator

    • 所有的验证类都需要实现 ConstraintValidator<注解类型,校验bean类型> 接口,实现该接口必须指定对应的注解类型以及校验 Bean 的类型,接口包含一个初始化事件方法 initialize,和一个判断是否合法的方法 isValid
    • isValid() 中的 ConstraintValidatorContext 包含了认证中所有的信息,可以利用这个上下文获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作请附上原文出处链接及本声明
    public interface ConstraintValidator<A extends Annotation, T> {
      default void initialize(A constraintAnnotation) { }
      boolean isValid(T var1, ConstraintValidatorContext var2);
    }
    
    1
    2
    3
    4
    public class CanNotHaveBlankValidator implements ConstraintValidator<CanNotHaveBlank, String> {
        /**
        * 初始化获取注解的值
        * @param constraintAnnotation
        */
        @Override
        public void initialize(CannotHaveBlank constraintAnnotation) { 
            ConstraintValidator.super.initialize(constraintAnnotation);
        }
    
        /**
        * 初始化获取注解的值
        * @param value 要校验的字段值
        * @param context 校验上下文
        * @return true 校验通过、false 校验失败
        */
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            // null 时不进行校验
            if (value != null && !value.contains(" ")) {
                // 获取默认提示信息
                String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
                System.out.println("default message :" + defaultConstraintMessageTemplate);
                // 禁用默认提示信息
                context.disableDefaultConstraintViolation();
                // 设置提示语
                context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
                return false;
            }
            return true;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

CannotHaveBlank 是自己定义的注解。

  1. 表单数据

    @Data
    public class CustomForm {
    	// 电话号码
        @CanNotHaveBlank
        private String phone;
    }
    
    1
    2
    3
    4
    5
    6

# 注解 2

对状态的校验: status: 1 或者 2.

  1. 自定义注解:

    /**
     * 使用方式:
     * @IncludeValid(value = {"0","1"}, message = "状态值必须为 0, 1")
     * 
     *
     * 校验值是否为指定的值
     *
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Constraint(validatedBy = IncludeValidatorClass.class)
    public @interface IncludeValid {
        String[] value() default {};
    
        String message() default "flag is not found";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
        
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  2. 具体的逻辑校验

    /**
     * 校验值是否为指定的值, 处理类
     *
     */
    public class IncludeValidatorClass implements ConstraintValidator<IncludeValid, Integer> {
        private String[] values;
    
        @Override
        public void initialize(IncludeValid constraintAnnotation) {
            this.values = constraintAnnotation.value();
        }
    
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
            boolean isValid = false;
            if (null == value) {
                return true;
            }
            for (int i = 0; i < values.length; i++) {
                if (values[i].equals(String.valueOf(value))) {
                    isValid = true;
                    break;
                }
            }
            return isValid;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
  3. 使用方式

    @NotEmpty(message = "用户名不能为空")
    @ApiModelProperty("用户名")
    private String name;
    
    // 自定义的校验
    @IncludeValid(value = {"1", "2"}, message = "用户的状态不正确")
    private Integer status;
    
    1
    2
    3
    4
    5
    6
    7

# 注解 3

手机号码验证

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {
	
	boolean required() default true;
 
	String message() default "手机号格式不正确";
 
	Class<?>[] groups() default {};
 
	Class<? extends Payload>[] payload() default {};
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

逻辑校验

public class MobileValidator implements ConstraintValidator<IsMobile, String>{
 
	@Override
	public void initialize(IsMobile constraintAnnotation) {
		ConstraintValidator.super.initialize(constraintAnnotation);
	}
	
	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		// 关于手机号的验证偷个懒 不为空且长度不是 11 位的字符串 就被认为是非法
		if(value != null && value.length() != 11) {
			System.out.println("手机号非法");
			return false;
		}
		return true;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

使用

@Data
public class User {
    
    // ...
	
	@IsMobile(message = "手机号格式非法")
	private String mobile;
}
1
2
3
4
5
6
7
8

# 自定义分组校验@GroupSequenceProvider

@GroupSequence只能在类中事先定义校验分组的顺序。

  • 如遇到这种需求: 当 type 值为 A,paramA 值必传。type 值为 B,paramB 值必须传,单独使用分组校验和控制分组校验顺序都无法满足需求。需要使用 @GroupSequenceProvider

定义校验类

@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroupForm {
    // 类型
    @Pattern(regexp = "[A|B]", message = "类型不必须为 A|B")
    private String type;

    //参数 A
    @NotEmpty(message = "参数A不能为空", groups = {WhenTypeIsA.class})
    private String paramA;

    // 参数 B
    @NotEmpty(message = "参数B不能为空", groups = {WhenTypeIsB.class})
    private String paramB;

    // 分组 A
    public interface WhenTypeIsA { }
    // 分组 B
    public interface WhenTypeIsB { }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

实现 DefaultGroupSequenceProvider 接口,编写分组校验逻辑

public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> {
    @Override
    public List<Class<?>> getValidationGroups(CustomGroupForm form) {
        List<Class<?>> defaultGroupSequence = new ArrayList<>();
        // 默认分组
        defaultGroupSequence.add(CustomGroupForm.class);

        // 如果类型值为 A 使用 A 分组 WhenTypeIsA
        if (form != null && "A".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class);
        }
        // 如果类型值为 B 使用 B 分组 WhenTypeIsB
        if (form != null && "B".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class);
        }
        // 返回分组
        return defaultGroupSequence;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 手动校验

在某些场景下需要我们手动校验 bean,用校验器对需要被校验的 bean 发起 validate 获得校验结果

  • 既可以使用 Hibernate Validation 提供 Validator,也可以使用 Spring ValidationValidator

使用 Hibernate Validation 提供 Validator

public class ValidationTest {
    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.setUsername(null);
        foo.setPassword(null);
        foo.setUserType("");

		// 构建 Validator
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
		// 使用 Validator 校验 bean
        Set<ConstraintViolation<Foo>> set = validator.validate(foo);
        for (ConstraintViolation<Foo> constraintViolation : set) {
            System.out.println(constraintViolation.getMessage());
        }
    }

    @Data
    public static class Foo {
    
        @NotNull(message = "username不能为空")
        private String username;

        @NotNull(message = "password不能为空")
        private String password;

        @NotBlank(message = "userType不能为空")
        private String userType;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

使用 Spring ValidationValidator

也可以使用配置类初始化 LocalValidatorFactoryBean,然后从 Spring 容器中获取

@Component
@Configuration
public class GlobalWebConfig {

 	@Bean
	public Validator validator() {
   		return new LocalValidatorFactoryBean();
	}
}
1
2
3
4
5
6
7
8
9

如果使用 springBootLocalValidatorFactoryBean 已经成为了 Validator 的默认实现,使用时只需要自动注入即可

@Autowired 
Validator globalValidator;
1
2

# 工具类 ValidatorUtils

/**
 *  为什么要使用这个工具类呢?
 *   1、controller方法中不用加入BindingResult参数
 *   2、controller方法中需要校验的参数也不需要加入@Valid或者@Validated注解
 * <p>
 *  具体使用
 * 在controller方法或者全局拦截校验器中调用 ValidatorUtils.validateResultProcess(需要校验的Bean) 直接获取校验的结果。
 *
 **/
@Component
public class ValidatorUtils implements ApplicationContextAware {
    //jackson的对象映射类
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static Validator validator;

    /*
     * 校验bean并返回所有验证失败信息
     * @param obj 当前校验对象
     * @param groups 当前校验的组,非必传,不传按照默认分组校验
     * @return 如: Optional[[{"propertyPath":"Foo.password","message":"password为NULL"},{"propertyPath":"Foo.userType","message":"userType为BLANK"}]]
     * @throws ServiceException
     */
    public static Optional<String> validateResultProcess(Object obj, Class<?>... groups) throws ServiceException {
        // 用验证器执行验证,返回一个验证失败的set集合
        Set<ConstraintViolation<Object>> results = validator.validate(obj,groups);

        // 判断是否为空,空:说明验证通过,否则就验证失败
        if (CollectionUtils.isEmpty(results)) {
            return Optional.empty();
        }

        List<ErrorMessage> errorMessages = results.stream()
                //将results转换成 List<ErrorMessage>返回
                .map(result -> {
            try {
                List<ErrorMessage> childErrorMessages = objectMapper.readValue(result.getMessage(), new TypeReference<List<ErrorMessage>>() {
                });
                return childErrorMessages;
            } catch (Exception e) {
                ErrorMessage errorMessage = new ErrorMessage();
                errorMessage.setPropertyPath(String.format("%s.%s", result.getRootBeanClass().getSimpleName(), result.getPropertyPath().toString()));
                errorMessage.setMessage(result.getMessage());
                return Arrays.asList(errorMessage);
            }
        })
                //合并 map操作转换成的多个 List<ErrorMessage>为一个
                .flatMap(errorMessageList -> errorMessageList.stream())
                .collect(Collectors.toList());

        try {
            return Optional.of(objectMapper.writeValueAsString(errorMessages));
        } catch (JsonProcessingException e) {
            throw new ServiceException("JsonProcessingException " + e.getMessage());
        }
    }

    /**
     * 校验bean校验失败抛出自定义异常 ServiceException
     * @param obj 当前校验对象
     * @param groups 当前校验的组,非必传,不传按照默认分组校验
     * @throws ServiceException
     */
    public static void validateResultProcessWithException(Object obj, Class<?>... groups) throws ServiceException {
        Optional<String> validateResult = ValidatorUtils.validateResultProcess(obj,groups);
        if (validateResult.isPresent()) {
            throw new ServiceException(validateResult.get());
        }
    }
    
    /**
     * 初始化validator 对象
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //获取Hibernate validator 的 validator
        //ValidatorUtils.validator = Validation.buildDefaultValidatorFactory().getValidator();

        //通过Spring 封装的 LocalValidatorFactoryBean获取validator
        ValidatorUtils.validator = (Validator) applicationContext.getBean("validator");
           /*
            @Bean
            public Validator validator() {
                return new LocalValidatorFactoryBean();
             }
            */
    }

    /**
     * 校验分组
     */
    public static class ValidatorGroup {
        public interface First extends Default { }
        public interface Second extends Default { }
        public interface Third extends Default { }
    }

    /**
     * 错误信息封装
     */
    public static class ErrorMessage {
        private String propertyPath;
        private String message;

        public String getPropertyPath() { return propertyPath; }
        public void setPropertyPath(String propertyPath) { this.propertyPath = propertyPath; }

        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

# 使用 ValidatorUtils 校验 bean

public void test() {
    // 新增时 id 必须为空
    UserForm addForm = new UserForm();
    addForm.setId("1");
    addForm.setName("张三");
    addForm.setAge(12);
    // 校验 bean 并返回所有验证失败信息
    Optional<String> addResult =  ValidatorUtils.validateResultProcess(addForm, One.class);
    System.out.println(addResult);

    // 更新时id不能为空
    UserForm updateForm = new UserForm();
    updateForm.setId(null);
    addForm.setName("张三");
    updateForm.setAge(12);
    // 校验 bean 并返回所有验证失败信息
    Optional<String> updateResult =  ValidatorUtils.validateResultProcess(updateForm, Two.class);
    System.out.println(updateResult);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

返回结果

Optional[[{"propertyPath":"UserForm.id","message":"新增时id必须为空"}]]
Optional[[{"propertyPath":"UserForm.id","message":"更新时id不能为空"}]]
1
2

# 参数校验常用注解

注解 说明
@Null 限制只能为 null
@NotNull 限制必须不为 null
@NotEmpty 验证注解的元素值 不为 null 且不为空字符串长度不为 0、集合大小不为 0)(主要用于:String,Collection,Map,array)
@NotBlank 只支持字符串类型字段,验证注解的元素值 不为空不为 null、去除首位空格后长度为 0),不同于 @NotEmpty,@NotBlank 只应用于字符串,且在比较时会去除字符串的空格
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在 min 到 max 之间,(主要用于:String,Collection,Map and array)
@Range(min, max) 被注释的元素必须在合适的范围内(主要用于:BigDecimal,BigInteger,String,byte,short,int,long,原始类型的包装类)
@Length(min, max) 被注解的对象必须是字符串,大小必须在制定的范围内
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction
@AssertFalse 限制必须为 false
@AssertTrue 限制必须为 true
@Future 限制必须是一个将来的日期
@Past 验证注解的元素值(日期类型)比当前时间早
@Email 验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 Email 格式
更新时间: 2024/01/17, 05:48:13
最近更新
01
JVM调优
12-10
02
jenkins
12-10
03
Arthas
12-10
更多文章>