14. 执行操作
本章向您展示如何使用action-state元素控制在流中的某个点调用操作。 还展示了如何使用decision-state元素进行流路由决策。 最后,讨论了从流中各个可能的点调用操作的几个示例。
14.1. 定义操作状态
你可以使用 action-state 元素,当你希望调用一个动作并根据该动作的结果过渡到另一个状态时,如下所示:
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
以下示例展示了一个面试流程,该流程使用前面的 action-state 来确定是否需要更多答案以完成面试:
<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
https://www.springframework.org/schema/webflow/spring-webflow.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>
在每次调用操作后,action-state会检查结果,看是否与声明的转换到另一个状态相匹配。 这意味着,如果配置了多个操作,则会按照有序链依次调用它们,直到其中一个返回的结果事件与操作状态之外的状态转换相匹配为止,其余的操作将被忽略。 这是“责任链”(CoR)模式的一种形式。
动作调用的结果通常是从此状态转换出去的标准。 您还可以在当前 RequestContext 中测试其他信息,作为允许复杂转换表达式的自定义转换条件的一部分,这些表达式可以根据上下文状态进行推理。
请注意,action-state(以及任何其他状态)可以具有更多在进入时触发的操作,这些操作会按照从头到尾的顺序作为列表被调用。
14.2. 定义决策状态
您可以使用 decision-state 元素作为 action-state 元素的替代方案,通过便捷的 if-else 语法来进行路由决策。 以下示例展示了 moreAnswersNeeded 状态(来自上一节中的示例),现在实现为决策状态而不是动作状态:
<decision-state id="moreAnswersNeeded">
<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
14.3. 动作结果事件映射
Actions often invoke methods on plain Java objects. 当从action-state和decision-state元素调用时,这些方法的返回值可以用于驱动状态转换。 由于转换是由事件触发的,因此方法的返回值必须首先映射到Event对象。 下表描述了常见的返回值类型如何映射到Event对象:
| 方法返回类型 | 映射事件标识符表达式 |
|---|---|
|
The |
|
是的(对于 |
|
the |
任何其他类型 |
成功 |
以下示例调用了一个返回布尔值的方法:
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
14.4. 动作实现
虽然将操作代码编写为 POJO 逻辑是最常见的做法,但还有其他几种操作实现选项。 有时,您需要编写需要访问流上下文的操作代码。 您可以随时调用一个 POJO 并将 flowRequestContext 作为 EL 变量传递给它。 或者,您可以实现 Action 接口或从 MultiAction 基类进行扩展。 当您的操作代码与 Spring Web Flow API 之间存在自然耦合时,这些选项提供了更强的类型安全性。 以下部分展示了每种方法的示例。
14.4.1. 调用 POJO 操作
以下示例展示了如何调用 POJO 操作:
<evaluate expression="pojoAction.method(flowRequestContext)" />
public class PojoAction {
public String method(RequestContext context) {
...
}
}
14.5. 动作异常
Actions often invoke services that encapsulate complex business logic. 这些服务可以抛出业务异常,操作代码应该处理这些异常。
14.5.1. 使用 POJO Action 处理业务异常
以下示例调用了一个操作,该操作捕获业务异常,向上下文中添加错误消息,并返回结果事件标识符。 结果被视为一个流程事件,调用流程可以对该事件进行响应。
<evaluate expression="bookingAction.makeBooking(booking, flowRequestContext)" />
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";
}
}
}
14.5.2. 使用 <code>@ExceptionHandler</code> 处理业务异常MultiAction
以下示例在功能上等同于上一节中的示例,但它是作为MultiAction实现的,而不是POJO动作。 MultiAction要求其动作方法的签名为Event ${methodName}(RequestContext),提供了更强的类型安全性,而POJO动作则允许更多的自由。
<evaluate expression="bookingAction.makeBooking" />
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();
}
}
}
14.6. 其他操作示例
本章的其余部分展示了使用操作的其他方法。
14.6.1. on-start元素
以下示例显示了一个操作,该操作通过调用服务中的方法来创建一个新的 Booking 对象:
<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
https://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
</flow>
14.6.2. on-entry元素
以下示例展示了一个状态进入动作,它设置了特殊的fragments变量,该变量导致view-state渲染其视图的部分片段:
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchForm" />
</on-entry>
</view-state>
14.6.3. on-exit元素
以下示例展示了一个状态退出操作,该操作释放了正在编辑的记录上的锁:
<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>
14.6.4. on-end元素
以下示例显示了与上一节中的示例等效的对象锁定行为,但使用了流开始和结束操作:
<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
https://www.springframework.org/schema/webflow/spring-webflow.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>
14.6.5. on-render元素
以下示例展示了一个渲染操作,该操作在视图渲染之前加载要显示的酒店列表:
<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>
14.6.6. on-transition元素
以下示例展示了一个过渡动作,该动作将子流结果事件属性添加到集合中:
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" />
</transition>
</subfow-state>
14.6.7. 命名操作
以下示例展示了如何在 action-state 中执行一系列动作链。每个动作的名称将成为该动作结果事件的限定符。
<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>
在这个示例中,当 thingTwo 成功完成后,流程会过渡到 showResults。
14.6.8. 流式操作
有时,某个操作需要将自定义响应流式传输回客户端。 一个例子是处理打印事件时渲染 PDF 文档的流程。 这可以通过让操作流式传输内容并在 ExternalContext 上记录状态为 Response Complete 来实现。 responseComplete 标志告诉暂停的 view-state 不要渲染响应,因为另一个对象已经处理了它。 以下操作展示了这样的行为:
<view-state id="reviewItinerary">
<transition on="print">
<evaluate expression="printBoardingPassAction" />
</transition>
</view-state>
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 并将响应标记为完成。
14.6.9. 处理文件上传
另一个常见的任务是使用 Web Flow 来处理多部分文件上传,并结合 Spring MVC 的 MultipartResolver。 一旦解析器正确设置,如这里所描述的,并且提交的 HTML 表单配置了 enctype="multipart/form-data",就可以在转换动作中处理文件上传。
| 以下示例中显示的文件上传示例在将 Web Flow 与 JSF 一起使用时并不相关。 有关如何使用 JSF 上传文件的详细信息,请参见使用 JSF 处理文件上传。 |
考虑以下列表中的表单:
<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>
然后考虑用于处理上传的后端对象:
package org.springframework.webflow.samples.booking;
public class FileUploadHandler {
private transient MultipartFile file;
public void processFile() {
//Do something with the MultipartFile here
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
你可以通过使用过渡动作来处理上传,如下所示:
<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 bean,以便在执行转换操作期间可用。