JSR 303 Doğrulama, Bir alan "bir şeye" eşitse, bu diğer alanlar boş olmamalıdır


92

JSR-303 ile küçük bir özel doğrulama yapmak istiyorum javax.validation.

Benim bir tarlanım var. Ve bu alana belirli bir değer girilirse, diğer birkaç alanın olmamasını zorunlu kılmak istiyorum null.

Bunu anlamaya çalışıyorum. Bir açıklama bulmaya yardımcı olması için buna tam olarak ne diyeceğimden emin değilim.

Herhangi bir yardım memnuniyetle karşılanacaktır. Bunda oldukça yeniyim

Şu anda bir Özel Kısıtlama düşünüyorum. Ancak bağımlı alanın değerini ek açıklamanın içinden nasıl test edeceğimi bilmiyorum. Temel olarak panel nesnesine açıklamadan nasıl erişeceğimi bilmiyorum.

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

O oluyor panel.status.getValue();Bunu gerçekleştirmek için emin nasıl .. bana sorun çıkarıyor.

Yanıtlar:


107

Bu durumda, bir alanın yalnızca başka bir alanın belirli bir değere sahip olması durumunda gerekli olduğunu sınıf düzeyinde doğrulayacak (nesnenin alanlarına erişmemizi sağlamak için) özel bir doğrulayıcı yazmanızı öneririm. 2 alan adı alan genel bir doğrulayıcı yazmanız ve yalnızca bu 2 alanla çalışmanız gerektiğini unutmayın. Birden fazla alanı zorunlu kılmak için her alan için bu doğrulayıcıyı eklemelisiniz.

Aşağıdaki kodu bir fikir olarak kullanın (test etmedim).

  • Doğrulayıcı arayüzü

    /**
     * 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();
        }
    
    }
    
  • Doğrulayıcı uygulaması

    /**
     * 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;
        }
    
    }
    
  • Doğrulayıcı kullanım örneği (hazırda bekletme-doğrulayıcı> = 6, Java 8+ ile)

    @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
    }
    
  • Doğrulayıcı kullanım örneği (hazırda bekletme-doğrulayıcı <6; eski örnek)

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

Doğrulayıcı uygulamasının kitaplıktaki BeanUtilssınıfı kullandığını , commons-beanutilsancak Spring Framework'ten de kullanabileceğinizi unutmayın BeanWrapperImpl. .

Ayrıca şu harika yanıta bakın: Hibernate Validator (JSR 303) ile çapraz alan doğrulama


1
@Benedictus Bu örnek yalnızca dizelerle çalışacaktır, ancak herhangi bir nesneyle çalışacak şekilde değiştirebilirsiniz. 2 yol vardır: 1) Doğrulamak istediğiniz sınıfla doğrulayıcıyı parametrize edin (yerine Object). Bu durumda, değerleri almak için yansıma kullanmanız bile gerekmez, ancak bu durumda doğrulayıcı daha az genel hale gelir 2) BeanWrapperImpSpring Framework'ten (veya diğer kitaplıklardan) ve onun getPropertyValue()yönteminden kullanın. Bu durumda, bir değer elde edebilecek Objectve ihtiyacınız olan herhangi bir türe çevirebileceksiniz.
Slava Semushin

Evet, ancak ek açıklama parametresi olarak Nesne olamaz, Bu nedenle doğrulamak istediğiniz her tür için bir dizi farklı ek açıklamaya ihtiyacınız olacak.
Ben

1
Evet, "bu durumda doğrulayıcı daha az jenerik hale gelir" dediğimde bunu kastediyorum.
Slava Semushin

Bu numarayı protoBuffer sınıfları için kullanmak istiyorum. bu çok faydalıdır (:
Saeed

Güzel çözüm. Özel açıklama oluşturmak için çok yararlı!
Vishwa

128

Doğruyu doğrulaması gereken yöntemi tanımlayın ve @AssertTrueek açıklamayı bunun üstüne koyun:

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

Yöntem "eşittir" ile başlamalıdır.


Yöntemini kullandım ve işe yarıyor, ancak mesajı nasıl alacağımı çözemiyorum. Biliyor musun?
anaBad

12
Bu, seçenekler arasında açık ara en verimli olanıydı. Teşekkürler! @anaBad: AssertTrue ek açıklaması, diğer kısıtlama ek açıklamaları gibi özel bir mesaj alabilir.
ernest_k

@ErnestKiwele Cevap verdiğiniz için teşekkürler, ancak benim sorunum mesajı ayarlamak değil jsp'mde almakla ilgili değil. Modelde aşağıdaki işleve sahibim: @AssertTrue(message="La reference doit etre un URL") public boolean isReferenceOk() { return origine!=Origine.Evolution||reference.contains("http://jira.bcaexpertise.org"); } Ve bu benim jsp'de: <th><form:label path="reference"><s:message code="reference"/></form:label></th><td><form:input path="reference" cssErrorClass="errorField"/><br/><form:errors path="isReferenceOk" cssClass="error"/></td> Ama bir hata atıyor.
anaBad

@ErnestKiwele Boş ver anladım, setReference () çağrıldığında ayarlanan bir boole özniteliği yaptım.
anaBad

2
yöntemi herkese açık hale getirmek zorunda kaldım
tibi

22

Özel kullanmalısınız DefaultGroupSequenceProvider<T>:

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
}

Ayrıca bu konuyla ilgili soruya bakın .


Bunu yapmanın ilginç bir yolu. Cevap, nasıl çalıştığına dair daha fazla açıklama ile yapılabilirdi, çünkü neler olduğunu görmeden önce iki kez okumak zorunda kaldım ...
Jules

Merhaba, çözümünüzü uyguladım ancak bir sorunla karşılaştım. getValidationGroups(MyCustomForm myCustomForm)Yönteme hiçbir nesne aktarılmıyor . Burada yardım edebilir misin? : stackoverflow.com/questions/44520306/…
user238607

2
@ user238607 getValidationGroups (MyCustomForm myCustomForm) bean örneği başına birçok kez çağırır ve bir süre boş geçer. Sadece boş geçerse görmezden gelirsiniz.
pramoth

9

İşte benim görüşüm, mümkün olduğunca basit tutmaya çalıştım.

Arayüz:

@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();
}

Doğrulama uygulaması:

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();
    }

}

Kullanım:

@OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {

    private StateType stateType;

    private ModeType modeType;

}

Mesajlar:

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}

3

Farklı bir yaklaşım, tüm bağımlı alanları içeren bir nesneyi döndüren bir (korumalı) alıcı oluşturmak olacaktır. Misal:

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

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

StatusAndSomethingValidator artık StatusAndSomething.status ve StatusAndSomething.something'e erişebilir ve bağımlı bir kontrol yapabilir.


0

Aşağıdaki örnek:

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";
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.