Validadores + Aspectos: personalizar validação

Bom dia, Habr!

Depois de algum tempo, decidi escrever novamente aqui e compartilhar minha experiência. Desta vez, o artigo abordará como personalizar validadores padrão e chamá-los onde for necessário, usando os aspectos do Spring. Bem, isso me incentivou a escrever - a falta de tais informações, especialmente em russo.

O problema


Portanto, a essência do aplicativo é aproximadamente o seguinte: existe um gateway - api que aceita a solicitação e a modifica e redireciona ainda mais para o banco apropriado. Mas a solicitação para cada um dos bancos foi diferente - assim como os parâmetros de validação. Portanto, não foi possível validar a solicitação inicial. Havia duas maneiras: usar anotações de javax.validation ou escrever sua própria camada de validação separada. No primeiro caso, houve um problema - por padrão, os objetos só podem ser validados no controlador. No segundo caso, havia também desvantagens - essa é uma camada extra, uma grande quantidade de código e, mesmo que os modelos fossem alterados, os validadores precisariam ser alterados.

Portanto, foi decidido encontrar uma maneira de extrair validadores padrão sempre que necessário, e não apenas no controlador.

Nós puxamos validadores


Depois de algumas horas de escavação no Google, foram encontradas algumas soluções, a mais adequada para conectar automaticamente o javax.validation.Validator e chamar o método validate nele, para o qual você precisa passar um objeto validado como parâmetro.

Parece que uma solução foi encontrada, mas validar em todos os lugares o validador não parecia uma boa ideia, eu queria uma solução mais elegante.

Adicionar AOP


Sem pensar duas vezes, decidi tentar adaptar meus aspectos favoritos a esta solução.

A lógica era aproximadamente a seguinte: crie uma anotação e pendure-a em um método que converte um objeto em outro. Além disso, interceptamos todos os métodos marcados com esta anotação e chamamos o método validate pelo valor que eles retornam. Lucro

Então anotação:

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


Um método para converter consultas é:

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

Bem, na verdade, o próprio aspecto:

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

Brevemente sobre o aspecto do trabalho:

Interceptamos o objeto retornado pelo método marcado com a anotação Validar e , em seguida, passamos para o método validador, que retornará Set<ConstraintViolation<Object>> - em resumo - um conjunto de classes com informações diferentes sobre campos e erros validados. Se não houver erros, o conjunto estará vazio. Depois, passamos pelo conjunto e criamos uma mensagem de erro, com todos os campos não validados, e executamos a execução.

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

Conclusão


Assim, podemos chamar a validação de quaisquer objetos necessários em qualquer ponto do aplicativo e, se desejado, podemos suplementar a anotação e o aspecto para que a validação passe não apenas para métodos que retornam um objeto, mas também para campos e parâmetros de métodos.

PS


Além disso, se você chamar um método marcado como Validar de outro método da mesma classe, lembre-se da conexão entre aop e proxy .

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


All Articles