一止长渊

自定义数据校验注解

N 人看过
字数:764字 | 预计阅读时长:3分钟

在后端数据校验时,我们通常使用 javax.validation.constraints 或@Pattern 正则注解来使用,但可能存在一种这样的情况,我们的业务场景很复杂,官方提供的校验注解可能无法为我们提供合适的功能,这时候我们可以考虑采用自定义我们的校验注解。

场景:我们数据库中商品的 showStatus 属性规定 0 为不可见,1 为可见,我们为防止接口被用户滥用传入非法值

一、自定义注解的规范

截屏2021-04-05 18.41.40.png
图中红色部分是我们自定义注解必须使用上的
1、其中第一部分

  • @Target 表明该自定义注解可以运用在方法体、成员变量还是构造函数上
  • @Retention 表明注解使用的范围:SOURCE/CLASS/RUNTIME,其中 SOURCE 和 CLASS 级别的注解在 VM 中不会保留,在经过 VM 解释后该注解会被移除,而 RUNTIME 表明在被 VM 运行期间仍然保留
  • @Constraint 中的 validatedBy 就是我们编写的校验逻辑了——校验器

截屏2021-04-05 18.50.06.png
可以看到 validatedBy 的属性,要求我们校验逻辑必须实现 ConstraintValidator 接口,其中第一个泛型为我们自定义注解的接口,第二个为注解标注
2、第二部分:

  • message

我们自定义注解校验不通过时的提示消息,我们模仿官方将校验注解错误信息放置在 resources/ValidationMessages.properties 下,其中 key 为图中的 reference(包路径+message),value 为错误信息

  • groups

策略分组

  • payload

对于接收者有用的数据

二、实践

1.编写自定义校验注解@interface

@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class}) // 填写我们的校验器
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    String message() default "{com.lookstarry.common.valid.ListValue.message}";

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

    Class<? extends Payload>[] payload() default {};

    int[] vals() default {};
}

resource 下新建 ValidationMessages.properties 文件定义错误信息

com.lookstarry.common.valid.ListValue.message=必须提交指定的值

2.校验器
这里我们不允许 showStatus 出现 0,1 之外的值,实现 ConstraintValidator,其中第一个泛型为校验接口、第二个泛型为校验的类型(即校验注解添加在哪个本体上)

public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    Set<Integer> set = new HashSet<>();

    // 初始化方法,获取@ListValue(vals={1,2})的规定值
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for(int val : vals)
            set.add(val);
    }

    /**
     * 判断是否校验成功,interger即待校验的值,检查是否在set中,返回true或false表示校验是否通过
     * @param integer 需要校验的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(integer);
    }
}

3.校验属性

    @NotNull( groups={AddGroup.class, UpdateStatusGroup.class})
    @ListValue(vals={0,1}, groups={AddGroup.class, UpdateStatusGroup.class})
    private Integer showStatus;

4.测试
截屏2021-04-05 22.44.52.png

三、总结

1)编写自定义校验注解@interface
2)编写自定义校验器 ListValueConstraintValue
3)校验注解关联到自定义校验器 validatedBy={ListValueConstraintValue.class}

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。