在日常开发中经常会用到String类型的数据当作数值进行映射,势必会做出数值范围的校验,可以通过自定义注解的办法简化代码实现,减少冗余代码。
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = StrRangeValidator.class) public @interface StrRange { /** * 错误提示 * @return */ String message() default "value is not in given range"; /** * 最小值 * @return */ double min() default Double.MIN_VALUE; /** * 最大值 * @return */ double max() default Double.MAX_VALUE; /** * 是否包含边界 * @return */ boolean closeMin() default true; /** * 是否包含边界 * @return */ boolean closeMax() default true; /** * 是否可空 * @return */ boolean nullable() default true; Class>[] groups() default { }; Class extends Payload>[] payload() default { }; }
这里的groups,payload是必须的。其他方法是根据需要设定的参数:
1. 允许null值跳过校验
2. 边界值开区间、闭区间
3. 自定义errorMessage
validatedBy 是核心的验证逻辑:
public class StrRangeValidator implements ConstraintValidator{ private boolean nullable; private BigDecimal min; private BigDecimal max; private boolean closeMin; private boolean closeMax; @Override public void initialize(StrRange constraintAnnotation) { nullable = constraintAnnotation.nullable(); min = new BigDecimal(String.valueOf(constraintAnnotation.min())); max = new BigDecimal(String.valueOf(constraintAnnotation.max())); closeMin = constraintAnnotation.closeMin(); closeMax = constraintAnnotation.closeMax(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { if (s == null && nullable){ return true; } try { BigDecimal val = new BigDecimal(s); boolean checkMin = closeMin ? min.compareTo(val) <= 0 : min.compareTo(val) < 0; boolean checkMax = closeMax ? val.compareTo(max) <= 0 : val.compareTo(max) < 0; return checkMin && checkMax; } catch (Exception ex) { return false; } } }
String到枚举值的反向解析和验证也是比较常见的问题,也可以通过自定义注解的方式简化此类解析判断。
再来一个枚举验证:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidEnumValidator.class) public @interface ValidEnum { /** * 错误提示 * @return */ String message() default "invalid enum value"; /** * 目标类型 * @return */ Class> target(); /** * 是否可空 * @return */ boolean nullable() default true; Class>[] groups() default { }; Class extends Payload>[] payload() default { }; }
public class ValidEnumValidator implements ConstraintValidator{ private Class> clazz; private boolean nullable; @Override public void initialize(ValidEnum constraintAnnotation) { nullable = constraintAnnotation.nullable(); clazz = constraintAnnotation.target(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { if (!clazz.isEnum()) { return false; } if (s == null && nullable) { return true; } try { Method method = clazz.getDeclaredMethod("of", String.class); return method.invoke(null, s) != null; } catch (Exception e) { return false; } } }
注意,枚举需要保持类型一致:String,都存在这样的of方法
@AllArgsConstructor @Getter public enum EAccountAuthTypeEnum { OPEN_ACCOUNT("1", "开户"), ; private final String code; private final String msg; public static EAccountAuthTypeEnum of(String code) { return Arrays.stream(values()).filter(ele -> ele.getCode().equals(code)).findFirst().orElse(null); } }