JSR303验证,如果一个字段等于“ something”,那么这些其他字段不应该为 null

我希望用 JSR-303javax.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();给我的麻烦. . 不知道如何完成这一点。

136886 次浏览

In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.

Use the following code as an idea (I've not test it).

  • 验证程序接口

    /**
    * 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();
    }
    
    
    }
    
  • Validator implementation

    /**
    * 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;
    }
    
    
    }
    
  • Validator usage example (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
    }
    
  • Validator usage example (hibernate-validator < 6; the old example)

    @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
    }
    

Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.

See also this great answer: Cross field validation with Hibernate Validator (JSR 303)

另一种方法是创建一个(protected) getter,它返回一个包含所有依赖字段的对象:

public class MyBean {
protected String status;
protected String name;


@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}

StatusAndSomething Validator 现在可以访问 StatusAndSomething. status 和 StatusAndSomething. something 并进行依赖性检查。

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

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

方法必须以“ is”开头。

你应该使用定制的 DefaultGroupSequenceProvider<T>:

ConditionalValidation.java

// Marker interface
public interface ConditionalValidation {}

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
}

See also 关于这个话题的相关问题.

Here's my take on it, tried to keep it as simple as possible.

界面:

@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;


}

Messages:

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}

下面的例子:

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";
}
}
}