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

栏目: Java · 发布时间: 7年前

内容简介:大家知道,在 Spring 中,有个很实用的 Bean Validation 的功能,它可以让我们用声明式的方式轻松分离验证逻辑。它内置了一些基础的验证器,但是,有一个比较常见的场景,这些内置的验证器是没有支持的,这个场景就是 “开始时间必须在结束时间之前”。我想了一想,通过 Java 中的反射以及 Comparable/Comparator 实现了一套通用的验证器,理论上,任何一种能通过比较逻辑比较的值,都可以验了。大家可以先想一下,要实现一个类中去验证某两个属性的大小关系(或者一般的来讲:基于比较器的

大家知道,在 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:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Cascading Style Sheets 2.0 Programmer's Reference

Cascading Style Sheets 2.0 Programmer's Reference

Eric A. Meyer / McGraw-Hill Osborne Media / 2001-03-20 / USD 19.99

The most authoritative quick reference available for CSS programmers. This handy resource gives you programming essentials at your fingertips, including all the new tags and features in CSS 2.0. You'l......一起来看看 《Cascading Style Sheets 2.0 Programmer's Reference》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具