Формы
Через API статьи создают программы, а людям нужна обычная HTML-форма. Thymeleaf связывает поля формы с объектом Java: показывает в полях его значения, а после отправки Spring собирает объект обратно из полей. Проверка @Valid работает так же, как в API, только ошибки выводятся прямо в форме.
Показ формы
// src/main/java/com/example/blog/PageController.java
@GetMapping("/posts/new")
public String newPost(Model model) {
model.addAttribute("post", new NewPostRequest("", "", null));
model.addAttribute("users", users.findAll());
return "posts/form";
}В модель кладём пустую запись NewPostRequest — форма заполнится её значениями, и список пользователей для выбора автора. UserRepository users добавьте в конструктор PageController.
Шаблон формы
<!-- src/main/resources/templates/posts/form.html -->
<form th:action="@{/posts}" th:object="${post}" method="post">
<label>Заголовок
<input type="text" th:field="*{title}">
</label>
<p class="error" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Ошибка</p>
<label>Текст
<textarea th:field="*{body}" rows="8"></textarea>
</label>
<p class="error" th:if="${#fields.hasErrors('body')}" th:errors="*{body}">Ошибка</p>
<label>Автор
<select th:field="*{authorId}">
<option th:each="user : ${users}" th:value="${user.id}" th:text="${user.username}">anna</option>
</select>
</label>
<button type="submit">Опубликовать</button>
</form>th:object="${post}" выбирает объект формы, а th:field="*{title}" связывает поле с его компонентом: Thymeleaf сам поставит id, name и value. th:errors выводит сообщения об ошибках поля. Шаблону хватает компонентов записи — геттеры не нужны.
Обработка отправки
// src/main/java/com/example/blog/PageController.java
@PostMapping("/posts")
public String create(@Valid @ModelAttribute("post") NewPostRequest form,
BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("users", users.findAll());
return "posts/form";
}
PostResponse post = postService.create(form);
return "redirect:/posts/" + post.id();
}@ModelAttribute("post") собирает запись из полей формы. BindingResult хранит результат проверки: при ошибках мы снова показываем форму — введённые значения и сообщения окажутся на месте. При успехе возвращаем redirect: — браузер перейдёт на страницу новой статьи. Так повторное обновление страницы не отправит форму второй раз.
Проверка
Откройте http://localhost:8080/posts/new и нажмите «Опубликовать» с пустыми полями — под полями появится «не должно быть пустым». То же видно через curl:
curl -i -X POST http://localhost:8080/posts -d "title=Из формы&body=Написано в браузере&authorId=2"HTTP/1.1 302
Location: http://localhost:8080/posts/2Код 302 и заголовок Location — это и есть переадресация после успешной отправки.
Частая ошибка
BindingResult должен идти в параметрах сразу после проверяемого объекта. Если поставить между ними другой параметр, например Model, Spring решит, что обрабатывать ошибки вы не собираетесь: метод не вызовется, а пользователь вместо формы с подсказками увидит страницу ошибки 400.