每篇一句
> 不要像祥林嫂一样,天天抱怨着生活,日日思考着辞职。得罪点说一句:“沦落”到要跟这样的人共事工作,难道自己身上就没有原因?
前言
本以为洋洋洒洒的把Java/Spring
数据(绑定)校验这块说了这么多,基本已经算完结了。但今天中午一位热心小伙伴在使用Bean Validation
做数据校验时上遇到了一个稍显特殊的case,由于此校验场景也比较常见,因此便有了本文对数据校验补充。
关于Java/Spring
中的数据校验,我有理由坚信你肯定遇到过这样的场景需求:在对JavaBean
进行校验时,b属性的校验逻辑是依赖于a属性的值的;换个具象的例子说:当且仅当属性a的值=xxx时,属性b的校验逻辑才生效。这也就是我们常说的多字段联合校验逻辑~ 因为这个校验的case比较常见,因此促使了我记录本文的动力,因为它会变得有意义和有价值。当然对此问题有的小伙伴说可以自己用if else
来处理呀,也不是很麻烦。本文的目的还是希望对数据校验一以贯之的做到更清爽、更优雅、更好扩展而努力。 > 需要有一点坚持:既然用了Bean Validation
去简化校验,那就(最好)不要用得四不像,遇到问题就解决问题~
热心网友问题描述
为了更真实的还原问题场景,我贴上聊天截图如下: 待校验的请求JavaBean如下: 校需求描述简述如下: 这位网友描述的真实生产场景问题,这也是本文讲解的内容所在。 虽然这是在Spring MVC
条件的下使用的数据校验,但按照我的习惯为了更方便的说明问题,我会把此部分功能单摘出来,说清楚了方案和原理,再去实施解决问题本身(文末)~
方案和原理
对于单字段的校验、级联属性校验等,通过阅读我的系列文章,我有理由相信小伙伴们都能驾轻就熟
了的。本文给出一个最简单的例子简单"复习"一下:
@Getter
@Setter
@ToString
public class Person {
@NotNull
private String name;
@NotNull
@Range(min = 10, max = 40)
private Integer age;
@NotNull
@Size(min = 3, max = 5)
private List<string> hobbies;
// 级联校验
@Valid
@NotNull
private Child child;
}
测试:
public static void main(String[] args) {
Person person = new Person();
person.setName("fsx");
person.setAge(5);
person.setHobbies(Arrays.asList("足球","篮球"));
person.setChild(new Child());
Set<constraintviolation<person>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(person);
// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}
运行,打印输出:
child.name 不能为null: null
age 需要在10和40之间: 5
hobbies 个数必须在3和5之间: [足球,篮球]
结果符合预期,(级联)校验生效。 > 通过使用@Valid
可以实现递归验证,因此可以标注在List
上,对它里面的每个对象都执行校验
问题来了,针对上例,现在我有如下需求:
- 若20 <= age < 30,那么
hobbies
的size
需介于1和2之间 - 若30 <= age < 40,那么
hobbies
的size
需介于3和5之间 - age其余值,
hobbies
无校验逻辑
实现方案
Hibernate Validator
提供了非标准的@GroupSequenceProvider
注解。本功能提供根据当前对象实例的状态,动态来决定加载那些校验组进入默认校验组。
为了实现上面的需求达到目的,我们需要借助Hibernate Validation
提供给我们的DefaultGroupSequenceProvider
接口来处理。
// 该接口定义了:动态Group序列的协定
// 要想它生效,需要在T上标注@GroupSequenceProvider注解并且指定此类为处理类
// 如果`Default`组对T进行验证,则实际验证的实例将传递给此类以确定默认组序列(这句话特别重要 下面用例子解释)
public interface DefaultGroupSequenceProvider<t> {
// 合格方法是给T返回默认的组(多个)。因为默认的组是Default嘛~~~通过它可以自定指定
// 入参T object允许在验证值状态的函数中动态组合默认组序列。(非常强大)
// object是待校验的Bean。它可以为null哦~(Validator#validateValue的时候可以为null)
// 返回值表示默认组序列的List。它的效果同@GroupSequence定义组序列,尤其是列表List必须包含类型T
List<class<?>> getValidationGroups(T object);
}
注意:
- 此接口Hibernate并没有提供实现
- 若你实现请必须提供一个空的构造函数以及保证是线程安全的
按步骤解决多字段组合验证的逻辑: 1、自己实现DefaultGroupSequenceProvider
接口(处理Person这个Bean)
public class PersonGroupSequenceProvider implements DefaultGroupSequenceProvider<person> {
@Override
public List<class<?>> getValidationGroups(Person bean) {
List<class<?>> defaultGroupSequence = new ArrayList<>();
defaultGroupSequence.add(Person.class); // 这一步不能省,否则Default分组都不会执行了,会抛错的
if (bean != null) { // 这块判空请务必要做
Integer age = bean.getAge();
System.err.println("年龄为:"