【问题标题】:JSR 303 Validation, If one field equals "something", then these other fields should not be nullJSR 303 验证,如果一个字段等于“某物”,则这些其他字段不应为空
【发布时间】:2012-03-06 06:25:03
【问题描述】:

我希望使用 JSR-303 javax.validation 进行一些自定义验证。

我有一个领域。如果在这个字段中输入了某个值,我想要求其他几个字段不是null。

我正在努力解决这个问题。不确定我会称之为什么来帮助找到解释。

任何帮助将不胜感激。我对此很陌生。

目前我正在考虑自定义约束。但我不确定如何从注释中测试依赖字段的值。基本上我不确定如何从注释中访问面板对象。

public class StatusValidator implements ConstraintValidator<NotNull, String> {

    @Override
    public void initialize(NotNull constraintAnnotation) {}

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ("Canceled".equals(panel.status.getValue())) {
            if (value != null) {
                return true;
            }
        } else {
            return false;
        }
    }
}

这是panel.status.getValue(); 给我带来了麻烦.. 不知道如何做到这一点。

【问题讨论】:

    标签: java validation bean-validation


    【解决方案1】:

    示例如下:

    package io.quee.sample.javax;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Valid;
    import javax.validation.Validator;
    import javax.validation.constraints.Pattern;
    import java.util.Set;
    
    /**
     * Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
     * Created At **Wednesday **23**, September 2020**
     */
    @SpringBootApplication
    public class SampleJavaXValidation implements CommandLineRunner {
        private final Validator validator;
    
        public SampleJavaXValidation(Validator validator) {
            this.validator = validator;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(SampleJavaXValidation.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
            System.out.println(validate);
        }
    
        public enum SampleTypes {
            TYPE_A,
            TYPE_B;
        }
    
        @Valid
        public static class SampleDataCls {
            private final SampleTypes type;
            private final String valueA;
            private final String valueB;
    
            public SampleDataCls(SampleTypes type, String valueA, String valueB) {
                this.type = type;
                this.valueA = valueA;
                this.valueB = valueB;
            }
    
            public SampleTypes getType() {
                return type;
            }
    
            public String getValueA() {
                return valueA;
            }
    
            public String getValueB() {
                return valueB;
            }
    
            @Pattern(regexp = "TRUE")
            public String getConditionalValueA() {
                if (type.equals(SampleTypes.TYPE_A)) {
                    return valueA != null ? "TRUE" : "";
                }
                return "TRUE";
            }
    
            @Pattern(regexp = "TRUE")
            public String getConditionalValueB() {
                if (type.equals(SampleTypes.TYPE_B)) {
                    return valueB != null ? "TRUE" : "";
                }
                return "TRUE";
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      在这种情况下,我建议编写一个自定义验证器,它将在类级别验证(以允许我们访问对象的字段)只有当另一个字段具有特定值时才需要一个字段。请注意,您应该编写获取 2 个字段名称并仅使用这 2 个字段的通用验证器。要要求多个字段,您应该为每个字段添加此验证器。

      使用下面的代码作为一个想法(我没有测试过)。

      • 验证器接口

        /**
         * Validates that field {@code dependFieldName} is not null if
         * field {@code fieldName} has value {@code fieldValue}.
         **/
        @Target({TYPE, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
        @Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
        @Documented
        public @interface NotNullIfAnotherFieldHasValue {
        
            String fieldName();
            String fieldValue();
            String dependFieldName();
        
            String message() default "{NotNullIfAnotherFieldHasValue.message}";
            Class<?>[] groups() default {};
            Class<? extends Payload>[] payload() default {};
        
            @Target({TYPE, ANNOTATION_TYPE})
            @Retention(RUNTIME)
            @Documented
            @interface List {
                NotNullIfAnotherFieldHasValue[] value();
            }
        
        }
        
      • 验证器实现

        /**
         * Implementation of {@link NotNullIfAnotherFieldHasValue} validator.
         **/
        public class NotNullIfAnotherFieldHasValueValidator
            implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
        
            private String fieldName;
            private String expectedFieldValue;
            private String dependFieldName;
        
            @Override
            public void initialize(NotNullIfAnotherFieldHasValue annotation) {
                fieldName          = annotation.fieldName();
                expectedFieldValue = annotation.fieldValue();
                dependFieldName    = annotation.dependFieldName();
            }
        
            @Override
            public boolean isValid(Object value, ConstraintValidatorContext ctx) {
        
                if (value == null) {
                    return true;
                }
        
                try {
                    String fieldValue       = BeanUtils.getProperty(value, fieldName);
                    String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
        
                    if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
                        ctx.disableDefaultConstraintViolation();
                        ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
                            .addNode(dependFieldName)
                            .addConstraintViolation();
                            return false;
                    }
        
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
        
                return true;
            }
        
        }
        
      • 验证器使用示例(hibernate-validator >= 6 with Java 8+)

        @NotNullIfAnotherFieldHasValue(
            fieldName = "status",
            fieldValue = "Canceled",
            dependFieldName = "fieldOne")
        @NotNullIfAnotherFieldHasValue(
            fieldName = "status",
            fieldValue = "Canceled",
            dependFieldName = "fieldTwo")
        public class SampleBean {
            private String status;
            private String fieldOne;
            private String fieldTwo;
        
            // getters and setters omitted
        }
        
      • 验证器使用示例(hibernate-validator

        @NotNullIfAnotherFieldHasValue.List({
            @NotNullIfAnotherFieldHasValue(
                fieldName = "status",
                fieldValue = "Canceled",
                dependFieldName = "fieldOne"),
            @NotNullIfAnotherFieldHasValue(
                fieldName = "status",
                fieldValue = "Canceled",
                dependFieldName = "fieldTwo")
        })
        public class SampleBean {
            private String status;
            private String fieldOne;
            private String fieldTwo;
        
            // getters and setters omitted
        }
        

      请注意,验证器实现使用 commons-beanutils 库中的 BeanUtils 类,但您也可以使用 BeanWrapperImpl from Spring Framework。

      另请参阅这个很棒的答案:Cross field validation with Hibernate Validator (JSR 303)

      【讨论】:

      • @Benedictus 这个示例只适用于字符串,但您可以修改它以适用于任何对象。有 2 种方法:1) 使用要验证的类参数化验证器(而不是 Object)。在这种情况下,您甚至不需要使用反射来获取值,但在这种情况下验证器变得不那么通用 2)使用来自 Spring Framework(或其他库)的BeanWrapperImp 及其getPropertyValue() 方法。在这种情况下,您将能够获得 Object 的值并转换为您需要的任何类型。
      • 是的,但是你不能将 Object 作为注解参数,所以你需要为你想要验证的每种类型设置一堆不同的注解。
      • 是的,这就是我所说的“在这种情况下验证器变得不那么通用”的意思。
      • 我想将这个技巧用于 protoBuffer 类。这很有帮助(:
      • 不错的解决方案。对构建自定义注释非常有帮助!
      【解决方案3】:

      这是我的看法,尽量保持简单。

      界面:

      @Target({TYPE, ANNOTATION_TYPE})
      @Retention(RUNTIME)
      @Constraint(validatedBy = OneOfValidator.class)
      @Documented
      public @interface OneOf {
      
          String message() default "{one.of.message}";
      
          Class<?>[] groups() default {};
      
          Class<? extends Payload>[] payload() default {};
      
          String[] value();
      }
      

      验证实现:

      public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
      
          private String[] fields;
      
          @Override
          public void initialize(OneOf annotation) {
              this.fields = annotation.value();
          }
      
          @Override
          public boolean isValid(Object value, ConstraintValidatorContext context) {
      
              BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
      
              int matches = countNumberOfMatches(wrapper);
      
              if (matches > 1) {
                  setValidationErrorMessage(context, "one.of.too.many.matches.message");
                  return false;
              } else if (matches == 0) {
                  setValidationErrorMessage(context, "one.of.no.matches.message");
                  return false;
              }
      
              return true;
          }
      
          private int countNumberOfMatches(BeanWrapper wrapper) {
              int matches = 0;
              for (String field : fields) {
                  Object value = wrapper.getPropertyValue(field);
                  boolean isPresent = detectOptionalValue(value);
      
                  if (value != null && isPresent) {
                      matches++;
                  }
              }
              return matches;
          }
      
          private boolean detectOptionalValue(Object value) {
              if (value instanceof Optional) {
                  return ((Optional) value).isPresent();
              }
              return true;
          }
      
          private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
              context.disableDefaultConstraintViolation();
              context
                  .buildConstraintViolationWithTemplate("{" + template + "}")
                  .addConstraintViolation();
          }
      
      }
      

      用法:

      @OneOf({"stateType", "modeType"})
      public class OneOfValidatorTestClass {
      
          private StateType stateType;
      
          private ModeType modeType;
      
      }
      

      消息:

      one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
      one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
      

      【讨论】:

        【解决方案4】:

        定义必须验证为真的方法并将@AssertTrue注解放在其顶部:

          @AssertTrue
          private boolean isOk() {
            return someField != something || otherField != null;
          }
        

        方法必须以'is'开头。

        【讨论】:

        • 我使用了你的方法并且它有效,但我不知道如何获取消息。你碰巧知道吗?
        • 这是迄今为止最有效的选择。谢谢! @anaBad:AssertTrue 注释可以接受自定义消息,就像其他约束注释一样。
        • @ErnestKiwele 感谢您的回答,但我的问题不在于设置消息,而是在我的 jsp 中获取它。我的模型具有以下功能:@AssertTrue(message="La reference doit etre un URL") public boolean isReferenceOk() { return origine!=Origine.Evolution||reference.contains("http://jira.bcaexpertise.org"); } 而这在我的 jsp 中:&lt;th&gt;&lt;form:label path="reference"&gt;&lt;s:message code="reference"/&gt;&lt;/form:label&gt;&lt;/th&gt;&lt;td&gt;&lt;form:input path="reference" cssErrorClass="errorField"/&gt;&lt;br/&gt;&lt;form:errors path="isReferenceOk" cssClass="error"/&gt;&lt;/td&gt; 但它会引发错误。
        • @ErnestKiwele 没关系,我想通了,我创建了一个布尔属性,在调用 setReference() 时设置。
        • 我必须公开该方法
        【解决方案5】:

        你应该使用自定义DefaultGroupSequenceProvider&lt;T&gt;:

        ConditionalValidation.java

        // Marker interface
        public interface ConditionalValidation {}
        

        MyCustomFormSequenceProvider.java

        public class MyCustomFormSequenceProvider
            implements DefaultGroupSequenceProvider<MyCustomForm> {
        
            @Override
            public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
        
                List<Class<?>> sequence = new ArrayList<>();
        
                // Apply all validation rules from ConditionalValidation group
                // only if someField has given value
                if ("some value".equals(myCustomForm.getSomeField())) {
                    sequence.add(ConditionalValidation.class);
                }
        
                // Apply all validation rules from default group
                sequence.add(MyCustomForm.class);
        
                return sequence;
            }
        }
        

        MyCustomForm.java

        @GroupSequenceProvider(MyCustomFormSequenceProvider.class)
        public class MyCustomForm {
        
            private String someField;
        
            @NotEmpty(groups = ConditionalValidation.class)
            private String fieldTwo;
        
            @NotEmpty(groups = ConditionalValidation.class)
            private String fieldThree;
        
            @NotEmpty
            private String fieldAlwaysValidated;
        
        
            // getters, setters omitted
        }
        

        另见related question on this topic。

        【讨论】:

        • 有趣的做法。不过,答案可能需要更多地解释它是如何工作的,因为在我看到发生了什么之前我必须阅读它两次......
        • 您好,我实施了您的解决方案,但遇到了问题。没有对象被传递给getValidationGroups(MyCustomForm myCustomForm) 方法。你能在这里帮忙吗? :stackoverflow.com/questions/44520306/…
        • @user238607 getValidationGroups(MyCustomForm myCustomForm) 每个 bean 实例都会调用很多次,并且有时会传递 null。如果它通过 null,你就忽略它。
        【解决方案6】:

        另一种方法是创建一个(受保护的)getter,它返回一个包含所有相关字段的对象。示例:

        public class MyBean {
          protected String status;
          protected String name;
        
          @StatusAndSomethingValidator
          protected StatusAndSomething getStatusAndName() {
            return new StatusAndSomething(status,name);
          }
        }
        

        StatusAndSomethingValidator 现在可以访问 StatusAndSomething.status 和 StatusAndSomething.something 并进行相关检查。

        【讨论】:

          猜你喜欢
          • 2011-10-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-30
          • 1970-01-01
          • 2016-10-14
          • 2022-12-14
          相关资源
          最近更新 更多