1. Обзор
Веб-приложения часто зависят от пользовательского ввода для выполнения нескольких вариантов их использования. В результате отправка формы является широко используемым механизмом для сбора и обработки данных для таких приложений.
В этом руководстве мы узнаем, как flash-атрибуты Spring могут помочь нам в безопасном и надежном рабочем процессе отправки форм.
2. Основы Flash-атрибутов
Прежде чем мы сможем комфортно использовать flash-атрибуты, нам необходимо достичь приличного уровня понимания рабочего процесса отправки формы и нескольких ключевых связанных с ним концепций.
2.1. Опубликовать/перенаправить/получить шаблон
Наивным способом разработки веб-формы было бы использование одного HTTP-запроса POST , который обеспечивает отправку и возвращает подтверждение в своем ответе. Однако такой дизайн подвергает риску двойную обработку POST-запросов, если пользователь в конечном итоге обновит страницу.
Чтобы смягчить проблему дублирования обработки, мы можем создать рабочий процесс как последовательность взаимосвязанных запросов в определенном порядке, а именно POST, REDIRECT и GET . Короче говоря, мы называем это шаблоном Post/Redirect/Get (PRG) для отправки формы.
Получив запрос POST, сервер обрабатывает его, а затем передает управление для выполнения запроса GET. Впоследствии страница подтверждения отображается в зависимости от ответа на запрос GET. В идеале, даже если последний запрос GET будет предпринят более одного раза, не должно быть каких-либо неблагоприятных побочных эффектов.
2.2. Жизненный цикл атрибутов Flash
Чтобы завершить отправку формы с использованием шаблона PRG , нам потребуется передать информацию из начального запроса POST в окончательный запрос GET после перенаправления.
К сожалению, мы не можем использовать ни RequestAttributes
, ни SessionAttributes .
Это связано с тем, что первый не выдержит перенаправления между разными контроллерами, а второй будет действовать в течение всего сеанса даже после завершения отправки формы.
Но нам не нужно беспокоиться, поскольку веб-инфраструктура Spring предоставляет атрибуты flash, которые могут решить именно эту проблему.
Давайте посмотрим на методы в интерфейсе RedirectAttributes
, которые могут помочь нам использовать атрибуты flash в нашем проекте:
RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
RedirectAttributes addFlashAttribute(Object attributeValue);
Map<String, ?> getFlashAttributes();
Атрибуты Flash недолговечны . Таким образом, они временно хранятся в некотором базовом хранилище непосредственно перед перенаправлением. Они остаются доступными для последующего запроса после редиректа, а потом исчезают.
2.3. Структура данных FlashMap
Spring предоставляет абстрактную структуру данных под названием FlashMap
для хранения атрибутов флэш-памяти в виде пар ключ-значение.
Давайте посмотрим на определение класса FlashMap
:
public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {
@Nullable
private String targetRequestPath;
private final MultiValueMap<String, String> targetRequestParams
= new LinkedMultiValueMap<>(4);
private long expirationTime = -1;
}
Мы можем заметить, что класс FlashMap
наследует свое поведение от класса HashMap
. Таким образом, экземпляр FlashMap
может хранить сопоставление ключ-значение атрибутов . Кроме того, мы можем привязать экземпляр FlashMap
для использования только определенным URL-адресом перенаправления.
Кроме того, каждый запрос имеет два экземпляра FlashMap
, а именно Input FlashMap
и Output FlashMap,
которые играют важную роль в шаблоне PRG:
- Выходная
FlashMap
используется в запросе POST для временного сохранения атрибутов флэш-памяти и отправки их в следующий запрос GET после перенаправления. - Входная
карта FlashMap
используется в последнем запросе GET для доступа к доступным только для чтения атрибутам флэш-памяти, которые были отправлены предыдущим запросом POST перед перенаправлением.
2.4. FlashMapManager
и RequestContextUtils
Как следует из названия, мы можем использовать FlashMapManager
для управления экземплярами FlashMap
.
Во-первых, давайте взглянем на определение этого интерфейса стратегии:
public interface FlashMapManager {
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
Проще говоря, мы можем сказать, что FlashMapManager
позволяет нам читать, обновлять и сохранять экземпляры FlashMap
в каком-то базовом хранилище.
Далее давайте ознакомимся с несколькими статическими методами, доступными в абстрактном служебном классе RequestContextUtils
.
Чтобы не выходить за рамки этого руководства, мы ограничим наше рассмотрение методами, относящимися к атрибутам flash:
public static Map<String, ?> getInputFlashMap(HttpServletRequest request);
public static FlashMap getOutputFlashMap(HttpServletRequest request);
public static FlashMapManager getFlashMapManager(HttpServletRequest request);
public static void saveOutputFlashMap(String location,
HttpServletRequest request, HttpServletResponse response);
Мы можем использовать эти методы для извлечения входных/выходных экземпляров FlashMap
, получения FlashMapManager
для запроса и сохранения экземпляра FlashMap
.
3. Пример использования отправки формы
К настоящему времени мы установили базовое понимание различных концепций, связанных с атрибутами флэш-памяти. Итак, давайте двигаться дальше и использовать их в веб-приложении поэтического конкурса.
Наше приложение для поэтического конкурса имеет простой вариант использования: он принимает стихи от разных поэтов, отправляя форму. Кроме того, конкурсная работа будет содержать необходимую информацию, связанную со стихотворением, такую как название, основная часть и имя автора.
3.1. Конфигурация листа тимьяна
Мы будем использовать Thymeleaf , механизм шаблонов Java для создания динамических веб-страниц с помощью простых шаблонов HTML.
Во- первых, нам нужно добавить зависимость spring-boot-starter-thymeleaf
в pom.xml
нашего проекта :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
Затем мы можем определить некоторые свойства, специфичные для Thymeleaf, в нашем файле pplication.properties , расположенном в каталоге
src/main/resources
:
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
Определив эти свойства, теперь мы можем создавать все наши представления в каталоге /src/main/resources/templates
. В свою очередь, Spring добавит суффикс .html
ко всем представлениям, названным внутри нашего контроллера.
3.2. Модель домена
Далее давайте определим нашу модель предметной области в классе Poem :
public class Poem {
private String title;
private String author;
private String body;
}
Кроме того, мы можем добавить статический метод isValidPoem()
в наш класс Poem
, чтобы помочь нам проверить, что поля не допускают пустые строки:
public static boolean isValidPoem(Poem poem) {
return poem != null && Strings.isNotBlank(poem.getAuthor())
&& Strings.isNotBlank(poem.getBody())
&& Strings.isNotBlank(poem.getTitle());
}
3.3. Создать форму
Теперь мы готовы создать нашу форму отправки. Для этого нам нужна конечная точка /poem/submit
, которая будет обслуживать запрос GET, чтобы показать форму пользователю:
@GetMapping("/poem/submit")
public String submitGet(Model model) {
model.addAttribute("poem", new Poem());
return "submit";
}
Здесь мы использовали модель в качестве контейнера для хранения специфических для стихотворения данных, предоставленных пользователем. Кроме того, метод submitGet
возвращает представление, обслуживаемое представлением отправки
.
Кроме того, мы хотим связать форму POST с атрибутом модели стихотворение
:
<form action="#" method="post" th:action="@{/poem/submit}" th:object="${poem}">
<!-- form fields for poem title, body, and author -->
</form>
3.4. Поток отправки/перенаправления/получения отправки
Теперь давайте включим действие POST для формы. Для этого мы создадим конечную точку /poem/submit
в контроллере PoemSubmission
для обслуживания запроса POST :
@PostMapping("/poem/submit")
public RedirectView submitPost(
HttpServletRequest request,
@ModelAttribute Poem poem,
RedirectAttributes redirectAttributes) {
if (Poem.isValidPoem(poem)) {
redirectAttributes.addFlashAttribute("poem", poem);
return new RedirectView("/poem/success", true);
} else {
return new RedirectView("/poem/submit", true);
}
}
Мы можем заметить, что если отправка прошла успешно, то управление переходит к конечной точке /poem/success
. Кроме того, мы добавили данные стихотворения в качестве флэш-атрибута перед запуском перенаправления.
Теперь нам нужно показать пользователю страницу подтверждения, поэтому давайте реализуем функциональность для конечной точки /poem/success
, которая будет обслуживать запрос GET:
@GetMapping("/poem/success")
public String getSuccess(HttpServletRequest request) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if (inputFlashMap != null) {
Poem poem = (Poem) inputFlashMap.get("poem");
return "success";
} else {
return "redirect:/poem/submit";
}
}
Здесь важно отметить, что нам нужно проверить FlashMap
, прежде чем мы решим перенаправить на страницу успеха .
Наконец, давайте воспользуемся атрибутом flash на нашей странице успеха, чтобы отобразить название стихотворения
, отправленного пользователем:
<h1 th:if="${poem}">
<p th:text="${'You have successfully submitted poem titled - '+ poem?.title}"/>
Click <a th:href="@{/poem/submit}"> here</a> to submit more.
</h1>
4. Вывод
В этом руководстве мы изучили несколько концепций шаблона Post/Redirect/Get и атрибутов flash. И мы также видели атрибуты flash в действии с простой отправкой формы в веб-приложении Spring Boot.
Как всегда, полный исходный код руководства доступен на GitHub .