Java for Web学习笔记(七九):Validation(3)自定义验证限制

2023-04-10 17:36:42

从已提供的限制中继承

最常见的,就是继承@Pattern,对正则表达式进行具体描述。例如一个数据库的描述为mysql://<name>:<password>@host/<databaseName>,我们需要能验证这个URL的正确。下面是自定义的标记@MysqlUrl
/**
 * 用于检查一个mysql url,需要满足mysql://用户名:密码@host/数据库名字 
 * <p>
 * Accepts {@code CharSequence}. {@code null} elements are considered valid.
 * @author Wei 
 */
/* @Target :可以在哪里使用,通常为METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE
 * 其中TYPE_USER需要Java 1.8*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
/* @Retention 必须是RUNTIME,否则Bean validation不会进行检验*/
@Retention(RetentionPolicy.RUNTIME)
/* 标注了这个标记的target,在javadoc中应显示其存在。在eclipse中似乎默认就是显示的 */
@Documented 
/* @Constraint是必须的,表明这是一个Bean Validation限制,否则会被忽略。
 * validatedBy中表明用来验证的constraintValidator的实现,本例继承@Pattern的限制,不需要任何的ConstraintValidator */
@Constraint(validatedBy = {})
/* @Pattern表明将继承@Pattern的限制,如果我们同时要求不为null,则还可以继承@NotNull,@NotBlank */
@Pattern(regexp = "^mysql://[\\.a-zA-Z0-9`!#$%^&*'{}?/+=|_~-]+:[\\.a-zA-Z0-9`!#$%^&*'{}?/+=|_~-]+"
        + "@([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*/[a-zA-Z0-9_]+$")
/* @ReportAsSingleViolation表明作为一个限制,对于继承方式,这几乎是常规的。*/
@ReportAsSingleViolation
public @interface MysqlUrl {    
    //【part one】在学习限制时说过,有三个参数message,group,payload,这是标准参数,必须进行设置
    String message() default "{cn.wei.validation.mysqlurl.message}";

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

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

    // 【part two】定义@MysqlUrl.List,也是标准方式,需进行设置    
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented    
    static @interface List {
        MysqlUrl[] value();
    }
}

自定义验证器

@Ipv4小例子

以检查ipv4为例,当然我们也可以写@Pattern,我们也可以通过自定义的验证器来实现。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR, ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//支持通过Ipv4Validator来进行验证
@Constraint(validatedBy = {Ipv4Validator.class})
//不允许为null,继承@NotNull
@NotNull
@ReportAsSingleViolation
public @interface Ipv4 {
    //【part one】在学习限制时说过,有三个参数message,group,payload,这是标准参数,必须进行设置
    String message() default "{cn.wei.validation.ipv4.message}";

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

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

    // 【part two】定义@Ipv4.List,也是标准方式,需进行设置    
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented    
    static @interface List {
        Ipv4[] value();
    }
}
public class Ipv4Validator implements ConstraintValidator<Ipv4, CharSequence>{
    @Override
    public void initialize(Ipv4 constraintAnnotation) {
    }

    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
        if(! (value instanceof String))
            return false;
        String str = (String) value;
        String[] p = str.split("\\.");
        if(p.length != 4)
            return false;
        for(int i = 0 ; i < 4 ; i ++){
            if(!StringUtils.isNumeric(p[i])
                    || Integer.parseInt(p[i],10) > 255
                    || (StringUtils.startsWith(p[i], "0") && ! StringUtils.equals(p[i], "0"))) //避免172.001.020.2的情况
                return false;
        }
        return true;
    }
}

@AdvanceFuture的例子

在@AdvanceFuture例子中,我们增加自定义标记中的参数 hours,表示在多少小时之后,以及显示的message消息中会根据hours来变化。
... ...
public @interface AdvanceFuture{
	//我们应该使用{cn.wei.validation.AdvanceFuture.message},在properties文件中定义。
	//作为演示例子,从简,直接写入内容:Not in the furture after {hours} hour(s),这里{hours}将填入hours参数,
	//还可以使用${validatedValue}表示需要检查的值。更为复杂的如下
	String message() default "Not in the furture${hours == 0 ? '' : hours == 1 ? ' after 1 hour' : ' after ' += hours += ' hours'}";

	/** 
	 * 增加一个新参数hours,类型为int,缺省值为0
	 * @return after n hours,default is 0(now)
	 */
	int hours() default 0;	
	... ...
    }
}

public class AdvanceFutureValidator implements ConstraintValidator<AdvanceFuture, LocalDateTime>{
	private int hour;
	
	@Override
	public void initialize(AdvanceFuture constraintAnnotation) {
		ConstraintValidator.super.initialize(constraintAnnotation);		
		this.hour = constraintAnnotation.hours();		
	}

	@Override
	public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) {
		if(value == null)
			return true;
		
		return value.compareTo(LocalDateTime.now().plusHours(hour)) > 0;
	}
}

Constraint Validator的生命周期

对于自定义的限制:

  1. 先检查有没有继承其他限制,如果有,先进行这部分的检查
  2. 检查有没有定义的ConstraintValidator,如果没有,就完成检查,如果有多个,则找最匹配目标参数类型的那个。在上面的例子中,参数类型是CharSequence,如果还允许long,则会由另一个validator进行检验,这时:
    @Constraint(validatedBy = {Ipv4StringValidator.class, Ipv4LongValidator.class})
  3. 找到Validator后,进行实例化,实例化先调用initialize()。对于同一个field的验证,这个实例会重复利用的。在initialize(),以@Size为例,可以获取注解中min和max的值。
  4. 然后调用isValid()进行检验。
相关链接: 我的Professional Java for Web Applications相关文章
  • 作者:恺风
  • 原文链接:https://blog.csdn.net/flowingflying/article/details/78150149
    更新时间:2023-04-10 17:36:42