考虑一种场景:一个bean有1个属性(假如说是attrA),这个属性上添加了3个约束(假如说是@NotNull、@NotEmpty、@NotBlank)。默认情况下,validation-api对这3个约束的校验顺序是随机的。也就是说,可能先校验@NotNull,再校验@NotEmpty,最后校验@NotBlank,也有可能先校验@NotBlank,再校验@NotEmpty,最后校验@NotNull。
那么,如果我们的需求是先校验@NotNull,再校验@NotBlank,最后校验@NotEmpty。validation-api能否做到这一点呢?答案是:能。这就要用到validation-api提供的 @GroupSequence 注解了。
- 要被校验的bean
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class GroupSequenceDemoForm {
@NotBlank(message = "至少包含一个非空字符", groups = {First.class})
@Size(min = 11, max = 11, message = "长度必须是11", groups = {Second.class})
private String demoAttr;
public interface First {
}
public interface Second {
}
@GroupSequence(value = {First.class, Second.class})
public interface GroupOrderedOne {
// 先计算属于 First 组的约束,再计算属于 Second 组的约束
}
@GroupSequence(value = {Second.class, First.class})
public interface GroupOrderedTwo {
// 先计算属于 Second 组的约束,再计算属于 First 组的约束
}
}
- 单元测试
public class UnitFiveTest {
private static Validator validator;
@BeforeClass
public static void setUpValidator() {
// 通过工厂获取到一个Validator的实例
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
@Test
public void groupValidateThirdTestVerTwo() {
GroupSequenceDemoForm form = new GroupSequenceDemoForm(" ");
// 校验属于 First 和 Second 的约束
// 默认情况下,不论约束是 First 组还是 Second 组,校验顺序是不确定的
System.out.println("\n" + validator.validate(form, GroupSequenceDemoForm.First.class,
GroupSequenceDemoForm.Second.class) + "\n");
// 校验属于 First 和 Second 的约束
// 默认情况下,不论约束是 First 组还是 Second 组,校验顺序是不确定的
System.out.println("\n" + validator.validate(form, GroupSequenceDemoForm.Second.class,
GroupSequenceDemoForm.First.class) + "\n");
// 校验属于 First 和 Second 的约束
// 明确指定先计算属于 First 组的约束,再计算属于 Second 组的约束
System.out.println("\n"
+ validator.validate(form, GroupSequenceDemoForm.GroupOrderedOne.class) + "\n");
// 校验属于 First 和 Second 的约束
// 明确指定先计算属于 Second 组的约束,再计算属于 First 组的约束
System.out.println("\n"
+ validator.validate(form, GroupSequenceDemoForm.GroupOrderedTwo.class) + "\n");
}
- 输出结果
[ConstraintViolationImpl{interpolatedMessage='长度必须是11', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='长度必须是11'}, ConstraintViolationImpl{interpolatedMessage='至少包含一个非空字符', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='至少包含一个非空字符'}]
[ConstraintViolationImpl{interpolatedMessage='长度必须是11', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='长度必须是11'}, ConstraintViolationImpl{interpolatedMessage='至少包含一个非空字符', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='至少包含一个非空字符'}]
[ConstraintViolationImpl{interpolatedMessage='至少包含一个非空字符', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='至少包含一个非空字符'}]
[ConstraintViolationImpl{interpolatedMessage='长度必须是11', propertyPath=demoAttr, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.three.ver2.GroupSequenceDemoForm, messageTemplate='长度必须是11'}]
从上面的输出结果可以看出,如果明确指定校验顺序,当前面的约束校验失败的话,后面的约束就不会继续校验了。这种特性其实刚好符合下面这种需求:每次调用接口,只返回一个校验错误,前一个校验错误纠正过后,后一个校验错误才返回。比如说:产品要求你写的接口要满足姓名不能为空校验通后,再校验年龄是否满十八岁,年龄满十八岁之后,再校验身份证号是否为空,身份证不为空之后,再校验身份证号码格式是否正确。
看完这个例子,有的小伙伴可能要问了:你这里用的是单元测试,那要是放到spring项目中怎么搞?其实也很简单。只需要把 @Valided 的 value 属性设置为上面代码中的 GroupOrderedOne.class 或者 GroupOrderedTwo.class 就可以了。