Validation des éléments de conteneur avec la validation de bean 2.0

Validation des éléments de conteneur avec la validation de bean 2.0

1. Vue d'ensemble

La version 2.0 de la spécificationJava Bean Validation ajoute plusieurs nouvelles fonctionnalités, parmi lesquelles la possibilité de valider des éléments de conteneurs.

Cette nouvelle fonctionnalité tire parti des annotations de type introduites dans Java 8. Par conséquent, Java version 8 ou supérieure est nécessaire pour fonctionner.

Des annotations de validation peuvent être ajoutées à des conteneurs tels que des collections, des objetsOptional et d'autres conteneurs intégrés et personnalisés.

Pour une introduction àJava Bean Validation et comment configurer les dépendances deMaven dont nous avons besoin, consultez nosprevious article here.

Dans les sections suivantes, nous nous concentrerons sur la validation des éléments de chaque type de conteneur.

2. Éléments de collection

Nous pouvons ajouter des annotations de validation aux éléments des collections de typejava.util.Iterable,java.util.List etjava.util.Map.

Voyons un exemple de validation des éléments d’une liste:

public class Customer {
     List<@NotBlank(message="Address must not be blank") String> addresses;

    // standard getters, setters
}

Dans l'exemple ci-dessus, nous avons défini une propriétéaddresses pour une classeCustomer, qui contient des éléments qui ne peuvent pas être videsStrings.

Notez quethe @NotBlank validation applies to the String elements, and not the entire collection. Si la collection est vide, aucune validation n'est appliquée.

Vérifions que si nous essayons d’ajouter unString vide à la listeaddresses, le framework de validation renverra unConstraintViolation:

@Test
public void whenEmptyAddress_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");

    customer.setAddresses(Collections.singletonList(" "));
    Set> violations =
      validator.validate(customer);

    assertEquals(1, violations.size());
    assertEquals("Address must not be blank",
      violations.iterator().next().getMessage());
}

Voyons ensuite comment nous pouvons valider les éléments d'une collection de typeMap:

public class CustomerMap {

    private Map<@Email String, @NotNull Customer> customers;

    // standard getters, setters
}

Notez quewe can add validation annotations for both the key and the value of a Map element.

Vérifions que l'ajout d'une entrée avec un e-mail non valide entraînera une erreur de validation:

@Test
public void whenInvalidEmail_thenValidationFails() {
    CustomerMap map = new CustomerMap();
    map.setCustomers(Collections.singletonMap("john", new Customer()));
    Set> violations
      = validator.validate(map);

    assertEquals(1, violations.size());
    assertEquals(
      "Must be a valid email",
      violations.iterator().next().getMessage());
}

3. ValeursOptional

Les contraintes de validation peuvent également être appliquées à une valeurOptional:

private Integer age;

public Optional<@Min(18) Integer> getAge() {
    return Optional.ofNullable(age);
}

Créons unCustomer avec un âge trop bas - et vérifions que cela entraîne une erreur de validation:

@Test
public void whenAgeTooLow_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    customer.setAge(15);
    Set> violations
      = validator.validate(customer);

    assertEquals(1, violations.size());
}

Par contre, si leage est nul, alors la valeur deOptional n'est pas validée:

@Test
public void whenAgeNull_thenValidationSucceeds() {
    Customer customer = new Customer();
    customer.setName("John");
    Set> violations
      = validator.validate(customer);

    assertEquals(0, violations.size());
}

4. Éléments de conteneur non génériques

Outre l'ajout d'annotations pour les arguments de type, nous pouvons également appliquer la validation aux conteneurs non génériques, tant qu'il existe un extracteur de valeur pour le type avec l'annotation@UnwrapByDefault.

Les extracteurs de valeurs sont les classes qui extraient les valeurs des conteneurs pour la validation.

The reference implementation contains value extractors for OptionalInt, OptionalLong and OptionalDouble:

@Min(1)
private OptionalInt numberOfOrders;

Dans ce cas, l'annotation@Min s'applique à la valeurInteger encapsulée, et non au conteneur.

5. Éléments de conteneur personnalisés

Outre les extracteurs de valeur intégrés, nous pouvons également définir nos propres ressources et les enregistrer avec un type de conteneur.

De cette manière, nous pouvons ajouter des annotations de validation aux éléments de nos conteneurs personnalisés.

Ajoutons une nouvelle classeProfile qui contient une propriétécompanyName:

public class Profile {
    private String companyName;

    // standard getters, setters
}

Ensuite, nous voulons ajouter une propriétéProfile dans la classeCustomer avec une annotation@NotBlank - qui vérifie que lecompanyName n'est pas unString vide:

@NotBlank
private Profile profile;

Pour que cela fonctionne, nous avons besoin d'un extracteur de valeur qui détermine la validation à appliquer à la propriétécompanyName et non directement à l'objet de profil.

Ajoutons une classeProfileValueExtractor qui implémente l'interfaceValueExtractor et remplace la méthodeextractValue():

@UnwrapByDefault
public class ProfileValueExtractor
  implements ValueExtractor<@ExtractedValue(type = String.class) Profile> {

    @Override
    public void extractValues(Profile originalValue,
      ValueExtractor.ValueReceiver receiver) {
        receiver.value(null, originalValue.getCompanyName());
    }
}

Cette classe doit également spécifier le type de la valeur extraite à l'aide de l'annotation@ExtractedValue.

De plus, nous avons ajoutéthe @UnwrapByDefault annotation that specifies the validation should be applied to the unwrapped value and not the container.

Enfin, nous devons enregistrer la classe en ajoutant un fichier appeléjavax.validation.valueextraction.ValueExtractor dans le répertoireMETA-INF/services, qui contient le nom complet de notre classeProfileValueExtractor:

org.example.javaxval.container.validation.valueextractors.ProfileValueExtractor

Maintenant, lorsque nous validons un objetCustomer avec une propriétéprofile avec uncompanyName vide, nous verrons une erreur de validation:

@Test
public void whenProfileCompanyNameBlank_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    Profile profile = new Profile();
    profile.setCompanyName(" ");
    customer.setProfile(profile);
    Set> violations
     = validator.validate(customer);

    assertEquals(1, violations.size());
}

Notez que si vous utilisezhibernate-validator-annotation-processor, l'ajout d'une annotation de validation à une classe de conteneur personnalisée, lorsqu'elle est marquée comme@UnwrapByDefault, entraînera une erreur de compilation dans la version 6.0.2.

Ceci est unknown issue et sera probablement résolu dans une version future.

6. Conclusion

Dans cet article, nous avons montré comment nous pouvons valider plusieurs types d'éléments de conteneur à l'aide deJava Bean Validation 2.0.

Vous pouvez trouver le code source complet des exemplesover on GitHub.