具有多个字段的Spring自定义注释验证

Hyp*_*ate 4 java validation spring annotations spring-mvc

这里有一个小贪婪的问题,希望这个也可以帮助那些想要了解更多关于注释验证的人

我目前正在学习Spring,现在,我计划尝试自定义注释验证.

我已经搜索了很多,现在我知道主要有两种验证,一种用于控制器,另一种是使用@Valid的注释方法

所以这是我的场景:假设我有两个或更多字段,当它们是ALL NULL时可以为null.但只有当其中一个字段包含除空字符串之外的任何值时,这些字段才需要输入.我有两个想法,但不知道如何正确实现它们.

这是类示例:

public class Subscriber {
    private String name;
    private String email;
    private Integer age;
    private String phone;
    private Gender gender;
    private Date birthday;
    private Date confirmBirthday;
    private String birthdayMessage;
    private Boolean receiveNewsletter;

    //Getter and Setter
}
Run Code Online (Sandbox Code Playgroud)

假设我想生日和confirmBirthday字段需要为空或反对,我可能想要为每个使用一个注释注释它们,看起来像这样:

public class Subscriber {
    private String name;
    private String email;
    private Integer age;
    private String phone;
    private Gender gender;

    @NotNullIf(fieldName="confirmBirthday")
    private Date birthday;

    @NotNullIf(fieldName="birthday")
    private Date confirmBirthday;

    private String birthdayMessage;
    private Boolean receiveNewsletter;

    //Getter and Setter
}
Run Code Online (Sandbox Code Playgroud)

所以我需要像这样创建验证Annotation:

@Documented
@Constraint(validatedBy = NotNullIfConstraintValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface NotNullIf {

    String fieldName();

    String message() default "{NotNullIf.message}";
    Class<?>[] group() default {};
    Class<? extends Payload>[] payload() default {};
}
Run Code Online (Sandbox Code Playgroud)

之后我将需要创建Validator本身:

public class NotNullIfConstraintValidator implements ConstraintValidator<NotNullIf, String>{

    private String fieldName;

    public void initialize(NotNullIf constraintAnnotation) {
        fieldName = constraintAnnotation.fieldName();
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value == null) {
            return true;
        };
        //TODO Validation
        return false;
    }

}
Run Code Online (Sandbox Code Playgroud)

那怎么可以实现呢?

对于另一个想法使用相同的类作为一个例子说我想要生日,confirmBirthday和birthdayMessdage只能是null或同时反对.我这次可能需要使用类注释验证来进行跨领域验证.

这是我想如何注释类:

@NotNullIf(fieldName={"birthday", "confirmBirthday", "birthdayMessage"})
public class Subscriber {
    //Those field same as the above one
}
Run Code Online (Sandbox Code Playgroud)

因此,当其中一个字段不为null时,还需要在客户端大小上输入其余字段.可能吗?

我读过这篇文章:如何访问注释属性中描述的字段

但我仍然对注释验证如何从我上面列出的那些元素起作用感到困惑.也许我需要对该代码进行一些细节解释,或者更糟糕的是我可能需要一些基本的概念检查.

请帮忙!

Arn*_*ter 8

为此,您只能使用类型级别注释,因为字段级别注释无法访问其他字段!

我做了类似的事情以允许选择验证(确切地说,许多属性中的一个必须不为null).在您的情况下,@AllOrNone注释(或您喜欢的任何名称)将需要一个字段名称数组,您将获得带注释类型的整个对象到验证器:

@Target(ElementType.TYPE)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = AllOrNoneValidator.class)
public @interface AllOrNone {
    String[] value();

    String message() default "{AllOrNone.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class AllOrNoneValidator implements ConstraintValidator<AllOrNone, Object> {
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    private String[] fields;

    @Override
    public void initialize(AllOrNone constraintAnnotation) {
        fields = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        long notNull = Stream.of(fields)
                .map(field -> PARSER.parseExpression(field).getValue(value))
                .filter(Objects::nonNull)
                .count();
        return notNull == 0 || notNull == fields.length;
    }
}
Run Code Online (Sandbox Code Playgroud)

(正如你所说的使用Spring我使用SpEL来允许甚至嵌套字段访问)

现在您可以注释您的Subscriber类型:

@AllOrNone({"birthday", "confirmBirthday"})
public class Subscriber {
    private String name;
    private String email;
    private Integer age;
    private String phone;
    private Gender gender;
    private Date birthday;
    private Date confirmBirthday;
    private String birthdayMessage;
    private Boolean receiveNewsletter;
}
Run Code Online (Sandbox Code Playgroud)