📜 ⬆️ ⬇️

Validators + Aspects: customize validation

Good day, Habr!

After some time, I decided to write again here and share my experience. This time the article will be about how to customize the standard validators, and call them where we need them, using Spring - aspects. Well, it inspired me to write - almost no such information, especially in Russian.

Problem


So, the essence of the application is approximately as follows: there is a gateway - api, which accepts the request, and further modifies and redirects it to the appropriate bank. The only request for each of the banks was different - as were the validation parameters. Therefore, validating the original request was not possible. There were two ways - to use annotations from javax.validation, or to write your own separate layer of validation. In the first case there was a snag - by default, objects can be validated only in the controller. In the second case, there were also disadvantages - this is an extra layer, a large amount of code, and in the case of model changes, we would have to change the validators.
')
Therefore, it was decided to find a way to pull the standard validators where it was needed, and not just in the controller.

We pull validators


After a couple of hours of digging, a couple of solutions were found in Google, the most adequate of which was to customize javax.validation.Validator and call its validate method, which should be passed a validated object as a parameter.

It would seem that a solution was found, but to automatically type a validator everywhere did not seem like a good idea, I wanted a more elegant solution.

Add AOP


Without thinking twice, I decided to try to adapt my favorite aspects to this decision.

The logic was something like this: create an annotation, and hang it over a method that converts one object into another. Further in the aspect we intercept all methods marked with this annotation and call the validate method for the value returned by them. Profit

So, the summary:

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


One of the methods for converting queries:

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

Well, actually the aspect itself:

 @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()); } } } 

Briefly about the work aspect:

We intercept the object returned by the method that is marked with the Validate annotation, then we transfer it to the validator method, which returns Set<ConstraintViolation<Object>> - in short - a set of classes with various information about validated fields and errors. If there are no errors, then the set will be empty. Next, just go through the set and create an error message, with all the fields that have not passed validation and throw away the action.

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

Conclusion


Thus, we can call validation of any objects we need at any point of the application, and if you wish, you can add an annotation and an aspect so that validation takes place not only for the methods that return the object, but also for the fields and parameters of the methods.

PS


Also, if you call a method labeled Validate from another method of the same class, remember the connection between aop and proxy .

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


All Articles