Spring Web Flow — выполнение действий (executing actions)
6. Выполнение действий (execution actions)
6.1. Введение
Эта глава покажет вам как использовать элемент action-state для управления выполнения действия в определенной точке внутри потока. Так же будет показано использование элемента decision-state, позволяющего создавать маршрутизация потока на основе принятого решения. В конце будут показаны несколько примеров внедрения действий из различных возможных точек внутри потока.
6.2. Определение состояний действия (action states)
Используйте элемент action-state, когда вы желаете внедрить действие, на основании результата которого будет выполнен переход в другое состояние:
1 2 3 4 5 |
<action-state id="moreAnswersNeeded"> <evaluate expression="interview.moreAnswersNeeded()" /> <transition on="yes" to="answerQuestions" /> <transition on="no" to="finish" /> </action-state> |
Полный пример ниже иллюстрирует поток interview, который использует action-state из примера выше для определения нужно ли больше вопросов для завершения интервью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> <on-start> <evaluate expression="interviewFactory.createInterview()" result="flowScope.interview" /> </on-start> <view-state id="answerQuestions" model="questionSet"> <on-entry> <evaluate expression="interview.getNextQuestionSet()" result="viewScope.questionSet" /> </on-entry> <transition on="submitAnswers" to="moreAnswersNeeded"> <evaluate expression="interview.recordAnswers(questionSet)" /> </transition> </view-state> <action-state id="moreAnswersNeeded"> <evaluate expression="interview.moreAnswersNeeded()" /> <transition on="yes" to="answerQuestions" /> <transition on="no" to="finish" /> </action-state> <end-state id="finish" /> </flow> |
6.3. Определение состояний решения (decision states)
Используйте элемент decision-state как альтернативу элементу action-state для маршрутизации выбора, используя удобный синтаксис if/else. Пример ниже демонстрирует состояние moreAnswersNeeded из примера выше, которое теперь реализуется через decision-state вместо action-state:
1 2 3 |
<decision-state id="moreAnswersNeeded"> <if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" /> </decision-state> |
6.4. Маппинг результата действия
Действия часто вызывают методы для обычных Java объектов. Когда они вызываются из action-state или decision-state, возвращаемый результат может быть использован для управления переходами состояния. Поскольку переход был запущен событием, то необходимо, чтобы возвращаемое методом значение сначала было преобразовано в Event объект. Следующая таблица поясняет как общие типы возвращаемых значений преобразовывают к Event объектам:
Таблица 6.1. Преобразование возвращаемого значения метода Action к event id
Возвращаемое значение метода | Преобразованный Event идентификатор выражения |
---|---|
java.lang.String | the String value |
java.lang.Boolean | yes (for true), no (for false) |
java.lang.Enum | the Enum name |
any other type | success |
Это проиллюстрировано в следующем примере с использованием action-state, который вызывает метод, возвращающий значение типа boolean:
1 2 3 4 5 |
<action-state id="moreAnswersNeeded"> <evaluate expression="interview.moreAnswersNeeded()" /> <transition on="yes" to="answerQuestions" /> <transition on="no" to="finish" /> </action-state> |
6.5. Реализация действий
В то время, как написание кода действий используя логику POJO, является наиболее распространенным, есть несколько других вариантов реализации действий. Иногда нужно написать код действия, которому будет необходим доступ к контексту потока. Вы всегда можете вызвать POJO метод и передать ему значение flowRequestContext в качестве переменной EL. Кроме того, вы можете реализовать интерфейс Action или расширить класс от базового класса MultiAction. Эти варианты обеспечивают полную безопасность типов, когда имеется естественная связь между вашим кодом и API Spring Web Flow. Примеры каждого из этих подходов приведены ниже.
6.5.1. Вызов действий POJO
1 |
<evaluate expression="pojoAction.method(flowRequestContext)" /> |
1 2 3 4 5 |
public class PojoAction { public String method(RequestContext context) { ... } } |
6.5.2. Вызов пользовательской реализации Action
1 |
<evaluate expression="customAction" /> |
1 2 3 4 5 |
public class CustomAction implements Action { public Event execute(RequestContext context) { ... } } |
6.5.3. Вызов реализации MultiAction
1 |
<evaluate expression="multiAction.actionMethod1" /> |
1 2 3 4 5 6 7 8 9 10 11 |
public class CustomMultiAction extends MultiAction { public Event actionMethod1(RequestContext context) { ... } public Event actionMethod2(RequestContext context) { ... } ... } |
6.6. Исключения в действиях
Действия часто инкапсулируют сервис службы и инкапсулируют общую бизнес логику. Эти сервисные службы могут выбрасывать исключения, которые должны быть обработаны кодом действий.
6.6.1 Перехват служебных исключений в действиями POJO
В следующем примере вызванное действие перехватывает служебное исключение, добавляет сообщение об ошибке в контекст и возвращает идентификатор события:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class BookingAction { public String makeBooking(Booking booking, RequestContext context) { try { BookingConfirmation confirmation = bookingService.make(booking); context.getFlowScope().put("confirmation", confirmation); return "success"; } catch (RoomNotAvailableException e) { context.addMessage(new MessageBuilder().error(). .defaultText("No room is available at this hotel").build()); return "error"; } } } |
6.6.2. Перехват служебных исключений с MultiAction
Этот пример эквивалентен предыдущему, но выполняется с помощью расширения от MultiAction вместо обычного POJO действия. MultiAction требует, чтобы методы действия имели сигнатуру Event — ${methodName}(RequestContext), что позволит обеспечить безопасность типов, в то время как действия POJO позволяют большую свободу.
1 |
<evaluate expression="bookingAction.makeBooking" /> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class BookingAction extends MultiAction { public Event makeBooking(RequestContext context) { try { Booking booking = (Booking) context.getFlowScope().get("booking"); BookingConfirmation confirmation = bookingService.make(booking); context.getFlowScope().put("confirmation", confirmation); return success(); } catch (RoomNotAvailableException e) { context.getMessageContext().addMessage(new MessageBuilder().error(). .defaultText("No room is available at this hotel").build()); return error(); } } } |
6.6.3. Использование элемента exception-handler
В целом, для перехвата исключений в действиях и возвращения результата события, который управлять стандартными переходами, рекомендуется добавлять подэлемент exception-handler для любого состояния с указанием бина в качестве атрибута, который ссылается на бин типа FlowExecutionExceptionHandler. Это дополнительная возможность, которая при неправильном использовании, может привести выполнение потока в недопустимое состояние. Посмотрите встроенную реализацию TransitionExecutingFlowExecutionExceptionHandler в качестве примера корректной реализации.
6.7. Другие примеры выполнения действий
6.7.1. on-start
В примере показано действие, которое создает новый объект Booking вызовом сервисного метода:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> <input name="hotelId" /> <on-start> <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)" result="flowScope.booking" /> </on-start> </flow> |
6.7.2. on-entry
Пример показывает действие на входе в состояние, которое устанавливает специальную переменную fragments, которая позволит view-state частично обновлять фрагмент его представления:
1 2 3 4 5 |
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true"> <on-entry> <render fragments="hotelSearchForm" /> </on-entry> </view-state> |
6.7.3. on-exit
Следующий пример показывает действие при выходе из состояния, которое реализует блокирование в момент выполнения редактирования:
1 2 3 4 5 6 7 8 9 10 11 12 |
<view-state id="editOrder"> <on-entry> <evaluate expression="orderService.selectForUpdate(orderId, currentUser)" result="viewScope.order" /> </on-entry> <transition on="save" to="finish"> <evaluate expression="orderService.update(order, currentUser)" /> </transition> <on-exit> <evaluate expression="orderService.releaseLock(order, currentUser)" /> </on-exit> </view-state> |
6.7.4. on-end
Здесь показано эквивалентное поведение по блокировке объекта, используя действия при старте и выходе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> <input name="orderId" /> <on-start> <evaluate expression="orderService.selectForUpdate(orderId, currentUser)" result="flowScope.order" /> </on-start> <view-state id="editOrder"> <transition on="save" to="finish"> <evaluate expression="orderService.update(order, currentUser)" /> </transition> </view-state> <on-end> <evaluate expression="orderService.releaseLock(order, currentUser)" /> </on-end> </flow> |
6.7.5. on-render
В данном примере показывается действие on-render, которое загружает список отелей для отображения до того как представление будет отрисовано:
1 2 3 4 5 6 7 8 9 |
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="select" to="reviewHotel"> <set name="flowScope.hotel" value="hotels.selectedRow" /> </transition> </view-state> |
6.7.6. on-transition
Ниже показано действие при переходе, в котором добавляется атрибут результата события подпотока в коллекцию:
1 2 3 4 5 |
<subflow-state id="addGuest" subflow="createGuest"> <transition on="guestCreated" to="reviewBooking"> <evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" /> </transition> </subfow-state> |
6.7.7. Названные действия
Следующий пример демонстрирует как выполнить цепочку действий в action-state. Имя каждого действия становится квалификатором для результата события действия.
1 2 3 4 5 6 7 8 9 |
<action-state id="doTwoThings"> <evaluate expression="service.thingOne()"> <attribute name="name" value="thingOne" /> </evaluate> <evaluate expression="service.thingTwo()"> <attribute name="name" value="thingTwo" /> </evaluate> <transition on="thingTwo.success" to="showResults" /> </action-state> |
В этом примере поток выполнит переход к showResults, когда thingTwo успешно завершится.
6.7.8. Поточные действия
Иногда действие должно выдавать поток (stream) в обратном ответе клиенту. В качестве примера можно привести поток, создающий PDF документ при вызове события печати. Это может быть достигнуто с помощью действия, преобразующего контент в поток и записывающего статус «Response Complete» в ExternalContext. Флаг responseComplete говорит приостановить обновление ответа от view-state, потому что этим займется другой объект.
1 2 3 4 5 |
<view-state id="reviewItinerary"> <transition on="print"> <evaluate expression="printBoardingPassAction" /> </transition> </view-state> |
1 2 3 4 5 6 7 8 |
public class PrintBoardingPassAction extends AbstractAction { public Event doExecute(RequestContext context) { // stream PDF content here... // - Access HttpServletResponse by calling context.getExternalContext().getNativeResponse(); // - Mark response complete by calling context.getExternalContext().recordResponseComplete(); return success(); } } |
В этом примере, когда будет вызвано событие печати, поток вызовет printBoardingPassAction. Действие будет создавать PDF и затем отметит ответ как выполненный.
6.7.9. Обработка загрузки файлов
Другой частой задачей при использовании Web Flow является обработка многокомпонентной загрузки файлов в комбинации с Spring MVC MultipartResolver. Один раз правильно зарегистрировав обработчик (см. здесь) и сконфигурировав сабмит HTML формы с enctype=»multipart/form-data», вы можете легко обрабатывать загрузку файлов в действиях при переходах.
Обратите внимание, что пример выше не подходит при использовании Spring Web Flow и JSF. Как это сделать с JSF смотрите в Section 13.10, “Handling File Uploads with JSF”.
Создав такую форму:
1 2 3 4 |
<form:form modelAttribute="fileUploadHandler" enctype="multipart/form-data"> Select file: <input type="file" name="file"/> <input type="submit" name="_eventId_upload" value="Upload" /> </form:form> |
и объект в java коде для обработки загрузки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package org.springframework.webflow.samples.booking; import org.springframework.web.multipart.MultipartFile; public class FileUploadHandler { private transient MultipartFile file; public void processFile() { //Do something with the MultipartFile here } public void setFile(MultipartFile file) { this.file = file; } } |
вы можете выполнять загрузку файлов, используя действия при переходе, как это показано в следующем примере:
1 2 3 4 5 6 7 |
<view-state id="uploadFile" model="uploadFileHandler"> <var name="fileUploadHandler" class="org.springframework.webflow.samples.booking.FileUploadHandler" /> <transition on="upload" to="finish" > <evaluate expression="fileUploadHandler.processFile()"/> </transition> <transition on="cancel" to="finish" bind="false"/> </view-state> |
MultipartFile будет связан с бином FileUploadHandler как часть обычного процесса связывания формы, так что он будет доступен при процессе выполнения действий при переходе.
0