【问题标题】:Spring Boot validation - one from two not nullSpring Boot 验证 - 一个从两个不为空
【发布时间】:2019-07-05 09:38:28
【问题描述】:

我正在尝试验证 Spring Boot 中两个字段之一是否不为空?

我已经在主对象的方法类中设置了:

@NotNull(message = "Username field is required")
private String username;

@NotNull(message = "Email field is required")
private String email;

但这需要两个字段都不为空。然后我使用此处描述的自定义验证https://lmonkiewicz.com/programming/get-noticed-2017/spring-boot-rest-request-validation/,但我无法让该示例正常工作。我必须坚持

用户类声明:

@CombinedNotNull(fields = {"username","email"})
public class User implements {

    private long id = 0L;
    @NotNull(message = "First name field is required")
    private String firstName;

    @NotNull(message = "Last name field is required")
    private String lastName;

    private String username;
    private String email;

    @NotNull(message = "Status field is required")
    private String status;

    ...all methods here...
    ...setters and getters...

}

CombibnedNotNull 类:

@Documented
@Retention(RUNTIME)
@Target({ TYPE, ANNOTATION_TYPE })
@Constraint(validatedBy = userValidator.class)
public @interface CombinedNotNull {
        String message() default "username or email is required";
        Class<?>[] groups() default { };
        Class<? extends Payload>[] payload() default { };
}

userValidator 类:

@Component
public class userValidator implements ConstraintValidator<CombinedNotNull, User> {

    @Override
    public void initialize(final CombinedNotNull combinedNotNull) {
        fields = combinedNotNull.fields();
    }

    @Override
    public boolean isValid(final User user, final ConstraintValidatorContext context) {
        final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(user);

        for (final String f : fields) {
            final Object fieldValue = beanWrapper.getPropertyValue(f);

            if (fieldValue == null) {
                return false;
            }
        }

        return true;
    }
}

还有其他方法可以完成这项工作,还是我应该使用该页面中的“复杂”示例?

【问题讨论】:

  • 无论如何看看我的问题和答案stackoverflow.com/questions/46957854/…
  • 类验证是最佳方式。您遇到了哪些错误?
  • @LppEdd 我已经用错误更新了 OP。它会抛出 RUNTIME、FIELD、ANNOTATION_TYPE、PARAMETER 找不到它们的符号。
  • 它们是静态导入。只需使用 ElementType 或 RetentionPolicy 限定它们(例如 ElementType.FIELD - RetentionPolicy.RUNTIME)
  • @LppEdd 好的,继续前进,它出现了我无法将两个字段传递给ConstraintValidatorContext

标签: java validation spring-boot


【解决方案1】:

我假设 username OR email 不能为空。不是XOR。

在User 类中添加这个getter:

@AssertTrue(message = "username or email is required")
private boolean isUsernameOrEmailExists() {
    return username != null || email != null;
}

根据我的经验,方法名称必须遵循 getter 名称约定,否则这将不起作用。例如,getFoo 或 isBar。

这有一个小问题:来自此验证错误的字段名称将是usernameOrEmailExists,只有 1 个错误字段。如果这不是问题,这可能会有所帮助。


但如果您希望在发生错误时拥有username 和email 字段,您可以使用此解决方法:

public String getUsername() {
    return username;
}

public String getEmail() {
    return email;
}

@AssertTrue(message = "username or email is required")
private boolean isUsername() {
    return isUsernameOrEmailExists();
}

@AssertTrue(message = "username or email is required")
private boolean isEmail() {
    return isUsernameOrEmailExists();
}

private boolean isUsernameOrEmailExists() {
    return username != null || email != null;
}

get... 方法只是一般用途的简单 getter,is... 用于验证。这将发出 2 个带有 username 和 email 字段的验证错误。

【讨论】:

    【解决方案2】:

    我会尝试为您实现它(即使我没有 IDE)。
    在ConstraintValidator#initialize 中,您可以获取不能为null 的配置字段名称。

    @Override
    public void initialize(final CombinedNotNull combinedNotNull) {
        fields = combinedNotNull.fields();
    }
    

    在ConstraintValidator#isValid 中,您可以使用这些字段的名称来检查Object 字段。

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(value);
        
        for (final String f : fields) {
           final Object fieldValue = beanWrapper.getPropertyValue(f);
           
           if (fieldValue == null) {
              return false;
           }
        }
    
        return true;
    }
    

    注释:

    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    @Retention(RUNTIME)
    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Constraint(validatedBy = CombinedNotNullValidator.class)
    public @interface CombinedNotNull {
        String message() default "username or email is required";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        /**
         * Fields to validate against null.
         */
        String[] fields() default {};
    }
    

    注释可以应用为

    @CombinedNotNull(fields = {
          "fieldName1",
          "fieldName2"
    })
    public class MyClassToValidate { ... }
    

    要了解如何创建类级约束注释,请始终参阅官方文档。 Docs

    【讨论】:

    • @CombinedNotNull(fields = { "user","email"}) fields 无法解决
    • @JackTheKnife 你需要调整你的注释。也许发布实际的代码。
    • @JackTheKnife 看到我更新的答案。看我将验证器从“userValidator”重命名为“CombinedNotNullValidator”并添加了一个String数组“fields”
    • 好的。我看到我错过了什么。现在我需要更改一点ConstraintValidator#isValid,因为我正在寻找username 或email 不为空(其中一个必须存在)
    • @JackTheKnife 您可以通过不硬编码字段名称来概括此约束
    【解决方案3】:

    如果您想验证是否设置了一个而其他为空:

    注释

    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    @Retention(RUNTIME)
    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Constraint(validatedBy = OneNotNullValidator.class)
    public @interface OneNotNull {
        String message() default "Exactly one of the fields must be set and the other must be null";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        /**
         * Fields to validate against null.
         */
        String[] fields() default {};
    }
    

    验证器

    import java.util.Arrays;
    import java.util.Objects;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.springframework.beans.BeanWrapperImpl;
    import org.springframework.stereotype.Component;
    
    @Component
    public class OneNotNullValidator implements ConstraintValidator<OneNotNull, Object> {
        private String[] fields;
    
        @Override
        public void initialize(final OneNotNull combinedNotNull) {
            fields = combinedNotNull.fields();
        }
    
        @Override
        public boolean isValid(final Object obj, final ConstraintValidatorContext context) {
            final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
    
            return Arrays.stream(fields)
                    .map(beanWrapper::getPropertyValue)
                    .filter(Objects::isNull)
                    .count()
                    == 1;
        }
    }
    
    

    用法

    @OneNotNull(
        fields = {"username","email"},
        message="Either username or email must be set"
    )
    public class User {
    
        private String username;
        private String email;
    
       // ...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多