自定义注解校验

在日常开发中经常会用到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[] 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[] 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);
    }
}