转载

实现一个通用的基于Comparable的Validator

大家知道,在 Spring 中,有个很实用的 Bean Validation 的功能,它可以让我们用声明式的方式轻松分离验证逻辑。它内置了一些基础的验证器,但是,有一个比较常见的场景,这些内置的验证器是没有支持的,这个场景就是 “开始时间必须在结束时间之前”。我想了一想,通过 Java 中的反射以及 Comparable/Comparator 实现了一套通用的验证器,理论上,任何一种能通过比较逻辑比较的值,都可以验了。

正文

大家可以先想一下,要实现一个类中去验证某两个属性的大小关系(或者一般的来讲:基于比较器的关系),该有哪些步骤呢?首先要比较两个属性,那么就需要拿到这两个属性的值,凭借经验,我们很容易就能想到:反射;其次,想比较两个值的大小,这个就更简单了,若两个值都是 Comparable 的,那么直接调用 java.lang.Comparable#compareTo 就好了。倘若不是 Comparable 的,那也好办,在 Java 中,有这么一个类 java.util.Comparator 它是任何一种比较逻辑的总接口,只要我们能给出一个实现了它的比较逻辑(或者说函数),也就可以验了。

OK,思路有了,按照 Spring 以及 Bean Validation 的规则实现出来就好了(这些规则请自行 Google),先来看基于 Comparable 的验证:

@Constraint(validatedBy = ComparableFieldsMatchValidator.class) //注意这个,这个声明了用哪个验证器去验证
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ComparableFieldsMatch {

    // 想要参与比较的左值
    String leftFieldName();

    // 想要参与比较的左值
    String rightFieldName();

    // 比较的规则
    CompareRule compareRule();

    // 以下三个方法是 @Constraint 必须要有的
    String message() default "";

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

    Class<? extends Payload>[] payload() default {};
}
// 比较的规则
public enum CompareRule {

    LEFT_GREATER_THEN_RIGHT {
        @Override
        public List<Integer> acceptableValue() {
            return Collections.singletonList(1);
        }

        @Override
        public String messageTemplate() {
            return "⚠ 左值 [%s] 应比右值 [%s] 大";
        }
    },

    LEFT_EQUAL_RIGHT {
        @Override
        public List<Integer> acceptableValue() {
            return Collections.singletonList(0);
        }

        @Override
        public String messageTemplate() {
            return "⚠ 左值 [%s] 应等于右值 [%s]";
        }
    },

    LEFT_LESS_THEN_RIGHT {
        @Override
        public List<Integer> acceptableValue() {
            return Collections.singletonList(-1);
        }

        @Override
        public String messageTemplate() {
            return "⚠ 左值 [%s] 应比右值 [%s] 小";
        }
    },

    LEFT_GREATER_EQUAL_THEN_RIGHT {
        @Override
        public List<Integer> acceptableValue() {
            return Arrays.asList(1, 0);
        }

        @Override
        public String messageTemplate() {
            return "⚠ 左值 [%s] 应大于等于右值 [%s]";
        }
    },

    LEFT_LESS_EQUAL_THEN_RIGHT {
        @Override
        public List<Integer> acceptableValue() {
            return Arrays.asList(-1, 0);
        }

        @Override
        public String messageTemplate() {
            return "⚠ 左值 [%s] 应小于等于右值 [%s]";
        }
    };

    public abstract List<Integer> acceptableValue();

    public abstract String messageTemplate();
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ComparableFieldsMatches {

    ComparableFieldsMatch[] value() default {};
}
// 验证器
public class ComparableFieldsMatchValidator implements ConstraintValidator<ComparableFieldsMatch, Object> {

    private ComparableFieldsMatch constraintAnnotation;

    @Override
    public void initialize(ComparableFieldsMatch constraintAnnotation) {
        this.constraintAnnotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object fieldsOwner, ConstraintValidatorContext context) {
        // 通过反射拿到需要比较的 左、右值
        Object leftFieldValue = Utils.getFieldThenMakeAccessible(fieldsOwner, constraintAnnotation.leftFieldName());
        Object rightFieldValue = Utils.getFieldThenMakeAccessible(fieldsOwner, constraintAnnotation.rightFieldName());

        CompareRule compareRule = constraintAnnotation.compareRule();

        int compareToResult = ((Comparable) leftFieldValue).compareTo(rightFieldValue);
        return compareRule.acceptableValue().contains(compareToResult); // 如果比较结果命中了比较规则编码的 acceptableValue,则是合规的
    }
}

再来看基于 Comparator 的验证:

@Constraint(validatedBy = ComparatorFieldsMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ComparatorFieldsMatch {

    String leftFieldName();

    String rightFieldName();

    // 需要的 Comparator 的  Class
    Class<? extends Comparator> comparatorClass();

    CompareRule compareRule();

    String message() default "";

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

    Class<? extends Payload>[] payload() default {};
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ComparatorFieldsMatches {

    ComparatorFieldsMatch[] value() default {};
}
// https://www.baeldung.com/spring-mvc-custom-validator
// http://daobin.wang/2017/06/Spring-Validation/
public class ComparatorFieldsMatchValidator implements ConstraintValidator<ComparatorFieldsMatch, Object> {

    private ComparatorFieldsMatch constraintAnnotation;

    @Override
    public void initialize(ComparatorFieldsMatch constraintAnnotation) {
        this.constraintAnnotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object fieldsOwner, ConstraintValidatorContext context) {
        Object leftFieldValue = Utils.getFieldThenMakeAccessible(fieldsOwner, constraintAnnotation.leftFieldName());
        Object rightFieldValue = Utils.getFieldThenMakeAccessible(fieldsOwner, constraintAnnotation.rightFieldName());

        CompareRule compareRule = constraintAnnotation.compareRule();

        // 获得 Comparator 的 Class,并用反射创建示例
        try {
            Comparator comparator = constraintAnnotation.comparatorClass().newInstance();
            int compareResult = comparator.compare(leftFieldValue, rightFieldValue);

            return compareRule.acceptableValue().contains(compareResult);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

好了,验证器写好了,我们再来看一下使用:

@ComparableFieldsMatches(
        @ComparableFieldsMatch(
                leftFieldName = "startTime",
                rightFieldName = "endTime",
                compareRule = CompareRule.LEFT_LESS_THEN_RIGHT,
                message = "开始时间不能在结束时间之后"
        )
)
public class DatePeriod {

    public DatePeriod() {
    }

    public DatePeriod(@PastOrPresent @NotNull Date startTime, @PastOrPresent @NotNull Date endTime) {
        this.startTime = startTime;
        this.endTime = endTime;
    }

    @ApiModelProperty(value = "开始时间", required = true, example = "2018-10-01 00:00:00")
    @PastOrPresent
    @NotNull
    private Date startTime;

    @ApiModelProperty(value = "结束时间", required = true, example = "2018-12-01 00:00:00")
    @PastOrPresent
    @NotNull
    private Date endTime;

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    // {"startTime": "%s", "endTime": "%s"}
    @Override
    public String toString() {
        return String.format("{/"startTime/": /"%s/", /"endTime/": /"%s/"}", DateFormatUtils.format(startTime, "yyyyMMdd"), DateFormatUtils.format(endTime, "yyyyMMdd"));
    }
}

是不是很简单呢,这样我们的通用验证器能为我们免去很多重复的验证逻辑,解放了生产力 :smile:

原文  https://since1986.github.io/blog/739a4aff.html
正文到此结束
Loading...