JSR 303 유효성 검사, 한 필드가 "something"과 같으면 다른 필드는 null이 아니어야합니다.


89

JSR-303으로 약간의 사용자 지정 유효성 검사를 수행하려고 javax.validation합니다.

나는 분야가 있습니다. 그리고이 필드에 특정 값이 입력되면 다른 몇 가지 필드는 그렇지 않습니다 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();나에게 문제를주는 것입니다. 어떻게 이것을 성취해야할지 모르겠습니다.

답변:


106

이 경우에는 다른 필드에 특정 값이있는 경우에만 한 필드가 필요하다는 것을 클래스 수준에서 확인하는 사용자 지정 유효성 검사기를 작성하는 것이 좋습니다. 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;
        }
    
    }
    
  • 유효성 검사기 사용 예 (Java 8 이상에서 최대 절전 모드 유효성 검사기> = 6)

    @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 <6, 이전 예제)

    @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하지만 BeanWrapperImplSpring Framework 에서도 사용할 수 있습니다 .

이 훌륭한 답변을 참조하십시오 : Hibernate Validator (JSR 303)를 사용한 교차 필드 유효성 검사


1
@Benedictus이 예제 는 문자열로만 작동하지만 모든 객체에서 작동하도록 수정할 수 있습니다. 두 가지 방법이 있습니다. 1) 유효성을 검사하려는 클래스로 유효성 검사기를 매개 변수화합니다 (대신 Object). 이 경우 값을 가져 오기 위해 리플렉션을 사용할 필요조차 없지만이 경우 유효성 검사기는 덜 일반적이됩니다. 2) BeanWrapperImpSpring Framework (또는 다른 라이브러리) 및 해당 getPropertyValue()메서드에서 사용합니다. 이 경우 값을 가져와 Object필요한 모든 유형으로 캐스트 할 수 있습니다.
Slava Semushin

예,하지만 Object를 주석 매개 변수로 사용할 수 없으므로 유효성을 검사하려는 각 유형에 대해 여러 가지 다른 주석이 필요합니다.
Ben

1
네, 제가 "이 경우 검증 인이 덜 일반적이된다"는 말의 의미입니다.
Slava Semushin 17.06.08

protoBuffer 클래스에이 트릭을 사용하고 싶습니다. 이것은 매우 유용합니다 (:
Saeed

좋은 솔루션입니다. 사용자 지정 주석을 작성하는 데 매우 유용합니다!
Vishwa

126

true로 확인해야하는 메서드를 정의 @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
내가 방법을 공개했습니다
티비

20

사용자 정의를 사용해야합니다. 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 jul.

2
@ user238607 getValidationGroups (MyCustomForm myCustomForm)은 빈 인스턴스 당 여러 번 호출하고 얼마 동안 null을 전달합니다. null을 전달하면 무시합니다.
pramoth

7

가능한 한 간단하게 유지하려고 노력했습니다.

인터페이스 :

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