内容简介:大家知道,在 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:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Mybatis通用Mapper的实现
- Android 实现一个通用的圆角布局
- swoole通用协程池的实现
- Go 基于 Redis 通用频率控制的实现
- SpringBoot实现通用的接口参数校验,注解实战
- golang实现基于channel的通用连接池详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
大数据时代
[英] 维克托•迈尔•舍恩伯格(Viktor Mayer-Schönberger) / 周涛 / 浙江人民出版社 / 2012-12 / 49.90元
《大数据时代》是国外大数据研究的先河之作,本书作者维克托•迈尔•舍恩伯格被誉为“大数据商业应用第一人”,拥有在哈佛大学、牛津大学、耶鲁大学和新加坡国立大学等多个互联网研究重镇任教的经历,早在2010年就在《经济学人》上发布了长达14页对大数据应用的前瞻性研究。 维克托•迈尔•舍恩伯格在书中前瞻性地指出,大数据带来的信息风暴正在变革我们的生活、工作和思维,大数据开启了一次重大的时代转型,并用三......一起来看看 《大数据时代》 这本书的介绍吧!