Главная » Хабрахабр » Validators + Aspects: кастомизируем валидацию

Validators + Aspects: кастомизируем валидацию

Доброго времени суток, Хабр!

На этот раз статья будет о том, как кастомизировать стандартные валидаторы, и вызывать их там, где нам будет нужно, используя Spring — аспекты. Спустя некоторое время решил вновь написать сюда и поделиться своим опытом. Ну а сподвигло меня на написание — практически отсутствие подобной информации, особенно на русском.

Проблема

Итак, суть приложения примерно такова: есть gateway — api, который принимает запрос, а в дальнейшем модифицирует и перенаправляет его соответствующему банку. Вот только запрос для каждого из банков отличался — как и параметры валидации. Поэтому валидировать изначальный запрос не представлялось возможным. Тут было два пути — использовать аннотации из javax.validation, либо писать свой отдельный слой валидации. В первом случае была загвоздка — по умолчанию объекты можно валидировать только в контроллере. Во втором случае так-же были минусы — это лишний слой, большое количество кода, да и в случае изменения моделей, пришлось бы менять и валидаторы.

Поэтому было принято решение найти способ дергать стандартные валидаторы там где это было необходимо, а не только в контроллере.

Дергаем валидаторы

Спустя пару часов копания в гугле были найдены пару решений, самое адекватное из которых было заавтовайрить javax.validation.Validator и вызвать у него метод validate, которому в качестве параметра нужно передать валидируемый объект.

Казалось бы, решение найдено, но автовайрить везде валидатор не казалось хорошей идеей, хотелось более элегантного решения.

Добавляем АОП

Недолго думая я решил попробовать адаптировать под это решение мною всеми любимые аспекты.

Дальше в аспекте перехватываем все методы помеченные этой аннотацией и вызываем метод validate для возвращаемого ими значения. Логика была примерно следующей: создаём аннотацию, и вешаем её над методом который преобразует один объект в другой. Профит.
Итак, аннотация:

// будет работать только для методов @Target() @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Validate {}

Один из методов преобразующих запросы:

@Validate public SomeBankRequest requestToBankRequest(Request<T> request) { SomeBankRequest bankRequest = ...; ... // Преобразуем реквест в реквест для конкретного банка ... return bankRequest; }

Ну и собственно сам аспект:

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

Коротко о работе аспекта:

В случае если ошибок не будет, то и сэт будет пустым. Перехватываем объект возвращаемый методом, который помечен аннотацией Validate, дальше передаём его в метод валидатора, который вернёт нам Set<ConstraintViolation<Object>> — если коротко — сэт классов с различной информацией о валидируемых полях и ошибках. Дальше просто проходимся по сэту и создаём сообщение об ошибке, со всеми полями не прошедшими валидацию и выбрасываем экзепшн.

violation.getPropertyPath() - возвращает название поля violation.getMessage() - конкретное сообщение, почему данное поле не прошло валидацию

Заключение

Таким образом, мы можем вызывать валидацию любых нужных нам объектов в любой точке приложения, а при желании можно дополнить аннотацию и аспект, чтобы валидация проходила не только для методов, возвращающих объект, но и для полей и параметров методов.

P.S.

Так-же если вызываете метод помеченный Validate из другого метода этого же класса, помните о связи аоп и прокси.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Перевезти дата-центр за 14 400 секунд

Всем знакома пословица «Один переезд равен двум пожарам». Смысл этой народной мудрости в том, что процесс переезда сопряжен со стрессами, суетой, переживаниями и, конечно, беготней, которые бывают и при пожаре, а подчас и с утратами ценного имущества. К тому же, ...

Дорожная карта математических дисциплин для машинного обучения, часть 1

Вместо предисловия Допустим, сидя вечерком в теплом кресле вам вдруг пришла в голову шальная мысль: «Хм, а почему бы мне вместо случайного подбора гиперпараметров модели не узнать, а почему оно всё работает?»Это скользкий путь — вы думаете, что достаточно пары ...