Spring Web Flow — интеграция с Spring MVC (Spring MVC Integration)

11. Spring Web Flow — интеграция с Spring MVC (Spring MVC Integration)

11.1. Введение

В этой главе будет показано как интегрировать Web Flow в приложение Spring MVC. В приложении booking-mvc хорошо показано использование Spring MVC с Web Flow. Это приложение демонстрирует упрощенный туристический сайт, позволяющий искать и бронировать номера в отеле.

11.2. Настройки web.xml

Первым шагом для использования Spring MVC является настройка DispatcherServlet в web.xml. Обычно это необходимо сделать только один раз для конкретного приложения.

Пример ниже маппит все запросы начинающиеся с /spring/ в DispatcherServlet. Атрибут init-param используется для доступа к contextConfigLocation. Вот файл конфигурации для веб приложения.

11.3. Диспетчер для потоков

DispatcherServlet маппит запросы для ресурсов приложения в обработчики (handler). Поток — один из видов обработчика.

11.3.1. Регистрация FlowHandlerAdapter

Первым шагом в распределении запросов к потокам является включение обработчика потока (flow handling) внутри Spring MVC. Для этого зарегистрируйте FlowHandlerAdapter:

11.3.2. Определение маппинга потока

После того как был включен flowHandlerAdapter необходимо сопоставить конкретные ресурсы приложения для ваших потоков. Самый простой способ для этого — определить flowHandlerMapping:

Настройка этого маппинга позволит Dispatcher’у сопоставить пути ресурсов приложения к потокам из реестра потоков. К примеру, доступ по пути /hotels/booking приведет к регистрации запроса для потока с id hotels/booking. Если поток с таким id будет найден, то запрос будет обработан этим потоком. Если поток не будет найден, то будет запрошен следующий по очереди обработчик в Dispatcher’е или же будет возвращен ответ noHandlerFound.

11.3.3. Рабочий процесс обработки в потоке

Когда найден валидный поток, FlowHandlerAdapter выясняет начать ли новое выполнение (execution) этого потока или вернуть действующий execution, основанный на информации, представленной в HTTP запросе.  Существует ряд событий по умолчанию, которые начинают или возобновляют вычисления в потоке, используемые адаптером:

  • Параметры HTTP запроса доступны в внутренней карте всех стартовых вычислений потока.
  • В случае, если вычисление в потоке заканчивается без отправки ответа, то обработчик по умолчанию попытается начать новое вычисление в этом же запросе.
  • Необработанные исключения распространяются к диспетчеру сервлетов, кроме исключения NoSuchFlowExecutionException. Обработчик по умолчанию попытается восстановиться из исключения NoSuchFlowExecutionException путем старта нового вычисления.

Для более детальной информации обратитесь к API для FlowHandlerAdapter. Вы можете переопределить настройки по умолчанию используя подклассы или реализуя свой собственный FlowHandler, который будет рассмотрен в следующем разделе.

11.4. Реализация собственных FlowHandlers

FlowHandler может быть использован для настройки того, как потоки будут выполняться в среде HTTP сервлетов. FlowHandler использует FlowHandlerAdapter и ответственен за:

  • Возврат id опеределения потока для выполнения
  • Создание входа для предоставления новых вычислений при старте потока
  • Обработка outcomes, которые были возвращены после вычислений при выходе из этого потока
  • Обработка любых исключений выброшенных вычислениями внутри этого потока

Эти обязанности проиллюстрированы в описании интерфейса  org.springframework.mvc.servlet.FlowHandler:

Для реализации FlowHandler используется подкласс AbstractFlowHandler. Все эти операции являются обязательными и если их не реализовать, то будут применяться операции по умолчанию. Вам необходимо только переопределить только те методы, которые считаете нужными. В частности:

  • Переопределить getFlowId(HttpServletRequest) в случае, когда id вашего потока не может быть получен непосредственно из HTTP запроса. По умолчанию id выполняемого потока вытаскивается из части информации из пути URI запроса. Например для http://localhost/app/hotels/booking?hotelId=1 результатом id потока по умолчанию будет hotels/booking.
  • Переопределить createExecutionInputMap(HttpServletRequest) в случае, когда вам необходимо получить детальный контроль над извлечением входных параметров потока из HttpServletRequest. По умолчанию все параметры запроса относятся к входных параметрам потока.
  • Переопределить handleExecutionOutcome в случае, когда вам необходимо обработать конкретный outcome при выполнении потока в собственном методе. Поведение по умолчанию посылает редирект на URL завершенного потока для возобновления нового вычисления в потоке.
  • Переопределить handleException в случае, когда вам необходимо получить детальный контроль над не обработанными исключениями в потоке. Поведение по умолчанию попытается перезапустить поток, когда клиент попытается получить доступ к завершенному или с истекшему выполнению потока. Любое другое исключение будет переброшено по умолчанию в инфраструктуру Spring MVC ExceptionResolver.
11.4.1. Пример FlowHandler

Общий шаблон взаимодействия между Spring MVC и Web Flow заключается в перенаправлении по окончанию потока к @Controller‘у. FlowHandler‘ы позволяют осуществить это без соединения определения потока с конкретным URL контроллера. Пример перенаправления FlowHandler к контроллеру Spring MVC показано ниже:

Таким образом обработчику необходимо обработать только outcome в собственном методе и нет необходимости переопределять что-то ещё.При получении outcome bookingConfirmed будет совершен редирект для показа нового бронирования. Любой другой outcome будет переадресован на главную страницу hotels.

11.4.2 Внедрение собственного FlowHandler

Для установки собственного FlowHandler просто укажите его как бин. Имя бина должно совпадать с id обрабатываемого потока.

В этой конфигурации при доступе к ресурсу /hotels/booking будет запущен поток hotels/booking, использующий собственный BookingFlowHandler. Когда закончится поток booking, FlowHandler обработает результат выполнения потока (outcome) и передаст соответствующему контроллеру.

11.4.3. Перенаправления FlowHandler

FlowHandler обрабатывает FlowExecutionOutcome или FlowException, возвращающих String, для указания к какому ресурсу нужно выполнить перенаправление после обработки. Из предыдущего примера BookingFlowHandler перенаправляет к ресурсу с URI booking/show для outcome bookingConfirmed и к ресурсу с URI hotels/index для всех остальных outcomes.

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

Дополнительные поддерживаемые префиксы для перенаправления:

  • servletRelative: — перенаправление к ресурсам относительно текущему сервлету
  • contextRelative: — перенаправление к ресурсам, относительно к текущему базовому пути веб приложения
  • serverRelative: — перенаправление к ресурсам, относительно к корню сервера
  • http:// или https:// — перенаправление по точно указанному URI

Эти же самые префиксы используются в определении потока при использовании инструкции externalRedirect: в сочетании с view-state или end-state. Пример:

11.5. View Resolution

Web Flow 2 маппит выбранные идентификаторы представления к файлам, расположенным в той же рабочей директории, что и поток, если не указано другое. Для существующих приложений Spring MVC + Web Flow внешний ViewResolver скорее всего уже обрабатывает эти представления для вас. Поэтому для продолжения использования этого преобразователя и предотвратить изменение распознавания того, как ваши существующие представления потока упакованы, необходимо настроить Web Flow следующим образом:

MvcViewFactoryCreator — фабрика, предоставляющая возможность настройки того, как система представлений Spring MVC будет использоваться внутри Web Flow. Используйте её для настройки существующих ViewResolvers, а так же других сервисов, таких как MessageCodesResolver. Так же вы можете включить связывание данных используя родной BeanWrapper из Spring MVC указав флаг useSpringBinding в true. Это альтернатива к использованию OGNL или Unified EL для связывания данных в view-to-model. Смотрите документацию JavaDoc API для этих классов, чтобы получить дополнительную информацию.

11.6. Сигнализирование о событии от представления

Когда поток вошел в view-state, то он приостанавливается, перенаправляя пользователя к вычисленному URL, и ожидает события от пользователя для возобновления работы. Как правило, о событии сигнализируют нажатие на кнопку, ссылку или другие команды от пользовательского интерфейса. Как события обрабатываются на стороне сервера зависит от используемой технологии представления. Этот раздел покажет как инициировать события из представлений, на основе HTML, созданных с применением шаблонизаторов, таких как JSP, Velocity или Freemarker.

11.6.1. Использование именованной HTML кнопки для сигнализировании о событии

Пример ниже демонстрирует две кнопки на одной форме, которые по нажатию сигнализируют о событии proceed и cancel, соответственно.

Когда произошло нажатие на кнопку, Web Flow найдет параметр в запросе, начинающийся с _eventId_ и рассмотрит последующую подстроку как id события. В этом примере представленный _eventId_proceed становится proceed. Этот стиль должен рассматриваться, когда существует несколько различных событий, которые могут быть вызваны из одной формы.

11.6.2. Использование скрытых HTML параметров формы для сигнализировании о событии

В примере ниже показано форма, которая сигнализирует о событии proceed при ее подтверждении:

Здесь Web Flow обнаруживает специальный _eventId параметр и использует его значение как id события. Этот стиль может быть использован когда только одно событие может быть вызвано из этой формы.

11.6.3. Использование HTML ссылки для сигнализировании о событии

Пример ниже показывает как ссылка передает сигнал о событии cancel при ее активации:

Результат нажатия на ссылку будет передан обратно серверу в HTTP запросе. На стороне сервера обработчик потока декодирует событие в рамках его текущего состояния view-state. Как это будет происходить декодирование зависит от реализации представления. Напомним, что реализация представления Spring MVC просто ищет в запросе параметр с именем _eventId. Если параметр с именем _evenId не будет найден, то представление будет искать параметр начинающийся с _eventId_ и использует последующую подстроку как идентификатор события (event id). Если ни один из этих случаев не подошел, то никаких событий не будет запущено.

11.7. Встраивание потока на странице

По умолчанию, когда поток входит в состояние представления (view-state), он выполняет на стороне клиента перенаправления до рендера представления. Этот подход известен как POST-REDIRECT-GET. Этот подход предоставляет выгоду за счет отделения обработки формы для одного представления от рендера следующего представления. В результате кнопки браузера назад и обновить работают правильно, не вызывая каких-либо предупреждений браузера.

Обычно на стороне клиента перенаправление прозрачно с точки зрения пользователя. Тем не менее, бывают ситуации, когда POST-REDIRECT-GET может не принести какой-либо выгоды. Например, поток может быть встроен на странице и приводится в действие посредством Ajax запросов, обновляющих только область страницы, которая принадлежит к этому потоку. В таком случае не только нет необходимости использовать перенаправление на стороне клиента, но также это является не желательным поведением по отношению к сохранению остального содержимого страницы нетронутым.

Раздел Section 12.5, “Handling Ajax Requests” объясняет, как сделать частичное обновление во время Ajax запросов. В центре внимания этого раздела пояснение как нужно управлять перенаправлением в потоке при Ajax запросах. Для указания потоку о том, что необходимо включить режим выполнения «внутри страницы», нужно указать дополнительный параметр при запуске потока:

При запуске в режиме «вложенной страницы» поток не будет выполнять перенаправление в случае Ajax запросов. Параметр mode=embedded необходим только при запуске потока. Ваша единственная забота — использование Ajax запросов и обновление только того контента, которое необходимо для обновления части страницы, отображаемой потоком.

11.7.1. Embedded Mode против редиректа по умолчанию

По умолчанию Web Flow выполняет перенаправление на стороне клиента при каждом входе в view-state. Однако, если вы остаётесь в том же состоянии, например для transition без атрибута to, то во время Ajax запроса не произойдет перенаправления на стороне клиента. Это поведение должно быть хорошо знакомым для пользователей Spring Web Flow 2. Это подходит для потока верхнего уровня, который поддерживает кнопку браузера назад, в то же время используя Ajax и частичное обновление для использования в случаях, когда вы остаетесь в том же представлении, таком как форма валидации, показ результатов поиска и др.. Однако переходы в новый view-state всегда будет происходить с перенаправлением на стороне клиента. Это делает невозможным для встраивания потока на странице или внутри модального диалогового окна и выполнение более одно view-state, не вызывав обновления всей страницы. Следовательно, если ваш случай требует встраивания потока, вы можно запустить его в режиме «embedded«.

11.7.2. Примеры встроенных потоков

Если вы хотите увидеть примеры встроенного на странице и в модальном диалоге потока, то вам следует обратиться к проекту webflow-showcase. Вы можете развернуть исходный код локально, развернув его как Maven проект и импортировав его в Eclipse:

11.8. Сохранение выходных данных потока в MVC Flash Scope

Выходные данные потока могут быть автоматически сохранены в MVC flash scope, когда end-state выполняет внутренний редирект. Это особенно полезно при отображении страницы сводки (например результат заказа) в конце потока. Для обратной совместимости эта функция по умолчанию отключена, для того чтобы включить ее установите saveOutputToFlashScopeOnRedirect в true вашего FlowHandlerAdapter.

Следующий пример добавляет confirmationNumber в MVC flash scope до перенаправления на экран сводки.

 

Share Button
5
4477 Total Views 3 Views Today

Добавить комментарий