java实现@Valid自定义注解

时间:2025-03-31 08:50:21

@Valid注解是什么

用于验证被注解对象是否符合要求,当不符合要求时就会在方法中返回message的错误提示信息。

自定义注解

@Target({ElementType.FIELD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = XXXValidator.class)
@Repeatable(CheckXXX.List.class)
public @interface CheckXXX {

	String name() default "abc";

	int age() default 1;

	String message() default "111";

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

	Class<? extends Payload>[] payload() default {};
	@Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        CheckXXX[] value();
    }
}

@Target:值只能是枚举类ElementType,有以下一些值:

  • :允许被修饰的注解作用在类、接口和枚举上
  • :允许作用在属性字段上
  • :允许作用在方法上
  • :允许作用在方法参数上
  • :允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • :允许作用在包上

@Documented:只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented标注,最终生成的文档中就不会显示@B。这里的生成文档指的JavaDoc文档!

@Retention:定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中(生命周期长度 SOURCE < CLASS < RUNTIME ,前者能作用的地方后者一定也能作用。一般如果需要 在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要 在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果 只是做一些检查性的操作,比如@Override和@SuppressWarnings,则 可选用 SOURCE 注解):

  • source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略。
  • class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
  • runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

@Constraint:限定自定义注解的方法。

@Repeatable:被元注解@Repeatable修饰的注解,可以在同一个地方使用多次。使用要点:

  • 在需要重复使用的注解上修饰 @Repeatable。
  • @Repeatable中的参数为被修饰注解的容器的类对象(class对象)。
  • 容器包含一个value方法,返回一个被修饰注解的数组。

校验器

public class XXXValidator implements ConstraintValidator<CheckXXX, Object> {

    private String name;

    private int age;

    @Override
    public void initialize(CheckTimeInterval constraintAnnotation) {
        this.name = constraintAnnotation.name();
        this.age = constraintAnnotation.age();
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(o);
        String name = (String)beanWrapper.getPropertyValue(name);
        int age = (Integer)beanWrapper.getPropertyValue(age);
        return age > 10 && name.equals("abc");
    }
}

ConstraintValidator<>:第二个参数表示注解修饰的对象类型。(类注解就是Object)
initialize():类初始化方法。
isValid():返回true表示校验通过,返回false表示校验失败,返回message的错误提示信息。

示例

使用一次的注解:

@Target({ ElementType.FIELD })
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { VinValidator.class })
public @interface CheckVins {

    String message() default "格式错误,输入多行vin必须为17位!";

    int max();

    int itemLength();

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

    Class<? extends Payload>[] payload() default {};
}
public class VinValidator implements ConstraintValidator<CheckVins, String[]> {

    private int max;

    private int itemLength;

	@Override
    public void initialize(CheckVins constraintAnnotation) {
        max = constraintAnnotation.max();
        itemLength = constraintAnnotation.itemLength();
    }

    @Override
    public boolean isValid(String[] value, ConstraintValidatorContext context) {

        //没有该参数时不判断
        if(value == null){
            return true;
        }
        //最多输入200个vin
        else if (value.length > max) {
            return false;
        }
        //输入vin大于一个时,要求每个必须为17位
        if (value.length>1){
            for (String item : value) {
                if (item.length() != itemLength) {
                    return false;
                }
            }
        }
        return true;
    }
}
public class RequestDownloadRecord {
	...
	@CheckVins(max = 200, itemLength = 17)
	private String[] vin;
}

重复使用的注解:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckTimeIntervalValidation.class)
@Documented
@Repeatable(CheckTimeInterval.List.class)
public @interface CheckTimeInterval {
    /**
     * 开始日期
     * @return field
     */
    String beginTime() default "startTime";

    /**
     * 查询参数时间类型
     * @return field
     */
    String timeType() default "LocalDateTime";

    /**
     * 结束日期
     * @return field
     */
    String endTime() default "endTime";

    /**
     * 日期间隔
     * @return field
     */
    int dayRange() default 30;

    String message() default "{.}";

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

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

    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
            ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {

        CheckTimeInterval[] value();
    }
}
public class CheckTimeIntervalValidation implements ConstraintValidator<CheckTimeInterval, Object> {

    private String beginTime;

    private String endTime;

    private int dayRange;

    private String timeType;

    @Override
    public void initialize(CheckTimeInterval constraintAnnotation) {
        this.beginTime = constraintAnnotation.beginTime();
        this.endTime = constraintAnnotation.endTime();
        this.dayRange = constraintAnnotation.dayRange();
        this.timeType=constraintAnnotation.timeType();
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(o);
        long result=0;
        if ("LocalDateTime".equals(timeType)){
            LocalDateTime begin = (LocalDateTime) (beanWrapper.getPropertyValue(beginTime));
            LocalDateTime end = (LocalDateTime) (beanWrapper.getPropertyValue(endTime));
            if (null == begin || null == end) {
                return false;
            }
            // result = (begin);
            result=begin.until(end, ChronoUnit.DAYS);
        }else if ("LocalDate".equals(timeType)){
            LocalDate begin = (LocalDate) (beanWrapper.getPropertyValue(beginTime));
            LocalDate end = (LocalDate) (beanWrapper.getPropertyValue(endTime));
            if (null == begin || null == end) {
                return false;
            }
            result=begin.until(end, ChronoUnit.DAYS);
        }

        return result>=0&&result <= dayRange;
    }
}
@CheckTimeInterval(beginTime = "start",endTime = "end",timeType="LocalDateTime",dayRange = 14,message = "开始时间必须小于结束时间,只能查询14天的日期范围数据")
@CheckTimeInterval(beginTime = "start",endTime = "end",timeType="LocalDateTime",dayRange = 28,message = "开始时间必须小于结束时间,只能查询28天的日期范围数据")
public class RunningRecordQuery {
	...
}