«1. Обзор

Веб-приложения часто зависят от пользовательского ввода для выполнения нескольких вариантов их использования. В результате отправка формы является широко используемым механизмом для сбора и обработки данных для таких приложений.

В этом руководстве мы узнаем, как flash-атрибуты Spring могут помочь нам в безопасном и надежном рабочем процессе отправки форм.

2. Основы Flash-атрибутов

Прежде чем мы сможем комфортно использовать flash-атрибуты, нам нужно достичь приличного уровня понимания рабочего процесса отправки форм и нескольких ключевых связанных с ним концепций.

2.1. Post/Redirect/Get Pattern

Наивным способом разработки веб-формы было бы использование одного запроса HTTP POST, который обеспечивает отправку и возвращает подтверждение в своем ответе. Однако такой дизайн подвергает риску двойную обработку POST-запросов, если пользователь в конечном итоге обновит страницу.

Чтобы уменьшить проблему дублирования обработки, мы можем создать рабочий процесс как последовательность взаимосвязанных запросов в определенном порядке, а именно: POST, REDIRECT и GET. Короче говоря, мы называем это шаблоном Post/Redirect/Get (PRG) для отправки формы.

При получении POST-запроса сервер обрабатывает его, а затем передает управление для выполнения GET-запроса. Впоследствии страница подтверждения отображается в зависимости от ответа на запрос GET. В идеале, даже если последний запрос GET будет предпринят более одного раза, не должно быть каких-либо неблагоприятных побочных эффектов.

2.2. Жизненный цикл атрибутов Flash

Чтобы завершить отправку формы с использованием шаблона PRG, нам потребуется передать информацию из начального запроса POST в окончательный запрос GET после перенаправления.

К сожалению, мы не можем использовать ни RequestAttributes, ни SessionAttributes. Это связано с тем, что первый не выдержит перенаправления между разными контроллерами, а второй будет действовать в течение всего сеанса даже после завершения отправки формы.

Но нам не о чем беспокоиться, так как веб-фреймворк Spring предоставляет атрибуты флэш-памяти, которые могут решить именно эту проблему.

Давайте посмотрим на методы интерфейса RedirectAttributes, которые могут помочь нам использовать атрибуты flash в нашем проекте:

RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);

RedirectAttributes addFlashAttribute(Object attributeValue);

Map<String, ?> getFlashAttributes();

Атрибуты flash недолговечны. Таким образом, они временно хранятся в некотором базовом хранилище непосредственно перед перенаправлением. Они остаются доступными для последующего запроса после редиректа, а потом исчезают.

2.3. Структура данных FlashMap

Spring предоставляет абстрактную структуру данных под названием FlashMap для хранения атрибутов flash в виде пар ключ-значение.

Давайте посмотрим на определение класса 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:

    Output FlashMap используется в запросе POST для временного сохранения атрибутов flash и отправки их в следующий запрос GET после перенаправления Input FlashMap используется в финальном запросе GET для доступа к доступным только для чтения flash-атрибутам, которые были отправлены предыдущим запросом 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. Пример использования отправки формы

К настоящему моменту мы установили базовое понимание различных концепций, связанных с атрибутами flash. Итак, давайте двигаться дальше и использовать их в веб-приложении поэтического конкурса.

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

3.1. Конфигурация Thymeleaf

Мы будем использовать Thymeleaf, механизм шаблонов Java для создания динамических веб-страниц с помощью простых шаблонов HTML.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

Во-первых, нам нужно добавить зависимость spring-boot-starter-thymeleaf в файл pom.xml нашего проекта:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true 
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

Затем мы можем определить некоторые свойства Thymeleaf в нашем файле application.properties, расположенном в каталоге src/main/resources:

Определив эти свойства, теперь мы можем создавать все наши представления в каталоге /src/main/resources/templates. В свою очередь, Spring добавит суффикс .html ко всем представлениям, названным внутри нашего контроллера.

3.2. Модель предметной области

public class Poem {
    private String title;
    private String author;
    private String body;
}

Далее давайте определим нашу модель предметной области в классе Poem:

public static boolean isValidPoem(Poem poem) {
    return poem != null && Strings.isNotBlank(poem.getAuthor()) 
      && Strings.isNotBlank(poem.getBody())
      && Strings.isNotBlank(poem.getTitle());
}

Кроме того, мы можем добавить статический метод isValidPoem() в наш класс Poem, чтобы помочь нам проверить, что поля не позволяют пустые строки:

3.3. Создать форму

@GetMapping("/poem/submit")
public String submitGet(Model model) {
    model.addAttribute("poem", new Poem());
    return "submit";
}

Теперь мы готовы создать нашу форму отправки. Для этого нам нужна конечная точка /poem/submit, которая будет обслуживать запрос GET для отображения формы пользователю:

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

<form action="#" method="post" th:action="@{/poem/submit}" th:object="${poem}">
    <!-- form fields for poem title, body, and author -->
</form>

Кроме того, мы хотим связать форму POST с атрибутом модели стихотворения:

3.4. Post/Redirect/Get Submission Flow

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

Теперь давайте активируем действие POST для формы. Для этого мы создадим конечную точку /poem/submit в контроллере PoemSubmission для обслуживания запроса POST:

Мы можем заметить, что если отправка прошла успешно, то управление передается конечной точке /poem/success. . Кроме того, мы добавили данные стихотворения в качестве флэш-атрибута перед запуском перенаправления.

@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";
    }
}

Теперь нам нужно показать пользователю страницу подтверждения, поэтому давайте реализуем функциональность для конечной точки /poem/success, которая будет обслуживать запрос GET:

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

<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>

Наконец, давайте воспользуемся атрибутом flash на нашей странице успеха, чтобы показать название стихотворения, отправленного пользователем:

4. Заключение

В этом уроке мы узнали несколько концепций, связанных с Post/Redirect/Get pattern и flash атрибуты. И мы также видели атрибуты flash в действии с простой отправкой формы в веб-приложении Spring Boot.