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>
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 方法省略......
}
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);
}
}
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) {
// ......
}
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 {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 参数分组校验
当一个实体类需要多种验证方式时,例如:对于一个实体类的 id
来说,新增的时候是不需要的,对于更新时是必须的。可以通过 groups
对验证进行分组。
分组接口(空接口)
通过向 groups
分配不同类的 class
对象,达到分组目的
public interface UserServiceInsert {
}
public interface UserServiceUpdate {
}
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 {
}
}
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);
}
}
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 方法省略......
}
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);
}
}
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);
}
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);
}
}
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);
}
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 {}
}
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";
}
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("更新角色成功");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
此时 username
、projectId
、roleCode
的校验就会生效。
如果参数是实体类,则不会校验规则。
@RestController
@Validated
public class GenericUserController {
@PostMapping("/test/group")
public String userVO(@RequestBody UserVO form) {
return "success";
}
}
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";
}
}
2
3
4
5
6
7
8
# 自定义校验注解
业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验注解来满足我们的需求。
# 注解 1
如: 添加一个用于校验字符串不能包含空格的校验注解 @CanNotHaveBlank。
自定义校验注解,并且通过
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
用于显示错误信息这个字段是必须的,groups
和 payload
也是必须的。
@Constraint(validatedBy = {HandsomeBoyValidator.class})
用来指定处理这个注解逻辑的类。
编写验证类
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
4public 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 是自己定义的注解。
表单数据
@Data public class CustomForm { // 电话号码 @CanNotHaveBlank private String phone; }
1
2
3
4
5
6
# 注解 2
对状态的校验: status: 1 或者 2.
自定义注解:
/** * 使用方式: * @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具体的逻辑校验
/** * 校验值是否为指定的值, 处理类 * */ 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使用方式
@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 {};
}
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;
}
}
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;
}
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 { }
}
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;
}
}
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 Validation
的Validator
使用
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;
}
}
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 Validation
的Validator
也可以使用配置类初始化 LocalValidatorFactoryBean,然后从 Spring 容器中获取
@Component
@Configuration
public class GlobalWebConfig {
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
}
2
3
4
5
6
7
8
9
如果使用 springBoot
,LocalValidatorFactoryBean
已经成为了 Validator
的默认实现,使用时只需要自动注入即可
@Autowired
Validator globalValidator;
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; }
}
}
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);
}
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不能为空"}]]
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 格式 |