Java 自定义校验注解详解

参考资料

  1. Java Bean Validation 最佳实践

Java 自定义校验注解详解_第1张图片

Java 自定义校验注解详解_第2张图片

⏹一. 最基本自定义校验注解

校验字符串日期的格式

import org.springframework.util.ObjectUtils;

import javax.validation.*;
import java.lang.annotation.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;

// 当我们用javaDoc生成API文档时,将该注解记录到API文档中。
@Documented
// 该注解生效的位置(FIELD: 生效在字段上)
@Target({ ElementType.FIELD })
// 注解生效的阶段(RUNTIME: 运行时)
@Retention(RetentionPolicy.RUNTIME)
// 处理自定义注解的验证逻辑
@Constraint(validatedBy = {ValidateDateString.StrictDateStringValidator.class})
public @interface ValidateDateString {
    
    // 参数
    String msgArgs() default "";
    
    // 格式
    String pattern() default "yyyy/MM/dd";
    
    // 校验信息,不能省略
    String message() default "{1004E}";
    
    // 分组check,不能省略
    Class<?>[] groups() default {};
    
	// 作用暂时未知,不能省略
    Class<? extends Payload>[] payload() default {};
    
    // 使用内部类进行校验逻辑书写;还可以把校验的逻辑单独创建一个类,然后将.class文件通过@Constraint注解引入
    class StrictDateStringValidator implements ConstraintValidator<ValidateDateString, String> {

        private String pattern;

        @Override
        public void initialize(ValidateDateString validStrictDateString) {
            this.pattern = validStrictDateString.pattern();
        }

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

            try {

                if (ObjectUtils.isEmpty(value)) {
                    return true;
                }

                SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                // 设置非严格解析日期
                sdf.setLenient(false);
                sdf.parse(value);
            } catch (ParseException e) {
                return false;
            }
            return true;
        }
    }
}

⏹二. @ConstraintComposition注解的使用

作用于注解上,用于当多个校验注解作用于自定义注解的时候的校验逻辑

  • @ConstraintComposition(CompositionType.AND): 必须同时满足多个注解的条件才算校验通过
  • @ConstraintComposition(CompositionType.OR): 只要满足任意一个注解的条件,就算校验通过

2.1 @ConstraintComposition(CompositionType.AND)

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@Documented
// ❗❗❗当引入外界注解check的时候,此注解不能省略,否则会报错
@Constraint(validatedBy = {})
@ConstraintComposition(CompositionType.AND)
// 引入的校验注解: 非空校验
@NotEmpty
// 引入的校验注解: 正则校验,必须为半角数字
@Pattern(regexp = "[0-9]*")
@ReportAsSingleViolation
public @interface ValidateHalfNumeric {

	String message() default "{1005E}";

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

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

校验age是否为半角数字

import lombok.Data;

@Data
public class Test4Form {

	@ValidateHalfNumeric
    private String age;
}

⏹age为空: 不满足非空check,无法通过校验
Java 自定义校验注解详解_第3张图片
⏹age为非半角数字: 不满足正则check,无法通过校验
Java 自定义校验注解详解_第4张图片

2.2 @ConstraintComposition(CompositionType.OR)

⏹age为空: 仅满了正则check就通过校验
Java 自定义校验注解详解_第5张图片
⏹age为非半角数字: 仅满足了非空check就通过校验
Java 自定义校验注解详解_第6张图片


⏹三. @ReportAsSingleViolation注解的使用

当引入的多个校验注解同时作用于自定义注解时,才会起作用.如果不引入外界校验注解到自定义校验注解的话,@ReportAsSingleViolation注解没有效果.

  • 若待校验的数据不符合引入的校验注解的规则时,错误信息依然显示为自定义注解的.
  • 若不添加@ReportAsSingleViolation注解的话,则会显示为引入的自定义注解所带的错误信息.
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@Documented
@Constraint(validatedBy = {})
@ConstraintComposition(CompositionType.AND)
@NotEmpty
@Pattern(regexp = "[0-9]*")
// 
@ReportAsSingleViolation
public @interface ValidateHalfNumeric {

	String message() default "{1005E}";

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

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

待校验数据不满足引入的@Pattern注解条件,错误信息为自定义@ValidateHalfNumeric注解自带的
Java 自定义校验注解详解_第7张图片
若不添加@ReportAsSingleViolation注解的话,错误信息为@Pattern注解所携带的
Java 自定义校验注解详解_第8张图片


⏹四. @Repeatable和@interface List注解的使用

组合使用,让注解可多次作用于同一属性上

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@Documented
@Constraint(validatedBy = {})
@ConstraintComposition(CompositionType.AND)
@NotEmpty
@Pattern(regexp = "[0-9]*")
@ReportAsSingleViolation
// ⭕⭕⭕标记该注解是否可重复使用
@Repeatable(ValidateHalfNumeric.List.class)
public @interface ValidateHalfNumeric {

	String message() default "{1005E}";

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

	Class<? extends Payload>[] payload() default {};
	
	// 注解可以List的形式
    @Target({ FIELD })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        ValidateHalfNumeric[] value();
    }
}

4.1 使用案例1

import lombok.Data;
import javax.validation.groups.Default;

@Data
public class Test4Form {
	
	// 使用了同一个校验注解,不同的分组的错误消息不同
	@ValidateHalfNumeric(message = "{1005E}", groups = {AgeGroup.class})
    @ValidateHalfNumeric(message = "{1008E}", groups = {Default.class})
    private String age;
	
	public interface AgeGroup { }
}

⭕同样的注解,不同的分组,不同的提示消息
Java 自定义校验注解详解_第9张图片

4.2 使用案例2

import lombok.Data;

@Data
public class Test4Form {
	
	// 通过List的方式指定多个校验注解,根据不同的分组显示不同的校验消息
	@ValidateHalfNumeric.List({
        @ValidateHalfNumeric(message = "{1005E}", groups = {AgeGroup.class}),
        @ValidateHalfNumeric(message = "{1008E}", groups = {Default.class})
    })
    private String age;
}

⭕同样的注解,不同的分组,不同的提示消息
Java 自定义校验注解详解_第10张图片

⏹五. @OverridesAttribute注解的使用

我们自定义的@ValidateSize注解使用了JDK自带的@Size注解进行校验,
使用@OverridesAttribute注解覆盖重写原@Size注解中的属性

import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.constraints.Size;
import javax.validation.ReportAsSingleViolation;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@Documented
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Size
public @interface ValidateSize {

    String msgArgs() default "";

    String message() default "{1006E}";

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

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

    // 覆盖重写@Size注解中的属性
    @OverridesAttribute(constraint = Size.class, name = "min")
    int min() default 0;

    @OverridesAttribute(constraint = Size.class, name = "max")
    int max() default Integer.MAX_VALUE;
}

你可能感兴趣的:(Java,java)