Skip to content

Latest commit

 

History

History

Spring_part_18

Spring Boot lessons part 18 - Validation Starter

В папке DOC sql-скрипты и др. полезные файлы.

Док. (ссылки) для изучения:


Для начала проведем предварительную подготовку (первые 6-ть шагов из предыдущих частей, некоторые пропущены):

Шаг 1. - в файле build.gradle добавим необходимые plugin-ы:

/* 
   Плагин Spring Boot добавляет необходимые задачи в Gradle 
   и имеет обширную взаимосвязь с другими plugin-ами.
*/
id 'org.springframework.boot' version '3.1.3'

/* 
   Менеджер зависимостей позволяет решать проблемы несовместимости 
   различных версий и модулей Spring-а
*/
id "io.spring.dependency-management" version '1.0.11.RELEASE'

/* Подключим Lombok */
id "io.freefair.lombok" version "8.3"

Шаг 2. - подключаем Spring Boot starter:

/* 
   Подключим Spring Boot Starter он включает поддержку 
   авто-конфигурации, логирование и YAML
*/
implementation 'org.springframework.boot:spring-boot-starter'

Шаг 3. - подключаем блок тестирования (Spring Boot Starter Test) (он будет активен на этапе тестирования):

testImplementation 'org.springframework.boot:spring-boot-starter-test'

Шаг 4. - автоматически Gradle создал тестовую зависимость на Junit5 (мы можем использовать как Junit4, так и TestNG):

test {
    useJUnitPlatform()
}

Шаг 5. - подключим блок для работы с БД (Spring Boot Starter Data Jpa):

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

Для работы с PostgreSQL подключим и его зависимости:

implementation 'org.postgresql:postgresql'

Шаг 6. - Для использования средств подобных Hibernate ENVERS:

implementation 'org.springframework.data:spring-data-envers'

Шаг 7. - Подключим миграционный фреймворк Liquibase:

implementation 'org.liquibase:liquibase-core'

Шаг 8. - Подключаем Wed - Starter:

implementation 'org.springframework.boot:spring-boot-starter-web'

Шаг 9. - Поскольку сейчас мы начинаем изучение Thymeleaf, то нам нужно его подключить:

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

Хотя мы уже не используем явно *.JSP страницы, мы все же оставим зависимость Jasper-a:

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'

Lesson 89 - Validation-Starter - Простой пример валидации получаемых данных.

Для подгрузки зависимостей Validation Starter-а прописываем его в нашем build.gradle:

implementation 'org.springframework.boot:spring-boot-starter-validation'

Если все прошло нормально, то в зависимостях Gradle-а мы увидим validation-api (Hibernate JSR303) см. DOC/GradleDependencies.jpg совсем немного мы затронули данный вопрос в Hibernate lessons part 12 - Practice урок Lesson 47. Если нам не нужны особые способы обработки ошибок при валидации, то обычно встроенных аннотаций из данной спецификации будет достаточно.

Документация см. (ENG):

Краткое описание валидации в статьях см.:

В качестве примера:

Шаг 1. - поставим некоторые аннотации над классами нашей модели, например, UserCreateEditDto.java см. комментарии в коде класса;

Шаг 2. - запускаем процесс валидации аннотировав соответствующим образом аргумент UserCreateEditDto переданный в метод контроллера, в нашем случае это метод *.update() из UserController.java:

@PostMapping("/{id}/update")
public String update(@PathVariable("id") Long id, @ModelAttribute @Validated UserCreateEditDto user) {
    // ... some code ...
}

Тут мы могли применить либо @Valid из jakarta.validation, либо, как в нашем случае @Validated из org.springframework.validation.annotation. Особенность аннотации @Validated в том, что она к основному функционалу добавляет возможность валидации по группам (хотя с jakarta.validation сие тоже возможно).

В данном случае мы сделали грубую валидацию, без каких либо обработок и подсказок на стороне фронтэнда, что легко проверить запустив приложение и в форме templates/user/user.html умышленно внести ошибку, например, вместо email-a передать бессмысленную строку. Мы получим: Whitelabel Error Page с There was an unexpected error (type=Bad Request, status=400).

См. док.:


Lesson 90 - Validation-Starter - Чуть более дружелюбный отклик приложения при валидации данных.

Первый пример валидации показал, что использование аннотаций при валидации данных, вроде бы простая процедура. Однако такой грубый вариант не дружественен к пользователю приложения и необходимо такие неверные запросы обрабатывать и подсказывать пользователю, что нужно сделать для исправления ситуации, т.к. это все же его ошибка. Чуть улучшим дружелюбие интерфейса на примере формы регистрации - templates/user/registration.html.

Шаг 1. - Добавим аннотацию @Validated к передаваемому аргументу 'UserCreateEditDto user' метода *.create() нашего контроллера;

Шаг 2. - Туда же в аргументы внедряем новый объект BindingResult из org.springframework.validation см. ниже;

@PostMapping
public String create(@ModelAttribute @Validated UserCreateEditDto user,
                                                BindingResult bindingResult,                                         
                                                RedirectAttributes redirectAttributes) {
    // ... some code ...
}

!!! Особенность !!! Как это ни странно, при добавлении BindingResult важно его местоположение в последовательности 
                    передаваемых аргументов метода - сразу после аргумента помеченного как @Validated !!! 

В данном случае мы указываем системе, что сами планируем обрабатывать ошибки и объект BindingResult будет их хранить. Естественно мы получая к ним доступ можем взаимодействовать, как с приложением, так и с пользователем см. DOC/BindingResult.txt.

Шаг 3. - В форму регистрации templates/user/registration.html добавляем 'обработчик ошибок' - соответствующий блок отображающий проблемы при валидации введенных данных.

<div th:if="${errors}">
    <p style="color: red" th:each="error : ${errors}" th:text="${error.defaultMessage}">Error message</p>
</div>

Запускаем приложение, переходим в форму регистрации, вносим ошибки при заполнении формы и смотрим, что получилось.

См. док.:


Lesson 91 - Custom-validator - Самописные валидаторы или самописные аннотации для валидации.

Предположим, что написанных за нас и для нас аннотаций из validation пакетов Jakarta и org.springframework.validation.annotation не хватает. Мы можем сами создать интерфейс валидатора:

Шаг 1. - Создадим папку validation, где будем хранить наши файлы ограничений (валидации).

Шаг 2. - В папке validation создадим нашу первую самописную аннотацию для валидации UserInfo.java, в которой должны быть обязательные поля см. ниже:

String message() default "... некое сообщение об ошибке валидации ... "; // Задали выводимое сообщение
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };

И аннотации нас самим классом:

@Constraint(validatedBy = {  })
@Target(ElementType.TYPE, ...)
@Retention(RetentionPolicy.RUNTIME)

См. более подробные примеры в DOC/ValidationWithSpringBoot/ValidationWithSpringBoot.txt и DOC/ArticleAboutValidation.

Шаг 3. - Создаем сам валидатор, который будет обрабатывать нашу аннотацию UserInfo, это будет UserInfoValidator.java и реализуем метод *.isValid(UserCreateEditDto value, ConstraintValidatorContext context).

Шаг 4. - Применяем нашу аннотацию над классом UserCreateEditDto и удаляем аннотации @NotNull с полей lastname и firstname, т.к. проверку валидности этих переменных будет отрабатывать наш самописный валидатор.

Хороший пример, который описывает наши шаги приведен в DOC/ValidationWithSpringBoot/ValidationWithSpringBoot.txt

Шаг 5. - Запускаем наше приложение, обращаемся к форме регистрации, вносим ошибки при заполнении, и видим результат работы нескольких валидаций в классе UserCreateEditDto и нашего валидатора, в том числе.

См. док.:


Lesson 92 - Custom-validator - Использование групп проверки.

Используя аннотацию @Validated см. DOC/InterfaceValidated.txt мы можем управлять группами см. DOC/ValidationWithSpringBoot/ValidationWithSpringBoot.txt, как бы разделяя логику проверки на различные этапы и при различных условиях работы приложения.

Шаг 1. - Зайдем в наш UserController.java, и в методе *.create() в аннотации @Validated добавлю группу Default.class, она запускает все аннотации не использующие группы и предоставленные Spring. Если ее не указать в списке групп, то мы переназначим настройки запускаемых аннотаций (лучше ее оставлять):

@PostMapping
public String create(@ModelAttribute @Validated ({Default.class, CreateAction.class}) UserCreateEditDto user,
                                                                                 BindingResult bindingResult,
                                                                       RedirectAttributes redirectAttributes) {
}

Шаг 2. - Создадим интерфейс метку CreateAction, группа это обычно класс или интерфейс маркер и никакой программной логики в себе не несет. Наши интерфейсы метки поместим в папку java/spring/oldboy/validation/group. Туда же добавим еще интерфейс UpdateAction. Его мы добавим в аннотацию метода *.update():

@PostMapping("/{id}/update")
public String update(@PathVariable("id") Long id,
                     @ModelAttribute @Validated({Default.class, UpdateAction.class}) UserCreateEditDto user) {
}

В данном случае мы как бы разделяем проверку на два отдельных направления первая будет активна на этапе создания User, а вторая на этапе обновления его.

Шаг 3. - Добавляем нашу метку (интерфейс маркер) или группу в аннотацию @UserInfo, класса UserCreateEditDto:

@UserInfo(groups = CreateAction.class)
public class UserCreateEditDto {
}

Применив (groups = CreateAction.class), мы как бы говорим, что проверку валидности нужно проводить на этапе создания и только. Если же мы применим (groups = UpdateAction.class), то проверок на этапе создания проводится не будет, только обновления. Это легко проверить изменяя маркерные интерфейсы в параметрах аннотации @UserInfo. При различных параметрах будут активироваться разные сообщения об ошибках валидности, в зависимости от примененного группы - интерфейса маркера.


Lesson 93 - ControllerAdvice и ExceptionHandler - Самописные обработчики исключений.

В уроке 90, мы пытались по своему обработать отклик стандартного Spring валидатора на умышленно внесенных ошибки, мы сделали страницу отображения более информативной для пользователя, показывая ему подсказки связанные с ошибками. Для этого мы использовали объект BindingResult. В принципе это интересный вариант обработки ошибок валидации.

Однако существует другой вариант, когда мы применяем обработчики исключений - ExceptionHandler. Мы также можем пойти двумя путями и в первом случае написать метод обработчик исключений прямо в теле контроллера, например, UserController.

Данный способ более универсальный и часто используется в REST API.


REST (от англ. REpresentational State Transfer — «передача репрезентативного состояния» или «передача „самоописываемого“ состояния») — архитектурный стиль взаимодействия компонентов распределённого приложения в сети. Другими словами, REST - это набор правил того, как программисту организовать написание кода серверного приложения, чтобы все системы легко обменивались данными и приложение можно было масштабировать.

REST представляет собой согласованный набор ограничений, учитываемых при проектировании распределённой гипермедиа- системы. В определённых случаях (интернет-магазины, поисковые системы; прочие системы, основанные на данных) это приводит к повышению производительности и упрощению архитектуры.


Попробуем реализовать наш обработчик исключений валидации:

Шаг 1. - Создадим локальный обработчик ошибок - метод в UserController, и аннотируем наш контроллер @Slf4j:

@ExceptionHandler(Exception.class)
public String handleExceptions(Exception exception, HttpServletRequest request) {
    log.error("Failed to return response", exception);
    return "error/error500";
}

Теперь наш обработчик ловит исключения и отображает некую страницу с ошибкой.

Шаг 2. - В папке где хранятся наши *.HTML страницы создадим еще папку 'error', где будут храниться страницы отображения ошибок, возвращаемые пользователю при бросках исключений.

Шаг 3. - Создадим страницу отображения в папке 'error' для показа пользователю, в нашем методе *.handleExceptions() она уже прописана как возвращаемая на запрос в случае броска исключения - templates/error/error500.html.

Щаг 4. - Проверим работу обработчика - запустим приложение и обратимся к методу *.update(). Поскольку в него мы не передаем BindingResult, то ошибка валидации при работе этого метода подхватится нашим глобальным обработчиком исключений и отобразится страница - error500.html.

При запуске приложения в режиме DEBUG мы можем наглядно отследить, как происходит работа метода обработчика исключений и какие объекты созданы, какие объекты сгенерировали ошибки, какие ошибки отловлены см. DOC/MyHandleExceptionsWork.jpg Фактически мы снова получили в распоряжение BindingResult завернутый в исключение.

Естественно так будет вести себя наш обработчик только внутри нашего UserController-а, а что если мы хотим подобным образом обрабатывать ошибки валидации во всех контроллерах.

Сделаем это.


Lesson 94 - ControllerAdvice и ExceptionHandler - Глобальный самописный обработчик исключений.

Для решения вопроса обработки исключения в процессе валидации поучаемых данных, мы вынесем ранее разработанный метод *.handleExceptions() из нашего контроллера UserController в отдельный класс, чтобы и другие контроллеры могли к нему обращаться минуя прежние неудобства (сам метод закомментируем).

Шаг 1. - Создадим папку 'handler', а в ней создадим класс контроллер-обработчик ошибок - ControllerExceptionHandler.java.

Шаг 2. - Из класса UserController.java перенесем метод *.handleExceptions() в ControllerExceptionHandler.java.

Шаг 3. - Для того чтобы другие контроллеры смогли использовать новый класс обработчик помечаем его аннотацией @ControllerAdvice см.:

@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {
 . . . some method . . .
}

Шаг 4. - Запускаем приложение для тестирования работы обработчика-валидации.

См. док.:


См. официальные Guides:

  • Getting Started Guides - Эти руководства, рассчитанные на 15–30 минут, содержат быстрые практические инструкции по созданию «Hello World» для любой задачи разработки с помощью Spring. В большинстве случаев единственными необходимыми требованиями являются JDK и текстовый редактор.
  • Topical Guides - Тематические руководства предназначенные для прочтения и понимания за час или меньше, содержит более широкий или субъективный контент, чем руководство по началу работы.
  • Tutorials - Эти учебники, рассчитанные на 2–3 часа, обеспечивают более глубокое контекстное изучение тем разработки корпоративных приложений, что позволяет вам подготовиться к внедрению реальных решений.