Validation des haricots à Jersey

1. Vue d'ensemble

Dans ce didacticiel, nous allons jeter un œil à Bean Validation à l'aide du framework open source Jersey.

Comme nous l'avons déjà vu dans les articles précédents, Jersey est un framework open source pour le développement de services Web RESTful. Nous pouvons obtenir plus de détails sur Jersey dans notre introduction sur la création d'une API avec Jersey et Spring.

2. Validation des haricots à Jersey

La validation est le processus de vérification que certaines données obéissent à une ou plusieurs contraintes prédéfinies . C'est, bien sûr, un cas d'utilisation très courant dans la plupart des applications.

Le framework Java Bean Validation (JSR-380) est devenu le standard de facto pour gérer ce type d'opérations en Java. Pour récapituler sur les bases de Java Bean Validation, veuillez vous référer à notre tutoriel précédent.

Jersey contient un module d'extension pour prendre en charge la validation de Bean . Pour utiliser cette fonctionnalité dans notre application, nous devons d'abord la configurer. Dans la section suivante, nous verrons comment configurer notre application.

3. Configuration de l'application

Maintenant, nous allons nous inspirer de l'exemple simple d'API Fruit de l'excellent article de Jersey MVC Support.

3.1. Dépendances de Maven

Tout d'abord, ajoutons la dépendance Bean Validation à notre pom.xml :

 org.glassfish.jersey.ext jersey-bean-validation 2.27 

Nous pouvons obtenir la dernière version de Maven Central.

3.2. Configurer le serveur

À Jersey, nous enregistrons normalement la fonctionnalité d'extension que nous voulons utiliser dans notre classe de configuration de ressources personnalisée.

Cependant, pour l'extension de validation de bean, il n'est pas nécessaire de faire cet enregistrement. Heureusement, c'est l'une des rares extensions que le framework Jersey enregistre automatiquement.

Enfin, pour envoyer des erreurs de validation au client, nous ajouterons une propriété de serveur à notre configuration de ressource personnalisée :

public ViewApplicationConfig() { packages("com.baeldung.jersey.server"); property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true); } 

4. Validation des méthodes de ressources JAX-RS

Dans cette section, nous expliquerons deux manières différentes de valider les paramètres d'entrée à l'aide d'annotations de contraintes:

  • Utilisation des contraintes de l'API Bean Validation intégrées
  • Créer une contrainte et un validateur personnalisés

4.1. Utilisation d'annotations de contrainte intégrées

Commençons par examiner les annotations de contraintes intégrées:

@POST @Path("/create") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void createFruit( @NotNull(message = "Fruit name must not be null") @FormParam("name") String name, @NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) { Fruit fruit = new Fruit(name, colour); SimpleStorageService.storeFruit(fruit); } 

Dans cet exemple, nous créons un nouveau Fruit en utilisant deux paramètres de formulaire, le nom et la couleur . Nous utilisons l' annotation @NotNull qui fait déjà partie de l'API Bean Validation.

Cela impose une simple contrainte non nulle sur nos paramètres de formulaire. Si l'un des paramètres est nul , le message déclaré dans l'annotation sera renvoyé .

Naturellement, nous allons le démontrer avec un test unitaire:

@Test public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() { Form form = new Form(); form.param("name", "apple"); form.param("colour", null); Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED) .post(Entity.form(form)); assertEquals("Http Response should be 400 ", 400, response.getStatus()); assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null")); } 

Dans l'exemple ci-dessus, nous utilisons la classe de support JerseyTest pour tester notre ressource fruit . Nous envoyons une requête POST avec une couleur nulle et vérifions que la réponse contient le message attendu.

Pour obtenir la liste des contraintes de validation intégrées, consultez la documentation.

4.2. Définition d'une annotation de contrainte personnalisée

Parfois, nous devons imposer des contraintes plus complexes. Nous pouvons le faire en définissant notre propre annotation personnalisée.

En utilisant notre exemple simple d'API Fruit, imaginons que nous devons valider que tous les fruits ont un numéro de série valide:

@PUT @Path("/update") @Consumes("application/x-www-form-urlencoded") public void updateFruit(@SerialNumber @FormParam("serial") String serial) { //... } 

Dans cet exemple, le paramètre serial doit satisfaire les contraintes définies par @SerialNumber , que nous définirons ensuite.

Nous allons d'abord définir l'annotation de contrainte:

@Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = { SerialNumber.Validator.class }) public @interface SerialNumber { String message() default "Fruit serial number is not valid"; Class[] groups() default {}; Class[] payload() default {}; } 

Ensuite, nous définirons la classe de validation SerialNumber.Validator :

public class Validator implements ConstraintValidator { @Override public void initialize(SerialNumber serial) { } @Override public boolean isValid(String serial, ConstraintValidatorContext constraintValidatorContext) { String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$"; return Pattern.matches(serialNumRegex, serial); } } 

Le point clé ici est que la classe Validator doit implémenter ConstraintValidatorT est le type de valeur que nous voulons valider, dans notre cas une chaîne .

Enfin, nous implémentons ensuite notre logique de validation personnalisée dans la méthode isValid .

5. Validation des ressources

De plus, l'API Bean Validation nous permet également de valider des objets à l'aide de l' annotation @Valid .

Dans la section suivante, nous expliquerons deux manières différentes de valider les classes de ressources à l'aide de cette annotation:

  • Tout d'abord, demandez la validation des ressources
  • Deuxièmement, la validation des ressources de réponse

Commençons par ajouter l' annotation @Min à notre objet Fruit :

@XmlRootElement public class Fruit { @Min(value = 10, message = "Fruit weight must be 10 or greater") private Integer weight; //... } 

5.1. Demander la validation des ressources

Tout d'abord, nous allons activer la validation en utilisant @Valid dans notre classe FruitResource :

@POST @Path("/create") @Consumes("application/json") public void createFruit(@Valid Fruit fruit) { SimpleStorageService.storeFruit(fruit); } 

Dans l'exemple ci-dessus, si nous essayons de créer un fruit avec un poids inférieur à 10, nous obtiendrons une erreur de validation.

5.2. Validation des ressources de réponse

De même, dans l'exemple suivant, nous verrons comment valider une ressource de réponse:

@GET @Valid @Produces("application/json") @Path("/search/{name}") public Fruit findFruitByName(@PathParam("name") String name) { return SimpleStorageService.findByName(name); }

Notez comment nous utilisons la même annotation @Valid . Mais cette fois, nous l'utilisons au niveau de la méthode de ressource pour être sûr que la réponse est valide.

6. Gestionnaire d'exceptions personnalisé

Dans cette dernière partie, nous verrons brièvement comment créer un gestionnaire d'exceptions personnalisé. Ceci est utile lorsque nous voulons renvoyer une réponse personnalisée si nous violons une contrainte particulière.

Let's begin by defining our FruitExceptionMapper:

public class FruitExceptionMapper implements ExceptionMapper { @Override public Response toResponse(ConstraintViolationException exception) { return Response.status(Response.Status.BAD_REQUEST) .entity(prepareMessage(exception)) .type("text/plain") .build(); } private String prepareMessage(ConstraintViolationException exception) { StringBuilder message = new StringBuilder(); for (ConstraintViolation cv : exception.getConstraintViolations()) { message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n"); } return message.toString(); } }

First of all, we define a custom exception mapping provider. In order to do this, we implement the ExceptionMapper interface using a ConstraintViolationException.

Hence, we'll see that when this exception is thrown the toResponse method of our custom exception mapper instance will be invoked .

Also, in this simple example, we iterate through all the violations and append each property and message to be sent back in the response.

Next, in order to use our custom exception mapper we need to register our provider:

@Override protected Application configure() { ViewApplicationConfig config = new ViewApplicationConfig(); config.register(FruitExceptionMapper.class); return config; }

Finally, we add an endpoint to return an invalid Fruit to show the exception handler in action:

@GET @Produces(MediaType.TEXT_HTML) @Path("/exception") @Valid public Fruit exception() { Fruit fruit = new Fruit(); fruit.setName("a"); fruit.setColour("b"); return fruit; } 

7. Conclusion

Pour résumer, dans ce didacticiel, nous avons exploré l'extension de l'API Jersey Bean Validation.

Tout d'abord, nous avons commencé par présenter comment l'API Bean Validation peut être utilisée à Jersey. Nous avons également examiné comment configurer un exemple d'application Web.

Enfin, nous avons examiné plusieurs façons de faire la validation avec Jersey et comment écrire un gestionnaire d'exceptions personnalisé.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.