Validateurs + Aspects: personnaliser la validation

Bonjour, Habr!

Après un certain temps, j'ai décidé d'écrire à nouveau ici et de partager mon expérience. Cette fois, l'article expliquera comment personnaliser les validateurs standard et les appeler là où nous en avons besoin en utilisant les aspects Spring. Eh bien, cela m'a encouragé à écrire - le manque de telles informations, surtout en russe.

Le problème


Ainsi, l'essence de l'application est approximativement la suivante: il existe une passerelle - api qui accepte la demande, la modifie et la redirige vers la banque appropriée. Mais la demande pour chacune des banques était différente - ainsi que les paramètres de validation. Par conséquent, il n'a pas été possible de valider la demande initiale. Il y avait deux façons - d'utiliser les annotations de javax.validation ou d'écrire votre propre couche de validation distincte. Dans le premier cas, il y avait un hic - par défaut, les objets ne peuvent être validés que dans le contrôleur. Dans le second cas, il y avait également des inconvénients - il s'agit d'une couche supplémentaire, d'une grande quantité de code, et même si les modèles étaient modifiés, les validateurs devraient être modifiés.

Par conséquent, il a été décidé de trouver un moyen d'extraire les validateurs standard lorsque cela était nécessaire, et pas seulement dans le contrôleur.

Nous tirons des validateurs


Après quelques heures de fouille dans Google, deux solutions ont été trouvées, la plus adéquate étant de câbler automatiquement javax.validation.Validator et d'appeler la méthode validate dessus, qui doit passer un objet validé comme paramètre.

Il semblerait qu'une solution ait été trouvée, mais valider partout le validateur ne semblait pas être une bonne idée, je voulais une solution plus élégante.

Ajouter AOP


Sans réfléchir à deux fois, j'ai décidé d'essayer d'adapter mes aspects préférés à cette solution.

La logique était approximativement la suivante: créer une annotation et la suspendre à une méthode qui convertit un objet en un autre. Plus loin dans l'aspect, nous interceptons toutes les méthodes marquées avec cette annotation et appelons la méthode validate pour la valeur qu'elles retournent. Bénéfice

Donc annotation:

//      @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Validate {} 


Une méthode de conversion des requêtes est la suivante:

 @Validate public SomeBankRequest requestToBankRequest(Request<T> request) { SomeBankRequest bankRequest = ...; ... //        ... return bankRequest; } 

Eh bien, en fait, l'aspect lui-même:

 @Aspect @Component public class ValidationAspect { private final Validator validator; //    public ValidationAspect(Validator validator) { this.validator = validator; } //       // @Validate       @AfterReturning(pointcut = "@annotation(api.annotations.Validate)", returning = "result") public void validate(JoinPoint joinPoint, Object result) { //     Set<ConstraintViolation<Object>> violations = validator.validate(result); //    ,    ,     //        if (!violations.isEmpty()) { StringBuilder builder = new StringBuilder(); //          ,   //  violations.forEach(violation -> builder .append(violation.getPropertyPath()) .append("[" + violation.getMessage() + "],")); throw new IllegalArgumentException("Invalid values for fields: " + builder.toString()); } } } 

En bref sur l'aspect travail:

Nous interceptons l'objet retourné par la méthode marquée avec l'annotation Validate , puis nous la transmettons à la méthode validator, qui renverra Set<ConstraintViolation<Object>> - en bref - un ensemble de classes avec différentes informations sur les champs validés et les erreurs. S'il n'y a pas d'erreurs, l'ensemble sera vide. Ensuite, nous passons simplement par l'ensemble et créons un message d'erreur, avec tous les champs non validés, et lançons l'exécution.

 violation.getPropertyPath() -    violation.getMessage() -  ,       

Conclusion


Ainsi, nous pouvons appeler la validation de tous les objets dont nous avons besoin à tout moment dans l'application, et si vous le souhaitez, nous pouvons compléter l'annotation et l'aspect afin que la validation passe non seulement pour les méthodes qui renvoient un objet, mais aussi pour les champs et les paramètres des méthodes.

PS


De plus, si vous appelez une méthode marquée Validate à partir d'une autre méthode de la même classe, n'oubliez pas la connexion entre aop et proxy .

Source: https://habr.com/ru/post/fr432040/


All Articles