Формы — это основной способ взаимодействия пользователя с сервером через веб-интерфейс. Будь то регистрация, добавление данных или выполнение поискового запроса, формы являются мостом между фронтендом и бэкендом. Spring MVC предоставляет удобные инструменты для обработки пользовательских данных через формы, их валидации и передачи обратно в представления, если что-то пошло не так.
Обработка форм: пошаговый процесс
Чтобы лучше понять, как обрабатываются формы в Spring MVC, разобьём весь процесс на основные шаги:
- Создание формы в HTML с использованием Thymeleaf.
- Настройка модели для передачи данных.
- Создание контроллера для обработки данных формы.
- Валидация данных с помощью аннотации
@Valid. - Реакция на ошибки и отправка данных обратно в форму.
Создадим форму: HTML + Thymeleaf
Начнём с простого примера — регистрация пользователя. Вот HTML-фрагмент формы:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Регистрация</title>
</head>
<body>
<h1>Регистрация</h1>
<form th:action="@{/register}" method="post" th:object="${user}">
<label for="name">Имя:</label>
<input type="text" id="name" th:field="*{name}" />
<br/>
<label for="email">Email:</label>
<input type="email" id="email" th:field="*{email}" />
<br/>
<label for="password">Пароль:</label>
<input type="password" id="password" th:field="*{password}" />
<br/>
<button type="submit">Зарегистрироваться</button>
</form>
</body>
</html>
Разберём основные элементы:
th:action="@{/register}"— URL, куда будет отправлена форма. Здесь мы ссылаемся на контроллер с маршрутом/register.th:object="${user}"— объект, связанный с этой формой (модель). В нашем случае это объектuser.th:field="*{name}"— связывает поле формы с полем объектаuser.name.
Форма создаст HTTP POST-запрос с данными, введёнными пользователем.
Модель данных: создание класса DTO
Теперь создадим объект User для связывания данных, поступающих из формы. Это POJO-класс с нужными полями:
package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class User {
@NotBlank(message = "Имя не должно быть пустым")
@Size(min = 2, max = 30, message = "Имя должно быть длиной от 2 до 30 символов")
private String name;
@NotBlank(message = "Email не должен быть пустым")
@Email(message = "Введите корректный email")
private String email;
@NotBlank(message = "Пароль не должен быть пустым")
@Size(min = 6, message = "Пароль должен быть длиной минимум 6 символов")
private String password;
// Getters и Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Интересные моменты:
- Аннотации из пакета
jakarta.validation.constraintsпомогут нам валидировать поля. Например,@Emailпроверит корректность email, а@Sizeограничит длину текста.
Контроллер для обработки данных
Теперь сделаем контроллер, который будет показывать страницу с формой и обрабатывать отправленные данные.
package com.example.demo.controller;
import com.example.demo.dto.User;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class RegistrationController {
@GetMapping("/register")
public String showRegistrationForm(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register")
public String processRegistrationForm(@Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
return "register"; // Вернуть пользователя на форму, если есть ошибки
}
// Здесь вы можете сохранить данные в базу или отправить куда-то ещё
model.addAttribute("message", "Регистрация прошла успешно!");
return "success";
}
}
@GetMapping("/register"): показывает страницу с формой. Мы добавляем пустой объектUserв модель, чтобы Thymeleaf мог с ним работать.@PostMapping("/register"): обрабатывает POST-запрос. Обратите внимание на аннотацию@Validперед параметромUser. Это говорит Spring, что нужно выполнить валидацию этого объекта.BindingResult result: хранит результаты валидации. Если есть ошибки, мы возвращаем пользователя обратно на форму.
Валидация на стороне сервера
Допустим, пользователь ввёл что-то некорректно. Нам нужно отобразить ошибки на странице:
<form th:action="@{/register}" method="post" th:object="${user}">
<label for="name">Имя:</label>
<input type="text" id="name" th:field="*{name}" />
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
<br/>
<label for="email">Email:</label>
<input type="email" id="email" th:field="*{email}" />
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
<br/>
<label for="password">Пароль:</label>
<input type="password" id="password" th:field="*{password}" />
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
<br/>
<button type="submit">Зарегистрироваться</button>
</form>
Что происходит?
Если валидация объекта User выявила ошибки, метод #fields.hasErrors() вернёт true для полей с ошибками. Аннотация th:errors автоматически выведет описание ошибки, заданное в модели.
Глобальные ошибки формы (например, совпадение паролей)
Если вам нужно добавить общую ошибку для формы, это можно сделать через BindingResult:
if (!user.getPassword().equals(user.getConfirmPassword())) {
result.rejectValue("password", "error.user", "Пароли не совпадают");
}
Или глобальную ошибку:
if (user.getEmail().contains("spam")) {
result.reject("email.spam", "Email не может содержать 'spam'");
}
Подводные камни и типичные ошибки
Почему BindingResult всегда должен быть сразу после @Valid?
Spring обрабатывает валидацию и записывает результаты в объект BindingResult. Если вы поменяете порядок аргументов метода, это вызовет IllegalStateException.
Осторожно с названиями полей!
Имена полей формы (например, th:field="*{name}") должны точно совпадать с именами полей в объекте User. Иначе данные не будут связаны корректно.
Что дальше?
Теперь вы умеете обрабатывать формы, связывать их с объектами и валидировать данные. Эти знания пригодятся вам для создания функциональных интерфейсов регистрации, авторизации, создания или редактирования сущностей в вашем приложении.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ