如何以干净的方式验证 spring-boot 中基于另一个属性的属性?

Smi*_*ile 9 java bean-validation spring-boot

我正在验证 spring-boot 2.3.1.RELEASE Web 应用程序中的 REST 服务请求/bean。目前,我正在使用 Hibernate Validator,但我愿意使用任何其他方式进行验证。

说,我有一个模型Foo,我在Rest Controller. 我想验证如果completionDate不是null那么status应该“完成”或“关闭”。

@StatusValidate
public class Foo {
    private String status;
    private LocalDate completionDate;
    // getters and setters
}
Run Code Online (Sandbox Code Playgroud)

我创建了一个自定义类级别的注释@StatusValidate

@Constraint(validatedBy = StatusValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StatusValidate {

    String message() default "default status error";

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

    Class<? extends Payload>[] payload() default {};
}
Run Code Online (Sandbox Code Playgroud)

我创建了StatusValidator类。

public class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {

    @Override
    public void initialize(StatusValidateconstraintAnnotation) {
    }

    @Override
    public boolean isValid(Foovalue, ConstraintValidatorContext context) {
        if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete") && !value.getStatus().equalsIgnoreCase("closed"))) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).
                    .addPropertyNode("status").addConstraintViolation();
            return false;
        }
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我验证Foo对象时(通过使用@Valid@Validated手动调用该validator.validate()方法),我在ConstraintViolation. 代码:

// Update.class is a group
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo, Update.class);
constraintViolations.forEach(constraintViolation -> {
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.setKey(constraintViolation.getPropertyPath().toString());
    errorMessage.setValue(constraintViolation.getInvalidValue());
    // Do something with errorMessage here
});
Run Code Online (Sandbox Code Playgroud)

constraintViolation.getPropertyPath().toString() => 状态

constraintViolation.getInvalidValue() => (Foo 对象)

如何status在自定义ConstraintValidator或其他任何地方设置无效值(属性的实际值)以便constraintViolation.getInvalidValue()返回status属性值? 或者 是否有更好的方法来验证请求有效负载/bean,其中属性的验证取决于另一个属性的值?

编辑: 我可以做类似的事情

if(constraintViolation.getPropertyPath().toString().equals("status")) {
    errorMessage.setValue(foo.getStatus());
}
Run Code Online (Sandbox Code Playgroud)

但这将涉及在某处维护属性名称的字符串常量,例如。"status". 尽管如此,StatusValidator我也在设置属性名称.addPropertyNode("status"),这也是我想避免的。


摘要: 我正在寻找解决方案(不一定使用自定义验证或休眠验证器),其中

  1. 我可以验证 json 请求程序或 bean,对于其验证取决于其他属性值的属性。
  2. 我不必在任何地方将 bean 属性名称维护为字符串常量(维护噩梦)。
  3. 我能够同时获得无效的属性名称和值。

Ana*_*nov 3

您可以使用动态负载在约束违规中提供附加数据。可以使用以下方式设置HibernateConstraintValidatorContext

context.unwrap(HibernateConstraintValidatorContext.class)
        .withDynamicPayload(foo.getStatus().toString());
Run Code Online (Sandbox Code Playgroud)

并且javax.validation.ConstraintViolation可以依次解包HibernateConstraintViolation以检索动态负载:

constraintViolation.unwrap(HibernateConstraintViolation.class)
        .getDynamicPayload(String.class);
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,我们传递一个简单的字符串,但您可以传递一个包含您需要的所有属性的对象。


请注意,这仅适用于Hibernate Validator,它是 Bean Validation 规范 (JSR-303/JSR-349) 使用最广泛的实现,并被 Spring 用作其默认验证提供程序。