การตรวจสอบความถูกต้อง JSR 303 หากฟิลด์หนึ่งเท่ากับ "บางสิ่ง" ฟิลด์อื่น ๆ เหล่านี้ไม่ควรเป็นค่าว่าง


92

ฉันกำลังมองหาที่จะทำการตรวจสอบที่กำหนดเองเล็ก ๆ น้อย ๆ กับ javax.validationJSR-303

ฉันมีสนาม และหากมีค่าบางอย่างถูกป้อนลงในช่องนี้ผมต้องการที่จะกำหนดให้สาขาอื่น ๆ nullไม่กี่ไม่ได้

ฉันพยายามคิดออก ไม่แน่ใจว่าฉันจะเรียกสิ่งนี้ว่าอะไรเพื่อช่วยหาคำอธิบาย

ความช่วยเหลือใด ๆ จะได้รับการชื่นชม ฉันค่อนข้างใหม่สำหรับสิ่งนี้

ในขณะนี้ฉันกำลังคิดว่า Custom Constraint แต่ฉันไม่แน่ใจว่าจะทดสอบค่าของฟิลด์ที่อ้างอิงจากภายในคำอธิบายประกอบได้อย่างไร โดยทั่วไปฉันไม่แน่ใจว่าจะเข้าถึงวัตถุพาเนลจากคำอธิบายประกอบได้อย่างไร

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();ทำให้ฉันมีปัญหา .. ไม่แน่ใจว่าจะทำสำเร็จได้อย่างไร

คำตอบ:


107

ในกรณีนี้ฉันขอแนะนำให้เขียนตัวตรวจสอบความถูกต้องที่กำหนดเองซึ่งจะตรวจสอบความถูกต้องในระดับชั้นเรียน (เพื่อให้เราสามารถเข้าถึงฟิลด์ของออบเจ็กต์) ว่าฟิลด์ใดฟิลด์หนึ่งจำเป็นต้องใช้ก็ต่อเมื่อฟิลด์อื่นมีค่าเฉพาะเท่านั้น โปรดทราบว่าคุณควรเขียนโปรแกรมตรวจสอบความถูกต้องทั่วไปซึ่งได้รับ 2 ชื่อเขตข้อมูลและทำงานกับ 2 ฟิลด์นี้เท่านั้น หากต้องการต้องการมากกว่าหนึ่งฟิลด์คุณควรเพิ่มตัวตรวจสอบนี้สำหรับแต่ละฟิลด์

ใช้โค้ดต่อไปนี้เป็นไอเดีย (ยังไม่ได้ทดสอบ)

  • อินเทอร์เฟซ Validator

    /**
     * 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 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 (hibernate-validator> = 6 พร้อม 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 (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
    }
    

โปรดทราบว่าการใช้งานตัวตรวจสอบความถูกต้องจะใช้BeanUtilsคลาสจากcommons-beanutilsไลบรารี แต่คุณสามารถใช้BeanWrapperImplจาก Spring Frameworkได้เช่นกัน

ดูคำตอบที่ยอดเยี่ยมนี้ด้วย: การตรวจสอบความถูกต้องข้ามฟิลด์ด้วย Hibernate Validator (JSR 303)


1
@ เบเนดิกตัสตัวอย่างนี้ใช้ได้กับสตริงเท่านั้น แต่คุณสามารถปรับเปลี่ยนให้ทำงานกับอ็อบเจ็กต์ใดก็ได้ มี 2 ​​วิธี: 1) เครื่องมือตรวจสอบพารามิเตอร์กับคลาสที่คุณต้องการตรวจสอบความถูกต้อง (แทนObject) ในกรณีนี้คุณไม่จำเป็นต้องใช้การสะท้อนเพื่อรับค่า แต่ในกรณีนี้ตัวตรวจสอบความถูกต้องจะกลายเป็นแบบทั่วไปน้อยกว่า 2) ใช้BeanWrapperImpจาก Spring Framework (หรือไลบรารีอื่น ๆ ) และgetPropertyValue()วิธีการ ในกรณีนี้คุณจะสามารถรับค่าเป็นObjectและส่งเป็นประเภทใดก็ได้ที่คุณต้องการ
Slava Semushin

ใช่ แต่คุณไม่สามารถมี Object เป็นพารามิเตอร์คำอธิบายประกอบได้ดังนั้นคุณจะต้องมีคำอธิบายประกอบที่แตกต่างกันมากมายสำหรับแต่ละประเภทที่คุณต้องการตรวจสอบ
เบ็น

1
ใช่สิ่งที่ฉันหมายถึงเมื่อฉันพูดว่า "ในกรณีนี้ตัวตรวจสอบความถูกต้องจะกลายเป็นเรื่องธรรมดาน้อยกว่า"
Slava Semushin

ฉันต้องการใช้เคล็ดลับนี้สำหรับคลาส protoBuffer สิ่งนี้มีประโยชน์มาก (:
Saeed

ทางออกที่ดี มีประโยชน์มากในการสร้างคำอธิบายประกอบแบบกำหนดเอง!
Vishwa

128

กำหนดวิธีการที่ต้องตรวจสอบความถูกต้องเป็นจริงและใส่@AssertTrueคำอธิบายประกอบไว้ด้านบน:

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

เมธอดต้องขึ้นต้นด้วย "is"


ฉันใช้วิธีของคุณแล้วได้ผล แต่ฉันหาวิธีรับข้อความไม่ได้ คุณจะรู้หรือไม่?
anaBad

12
นี่เป็นตัวเลือกที่มีประสิทธิภาพมากที่สุด ขอบคุณ! @anaBad: คำอธิบายประกอบ AssertTrue สามารถใช้ข้อความที่กำหนดเองได้เช่นเดียวกับคำอธิบายประกอบข้อ จำกัด อื่น ๆ
ernest_k

@ErnestKiwele ขอบคุณสำหรับคำตอบ แต่ปัญหาของฉันไม่ได้อยู่ที่การตั้งค่าข้อความ แต่ได้รับใน jsp ของฉัน ฉันมีฟังก์ชั่นต่อไปนี้ในโมเดล: @AssertTrue(message="La reference doit etre un URL") public boolean isReferenceOk() { return origine!=Origine.Evolution||reference.contains("http://jira.bcaexpertise.org"); } และสิ่งนี้ใน jsp ของฉัน: <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> แต่มันแสดงข้อผิดพลาด
anaBad

@ErnestKiwele ไม่เป็นไรฉันคิดออกแล้วฉันสร้างแอตทริบิวต์บูลีนที่ตั้งค่าเมื่อเรียกใช้ setReference ()
anaBad

2
ฉันต้องทำให้วิธีนี้เป็นแบบสาธารณะ
tibi

22

คุณควรใช้แบบกำหนดเอง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
}

ดูคำถามที่เกี่ยวข้องในหัวข้อนี้


วิธีการทำที่น่าสนใจ คำตอบสามารถอธิบายเพิ่มเติมเกี่ยวกับวิธีการทำงานได้เพราะฉันต้องอ่านสองครั้งก่อนที่จะเห็นว่าเกิดอะไรขึ้น ...
Jules

สวัสดีฉันใช้วิธีแก้ปัญหาของคุณแล้ว แต่ประสบปัญหา ไม่มีการส่งวัตถุไปยังgetValidationGroups(MyCustomForm myCustomForm)เมธอด คุณสามารถช่วยที่นี่ได้หรือไม่? : stackoverflow.com/questions/44520306/…
user238607

2
@ user238607 getValidationGroups (MyCustomForm myCustomForm) จะเรียกหลายครั้งต่ออินสแตนซ์ bean และเวลาผ่านไปเป็นโมฆะ คุณก็ไม่สนใจถ้ามันผ่านโมฆะ
pramoth

9

นี่คือสิ่งที่ฉันทำพยายามทำให้มันง่ายที่สุด

อินเทอร์เฟซ:

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

3

วิธีการอื่นคือการสร้าง getter (ป้องกัน) ที่ส่งคืนอ็อบเจ็กต์ที่มีฟิลด์ที่ขึ้นต่อกันทั้งหมด ตัวอย่าง:

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

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

StatusAndSomethingValidator สามารถเข้าถึง StatusAndSomething.status และ StatusAndSomething.something และทำการตรวจสอบตาม


0

ตัวอย่างด้านล่าง:

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";
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.