Frage

Ich möchte in der Lage sein, so etwas wie zu tun:

@Email
public List<String> getEmailAddresses()
{
   return this.emailAddresses;
}

Mit anderen Worten, ich möchte, dass jedes Element in der Liste als E -Mail -Adresse validiert wird. Natürlich ist es nicht akzeptabel, eine solche Sammlung zu kommentieren.

Gibt es eine Möglichkeit, dies zu tun?

War es hilfreich?

Lösung

Weder JSR-303 noch Hibernate-Validator haben eine fertige Einschränkung, die jede Sammlungselemente validieren kann.

Eine mögliche Lösung, um dieses Problem anzugehen @ValidCollection Einschränkung und entsprechende Validator -Implementierung ValidCollectionValidator.

Um jedes Sammelelement zu validieren, brauchen wir eine Instanz von Validator Innerhalb ValidCollectionValidator; Und um eine solche Instanz zu erhalten, brauchen wir eine benutzerdefinierte Implementierung von ConstraintValidatorFactory.

Sehen Sie, ob Sie gerne Lösung folgen ...

Einfach,

  • Kopieren Sie alle diese Java-Klassen (und importieren Sie Relavent-Klassen);
  • Fügen Sie Validierungs-API, HibaNat-Validator, SLF4J-Log4J12 und TestNG-Gläser auf dem ClassPath hinzu;
  • Führen Sie den Testfall aus.

ValidCollection

    public @interface ValidCollection {

    Class<?> elementType();

    /* Specify constraints when collection element type is NOT constrained 
     * validator.getConstraintsForClass(elementType).isBeanConstrained(); */
    Class<?>[] constraints() default {};

    boolean allViolationMessages() default true;

    String message() default "{ValidCollection.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

ValidCollectionValidator

    public class ValidCollectionValidator implements ConstraintValidator<ValidCollection, Collection>, ValidatorContextAwareConstraintValidator {

    private static final Logger logger = LoggerFactory.getLogger(ValidCollectionValidator.class);

    private ValidatorContext validatorContext;

    private Class<?> elementType;
    private Class<?>[] constraints;
    private boolean allViolationMessages;

    @Override
    public void setValidatorContext(ValidatorContext validatorContext) {
        this.validatorContext = validatorContext;
    }

    @Override
    public void initialize(ValidCollection constraintAnnotation) {
        elementType = constraintAnnotation.elementType();
        constraints = constraintAnnotation.constraints();
        allViolationMessages = constraintAnnotation.allViolationMessages();
    }

    @Override
    public boolean isValid(Collection collection, ConstraintValidatorContext context) {
        boolean valid = true;

        if(collection == null) {
            //null collection cannot be validated
            return false;
        }

        Validator validator = validatorContext.getValidator();

        boolean beanConstrained = validator.getConstraintsForClass(elementType).isBeanConstrained();

        for(Object element : collection) {
            Set<ConstraintViolation<?>> violations = new HashSet<ConstraintViolation<?>> ();

            if(beanConstrained) {
                boolean hasValidCollectionConstraint = hasValidCollectionConstraint(elementType);
                if(hasValidCollectionConstraint) {
                    // elementType has @ValidCollection constraint
                    violations.addAll(validator.validate(element));
                } else {
                    violations.addAll(validator.validate(element));
                }
            } else {
                for(Class<?> constraint : constraints) {
                    String propertyName = constraint.getSimpleName();
                    propertyName = Introspector.decapitalize(propertyName);
                    violations.addAll(validator.validateValue(CollectionElementBean.class, propertyName, element));
                }
            }

            if(!violations.isEmpty()) {
                valid = false;
            }

            if(allViolationMessages) { //TODO improve
                for(ConstraintViolation<?> violation : violations) {
                    logger.debug(violation.getMessage());
                    ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(violation.getMessage());
                    violationBuilder.addConstraintViolation();
                }
            }

        }

        return valid;
    }

    private boolean hasValidCollectionConstraint(Class<?> beanType) {
        BeanDescriptor beanDescriptor = validatorContext.getValidator().getConstraintsForClass(beanType);
        boolean isBeanConstrained = beanDescriptor.isBeanConstrained();
        if(!isBeanConstrained) {
            return false;
        }
        Set<ConstraintDescriptor<?>> constraintDescriptors = beanDescriptor.getConstraintDescriptors(); 
        for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) {
            if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) {
                return true;
            }
        }
        Set<PropertyDescriptor> propertyDescriptors = beanDescriptor.getConstrainedProperties();
        for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            constraintDescriptors = propertyDescriptor.getConstraintDescriptors();
            for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) {
                if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) {
                    return true;
                }
            }    
        }
        return false;
    }

}

ValidatorContextawareconstraintValidator

public interface ValidatorContextAwareConstraintValidator {

    void setValidatorContext(ValidatorContext validatorContext);

}

Sammlungstreife

    public class CollectionElementBean {

    /* add more properties on-demand */
    private Object notNull;
    private String notBlank;
    private String email;

    protected CollectionElementBean() {
    }

    @NotNull
    public Object getNotNull() { return notNull; }
    public void setNotNull(Object notNull) { this.notNull = notNull; }

    @NotBlank
    public String getNotBlank() { return notBlank; }
    public void setNotBlank(String notBlank) { this.notBlank = notBlank; }

    @Email
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

}

ConstraintValidatorFactoryImpl

public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {

    private ValidatorContext validatorContext;

    public ConstraintValidatorFactoryImpl(ValidatorContext nativeValidator) {
        this.validatorContext = nativeValidator;
    }

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        T instance = null;

        try {
            instance = key.newInstance();
        } catch (Exception e) { 
            // could not instantiate class
            e.printStackTrace();
        }

        if(ValidatorContextAwareConstraintValidator.class.isAssignableFrom(key)) {
            ValidatorContextAwareConstraintValidator validator = (ValidatorContextAwareConstraintValidator) instance;
            validator.setValidatorContext(validatorContext);
        }

        return instance;
    }

}

Angestellter

public class Employee {

    private String firstName;
    private String lastName;
    private List<String> emailAddresses;

    @NotNull
    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }

    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }

    @ValidCollection(elementType=String.class, constraints={Email.class})
    public List<String> getEmailAddresses() { return emailAddresses; }
    public void setEmailAddresses(List<String> emailAddresses) { this.emailAddresses = emailAddresses; }

}

Mannschaft

public class Team {

    private String name;
    private Set<Employee> members;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @ValidCollection(elementType=Employee.class)
    public Set<Employee> getMembers() { return members; }
    public void setMembers(Set<Employee> members) { this.members = members; }

}

Einkaufswagen

public class ShoppingCart {

    private List<String> items;

    @ValidCollection(elementType=String.class, constraints={NotBlank.class})
    public List<String> getItems() { return items; }
    public void setItems(List<String> items) { this.items = items; }

}

ValidCollectionTest

public class ValidCollectionTest {

    private static final Logger logger = LoggerFactory.getLogger(ValidCollectionTest.class);

    private ValidatorFactory validatorFactory;

    @BeforeClass
    public void createValidatorFactory() {
        validatorFactory = Validation.buildDefaultValidatorFactory();
    }

    private Validator getValidator() {
        ValidatorContext validatorContext = validatorFactory.usingContext();
        validatorContext.constraintValidatorFactory(new ConstraintValidatorFactoryImpl(validatorContext));
        Validator validator = validatorContext.getValidator();
        return validator;
    }

    @Test
    public void beanConstrained() {
        Employee se = new Employee();
        se.setFirstName("Santiago");
        se.setLastName("Ennis");
        se.setEmailAddresses(new ArrayList<String> ());
        se.getEmailAddresses().add("segmail.com");
        Employee me = new Employee();
        me.setEmailAddresses(new ArrayList<String> ());
        me.getEmailAddresses().add("me@gmail.com");

        Team team = new Team();
        team.setMembers(new HashSet<Employee>());
        team.getMembers().add(se);
        team.getMembers().add(me);

        Validator validator = getValidator();

        Set<ConstraintViolation<Team>> violations = validator.validate(team);
        for(ConstraintViolation<Team> violation : violations) {
            logger.info(violation.getMessage());
        }
    }

    @Test
    public void beanNotConstrained() {
        ShoppingCart cart = new ShoppingCart();
        cart.setItems(new ArrayList<String> ());
        cart.getItems().add("JSR-303 Book");
        cart.getItems().add("");

        Validator validator = getValidator();

        Set<ConstraintViolation<ShoppingCart>> violations = validator.validate(cart, Default.class);
        for(ConstraintViolation<ShoppingCart> violation : violations) {
            logger.info(violation.getMessage());
        }
    }

}

Ausgabe

02:16:37,581  INFO main validation.ValidCollectionTest:66 - {ValidCollection.message}
02:16:38,303  INFO main validation.ValidCollectionTest:66 - may not be null
02:16:39,092  INFO main validation.ValidCollectionTest:66 - not a well-formed email address

02:17:46,460  INFO main validation.ValidCollectionTest:81 - may not be empty
02:17:47,064  INFO main validation.ValidCollectionTest:81 - {ValidCollection.message}

Notiz:- Wenn Bean Einschränkungen hat, geben Sie die nicht an constraints Attribut von @ValidCollection Zwang. Das constraints Attribut ist notwendig, wenn Bean keine Einschränkung hat.

Andere Tipps

Ich habe nicht genug Ruf, um dies zu der ursprünglichen Antwort zu kommentieren, aber vielleicht ist es auf diese Frage erwähnenswert JSR-308 befindet sich in seiner endgültigen Veröffentlichung und wird dieses Problem angehen, wenn es veröffentlicht wird! Es wird jedoch mindestens Java 8 erfordern.

Der einzige Unterschied wäre, dass die Validierungsanentwicklung in die Typdeklaration eingehen würde.

//@Email
public List<@Email String> getEmailAddresses()
{
   return this.emailAddresses;
}

Bitte lassen Sie mich wissen, wo Sie glauben, dass ich diese Informationen am besten für andere angeben könnte, die suchen. Vielen Dank!

PS für weitere Informationen, Schauen Sie sich diesen Beitrag an.

Es ist nicht möglich, eine generische Wrapper -Annotation zu schreiben, wie @EachElement Um alle Einschränkungen zu wickeln - aufgrund von Einschränkungen der Java -Anmerkungen selbst. Sie können jedoch eine generische Einschränkungs -Validator -Klasse schreiben, die die tatsächliche Validierung jedes Elements an einen vorhandenen Einschränkung Validator delegiert. Sie müssen für jede Einschränkung eine Wrapper -Annotation schreiben, aber nur einen Validator.

Ich habe diesen Ansatz in implementiert Jirutka/Validator-Sammlung (erhältlich in Maven Central). Zum Beispiel:

@EachSize(min = 5, max = 255)
List<String> values;

Mit dieser Bibliothek können Sie einfach eine „Pseudo -Einschränkung“ für erstellen irgendein Validierungsbeschränkung, um eine Sammlung einfacher Typen zu kommentieren, ohne einen zusätzlichen Validator oder unnötigen Wrapper -Klassen für jede Sammlung zu schreiben. EachX Die Einschränkung wird für alle Standardbeschränkungen für Bean -Validierungsbeschränkungen und Winterschlafspezifische Einschränkungen unterstützt.

Um ein zu erstellen @EachAwesome für Ihre eigene @Awesome Einschränkung, kopieren Sie einfach die Annotationsklasse und fügen Sie sie ein, ersetzen Sie @Constraint Annotation mit @Constraint(validatedBy = CommonEachValidator.class) und fügen Sie die Annotation hinzu @EachConstraint(validateAs = Awesome.class). Das ist alles!

// common boilerplate
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE})
// this is important!
@EachConstraint(validateAs = Awesome.class)
@Constraint(validatedBy = CommonEachValidator.class)
public @interface EachAwesome {

    // copy&paste all attributes from Awesome annotation here
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String someAttribute();
}

Bearbeiten: Aktualisiert für die aktuelle Version der Bibliothek.

Vielen Dank für die tolle Antwort von BechOomputer06. Aber ich denke, die folgenden Anmerkungen sollten zur Definition der validCollection hinzugefügt werden:

@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidCollectionValidator.class)

Und ich untersetzt immer noch nicht, was ich mit Sammlungen primitiver Typ -Verpacker und Annotationen wie @Size, @min, @max usw. einschränken soll, da Wert nicht durch den Weg von Becomputer06 weitergegeben werden kann.

Natürlich kann ich in meiner Bewerbung für alle Fälle benutzerdefinierte konfessionelle Annotationen erstellen, aber ich muss trotzdem Eigenschaften für diese Anmerkungen zur Sammlung von Sammelverkäufen hinzufügen. Und es scheint eine schlechte Lösung zu sein.

JSR-303 hat die Möglichkeit, die Zieltypen integrierter Einschränkungen zu erweitern: siehe 7.1.2. Übergeordnete Einschränkungsdefinitionen in XML.

Sie können a implementieren ConstraintValidator<Email, List<String>> Was das Gleiche wie die angegebenen Antworten macht und an den primitiven Validator delegiert. Dann können Sie Ihre Modelldefinition beibehalten und anwenden @Email an List<String>.

Eine sehr einfache Problemumgehung ist möglich. Sie können stattdessen eine Sammlung Ihrer Klassen validieren, die die einfache Werteigenschaft einwickeln. Damit dies funktioniert, müssen Sie verwenden @Valid Annotation zur Sammlung.

Beispiel:

public class EmailAddress {

  @Email
  String email;

  public EmailAddress(String email){
    this.email = email;
  }
}

public class Foo {

  /* Validation that works */
  @Valid
  List<EmailAddress> getEmailAddresses(){
    return this.emails.stream().map(EmailAddress::new).collect(toList());
  }

}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top