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