.
*/
package javax.validation.groups;
/**
* Default Bean Validation group.
*
* Unless a list of groups is explicitly defined:
*
* - constraints belong to the {@code Default} group
* - validation applies to the {@code Default} group
*
* Most structural constraints should belong to the default group.
*
* @author Emmanuel Bernard
*/
public interface Default {
}
```
把约束分组可以让我们在对bean进行验证时可以更灵活。
比如:同样一个bean,在新增和修改两个业务场景中需要验证的属性是不一样的(如新增不需要验证id,修改时则需要)
```java
package org.example;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@Builder(toBuilder = true)
public class Student {
@NotNull(groups = ModGroup.class)
public String no;
@NotEmpty(groups = {AddGroup.class, ModGroup.class})
public String name;
}
```
```java
package org.example;
public interface AddGroup {
}
```
```java
package org.example;
public interface ModGroup {
}
```
```java
package org.example;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.text.MessageFormat;
import java.util.Set;
public class TestStudent {
@Test
public void testBeanValidationGroup() {
Student student = Student.builder().build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(student, AddGroup.class);
for (ConstraintViolation studentConstraintViolation : validate) {
// property:name,error message:must not be empty
System.out.println(MessageFormat.format("property:{0},error message:{1}", studentConstraintViolation.getPropertyPath().toString(), studentConstraintViolation.getMessage()));
}
}
}
```
当然还可以指定多个组
```java
Validation.buildDefaultValidatorFactory().getValidator().validate(student, AddGroup.class,ModGroup.class);
```
如果设置了groups,但是校验的时候不指定groups,则默认检查Default组,所以下面这个代码不会检测出任何的error
```java
@Test
public void testBeanValidationGroup() {
Student student = Student.builder().build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(student);
for (ConstraintViolation studentConstraintViolation : validate) {
System.out.println(MessageFormat.format("property:{0},error message:{1}", studentConstraintViolation.getPropertyPath().toString(), studentConstraintViolation.getMessage()));
}
}
```
## `@GroupSequence`组序列
`@GroupSequence`它是`JSR`标准提供的注解
顾名思义,它表示`Group组序列`。**默认情况下,不同组别的约束验证是无序的** 在某些情况下,约束验证的**顺序**是非常的重要的,比如如下两个场景:
1. 第二个**组**的约束验证依赖于第一个约束执行完成的结果(必须第一个约束正确了,第二个约束执行才有意义)
2. 某个Group组的校验非常耗时,并且会消耗比较大的CPU/内存。那么我们的做法应该是把这种校验**放到最后**,所以对顺序提出了要求
**一个组可以定义为其他组的序列**,使用它进行验证的时候必须符合该序列规定的顺序。`在使用组序列验证的时候`,**如果序列前边的组验证失败**,则后面的组将不再给予验证。
```java
package org.example;
import lombok.Builder;
import lombok.Data;
import javax.validation.GroupSequence;
import javax.validation.constraints.NotEmpty;
import javax.validation.groups.Default;
@Data
@Builder(toBuilder = true)
public class User {
@NotEmpty(message = "firstName may be empty")
private String firstName;
@NotEmpty(message = "middleName may be empty", groups = Default.class)
private String middleName;
@NotEmpty(message = "lastName may be empty", groups = GroupA.class)
private String lastName;
@NotEmpty(message = "country may be empty", groups = GroupB.class)
private String country;
public interface GroupA {
}
public interface GroupB {
}
// 组序列
@GroupSequence({Default.class, GroupA.class, GroupB.class})
public interface Group {
}
}
```
```java
package org.example;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.text.MessageFormat;
import java.util.Set;
public class TestUser {
@Test
public void testGroupSequence() {
User user = User.builder().build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class);
// property: middleName, error message: middleName may be empty, invalid value: null
// property: firstName, error message: firstName may be empty, invalid value: null
validate.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
@Test
public void testGroupSequence2() {
User user = User.builder().firstName("k").middleName("c").build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class);
// property: lastName, error message: lastName may be empty, invalid value: null
validate.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
@Test
public void testGroupSequence3() {
User user = User.builder().firstName("k").middleName("c").lastName("g").build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class);
// property: country, error message: country may be empty, invalid value: null
validate.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
}
```
## 级联校验
通过使用`@Valid`可以实现递归验证,因此可以标注在`List`上,对它里面的每个对象都执行校验
```java
package org.example;
import lombok.Builder;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
@Builder(toBuilder = true)
public class Person {
@NotNull
private String name;
@NotNull
@Range(min = 10, max = 40)
private Integer age;
@Valid
@NotNull
@Size(min = 3, max = 5)
private List childList;
@Valid
@NotNull
private Child child;
}
@Data
@Builder(toBuilder = true)
class Child {
@NotNull
private String name;
}
```
```java
package org.example;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Set;
public class TestPerson {
@Test
public void testPersonValidation() {
Person person = Person.builder()
.child(Child.builder().build())
.childList(new ArrayList() {{
add(Child.builder().build());
}}).build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(person);
// property: child.name, error message: must not be null, invalid value: null
// property: age, error message: must not be null, invalid value: null
// property: name, error message: must not be null, invalid value: null
// property: childList, error message: size must be between 3 and 5, invalid value: [Child(name=null)]
// property: childList[0].name, error message: must not be null, invalid value: null
validate.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
}
```
## 方法约束声明和验证,ExecutableValidator
从Bean Validation 1.1开始,约束不仅可以应用于JavaBean及其属性,而且可以应用于任何Java类型的方法和构造函数的参数和返回值
```java
package org.example;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
@Builder(toBuilder = true)
public class Teacher {
private String name;
public Teacher(@NotNull String name) {
this.name = name;
}
public void teach(@NotBlank String content) {
System.out.println(content);
}
public @Size(min = 5, max = 10) String getTeacherName(String name) {
return name;
}
}
```
```java
package org.example;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.executable.ExecutableValidator;
import java.text.MessageFormat;
import java.util.Set;
public class TestTeacher {
@Test
public void testConstructorValidation() throws NoSuchMethodException {
ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
Set> constraintViolations = executableValidator.validateConstructorParameters(Teacher.class.getConstructor(String.class), new Object[]{null});
// property: Teacher.arg0, error message: must not be null, invalid value: null
constraintViolations.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
@Test
public void testMethodValidation() throws NoSuchMethodException {
Teacher teacher = Teacher.builder().build();
ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
Set> constraintViolations = executableValidator.validateParameters(teacher, Teacher.class.getMethod("teach", String.class), new Object[]{null});
// property: teach.arg0, error message: must not be blank, invalid value: null
constraintViolations.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
@Test
public void testReturnValueValidation() throws NoSuchMethodException {
Teacher teacher = Teacher.builder().build();
ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
Set> constraintViolations = executableValidator.validateReturnValue(teacher, Teacher.class.getMethod("getTeacherName", String.class), "CC");
// property: getTeacherName., error message: size must be between 5 and 10, invalid value: CC
constraintViolations.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
}
```
## 自定义约束
1.创建约束注解
```java
package org.example;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UpperCaseValidator.class)
public @interface UpperCase {
String message() default "must upper";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
```
2.编写验证器(Implement ConstraintValidator)
```java
package org.example;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UpperCaseValidator implements ConstraintValidator {
@Override
public void initialize(UpperCase constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return value.equals(value.toUpperCase());
}
}
```
```java
package org.example;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.text.MessageFormat;
import java.util.Set;
public class TestEngineer {
@Test
public void testConstraintValidator() {
Engineer engineer = Engineer.builder().name("test").build();
Set> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(engineer);
// property: name, error message: must upper, invalid value: test
validate.stream().map(v -> MessageFormat.format("property: {0}, error message: {1}, invalid value: {2}", v.getPropertyPath().toString(), v.getMessage(), v.getInvalidValue())).forEach(System.out::println);
}
}
```
## 参考
[参数校验 Hibernate-Validator](https://juejin.im/post/6844903961581846535)
https://hibernate.org/validator/documentation/
[Hibernate Validator 6.1.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/)
[Spring 参数校验详解](https://juejin.im/post/6844904003310977031)
[深入了解数据校验:Java Bean Validation 2.0(JSR380)](https://juejin.im/post/6844903895613833229)
[分组序列@GroupSequenceProvider、@GroupSequence控制数据校验顺序,解决多字段联合逻辑校验问题](https://cloud.tencent.com/developer/article/1497756)
[Bean Validation和Hibernate Validator](https://www.jianshu.com/p/e4c99dc9df5a)
[使用spring validation完成数据后端校验](https://blog.csdn.net/u013815546/article/details/77248003)
[@Validated和@Valid区别:Spring validation验证框架对入参实体进行嵌套验证必须在相应属性(字段)加上@Valid而不是@Validated](https://blog.csdn.net/qq_27680317/article/details/79970590)
[如何优雅的做数据校验-Hibernate Validator详细使用说明](https://juejin.im/post/6844903976270299149)
[Spring 参数校验的异常处理](https://juejin.im/post/6844904003684302861)
[使用spring validation完成数据后端校验](https://blog.csdn.net/u013815546/article/details/77248003)