# 6. 执行动作

## 6.1.导言

本章向你展示了如何使用`action-state`元素来控制流中某个点上的动作的执行。它还将展示如何使用`decision-state`元素来做出流路由决策。最后，将讨论几个从流中可能的各个点调用操作的示例。

## 6.2.界定行动状态

当你希望调用某个操作时，使用`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
						  http://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 会检查结果，以查看是否与声明的向另一种状态的转换匹配。这意味着，如果配置了多个动作，它们将在有序的链中执行，直到其中一个返回一个结果事件，该结果事件与动作状态的状态转换相匹配，而其余的则被忽略。这是责任链模式的一种形式。

动作执行的结果通常是转换出此状态的标准。当前请求上下文中的附加信息也可以作为自定义转换标准的一部分进行测试，该标准允许根据上下文状态推理复杂的转换表达式。

还请注意，一个动作状态就像其他任何状态一样，可以有另外一个输入动作，这些动作从头到尾以列表的形式执行。

## 6.3.决定状态的定义

使用`decision-state`元素作为 Action-State 的替代选项，以使用方便的 if/else 语法做出路由决策。下面的示例显示了上面的`moreAnswersNeeded`状态，该状态现在实现为一个决策状态，而不是一个动作状态：

```
<decision-state id="moreAnswersNeeded">
	<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
			
```

## 6.4.行动成果事件映射

动作经常调用普通 Java 对象上的方法。当从动作状态和决策状态调用时，可以使用这些方法返回值来驱动状态转换。由于转换是由事件触发的，因此方法返回值必须首先映射到事件对象。下表描述了如何将常见的返回值类型映射到事件对象：



**表 6.1.动作方法将值返回到事件 ID 映射**

|Method return type|映射的事件标识符表达式|
|------------------|----------------------------------|
| java.lang.String |字符串的值|
|java.lang.Boolean |是（为真），否（为假）|
|  java.lang.Enum  |枚举名称|
|  any other type  |成功|

下面的示例 Action State 演示了这一点，它调用了一个返回布尔值的方法：

```
<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`基类进行扩展。当你的操作代码和 Spring Web 流 API 之间具有天然的耦合时，这些选项提供了更强的类型安全性。下面是这些方法的示例。

### 6.5.1.调用 POJO 动作

```
<evaluate expression="pojoAction.method(flowRequestContext)" />
			
```

```
public class PojoAction {
	public String method(RequestContext context) {
		...
	}
}
			
```

### 6.5.2.调用自定义操作实现

```
<evaluate expression="customAction" />
			
```

```
public class CustomAction implements Action {
	public Event execute(RequestContext context) {
		...
	}
}
			
```

### 6.5.3.调用多操作实现

```
<evaluate expression="multiAction.actionMethod1" />

			
```

```
public class CustomMultiAction extends MultiAction {
	public Event actionMethod1(RequestContext context) {
		...
	}

	public Event actionMethod2(RequestContext context) {
		...
	}

	...
}
			
```

## 6.6.动作例外

动作通常调用封装复杂业务逻辑的服务。这些服务可能会抛出操作代码应该处理的业务异常。

### 6.6.1.使用 POJO 操作处理业务异常

下面的示例调用一个操作，该操作捕获业务异常，将错误消息添加到上下文中，并返回结果事件标识符。结果被视为一个流事件，然后调用流可以对其进行响应。

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

### 6.6.2.使用多操作处理业务异常

下面的示例在功能上与上一个示例相同，但实现为一个多操作，而不是一个 POJO 操作。多操作要求其操作方法的签名`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();
	   }
}
}
			
```

### 6.6.3.使用异常处理程序元素

通常，建议在操作中捕获异常并返回驱动标准转换的结果事件，也可以向具有`bean`属性的任何状态类型添加`exception-handler`子元素，该属性引用类型`FlowExecutionExceptionHandler`的 Bean。这是一个高级选项，如果使用不正确，可能会使流执行处于无效状态。将内建`TransitionExecutingFlowExecutionExceptionHandler`作为一个正确实现的示例。

## 6.7.其他动作执行示例

### 6.7.1.启动

下面的示例展示了一个操作，该操作通过调用服务上的一个方法来创建一个新的 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
						  http://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>
			
```

### 6.7.2.进入

下面的示例显示了一个状态条目操作，该操作设置特殊的`fragments`变量，该变量使视图状态呈现其视图的部分片段：

```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
	<on-entry>
		<render fragments="hotelSearchForm" />
	</on-entry>
</view-state>
			
```

### 6.7.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>
			
```

### 6.7.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
						  http://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>
			
```

### 6.7.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>
			
```

### 6.7.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>
			
```

### 6.7.7.命名动作

下面的示例展示了如何在动作状态下执行一系列动作。每个动作的名称成为动作结果事件的限定符。

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

### 6.7.8.串流动作

有时，一个动作需要将自定义响应流回客户端。一个例子可能是处理打印事件时呈现 PDF 文档的流。这可以通过使内容的动作流然后在 ExternalContext 上记录“响应完成”状态来实现。ResponseComplete 标志告诉暂停视图状态不要呈现响应，因为另一个对象已经处理了它。

```
<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，然后将响应标记为已完成。

### 6.7.9.处理文件上传

另一个常见的任务是使用 Web 流结合 Spring MVC 的`MultipartResolver`处理多部分文件上传。一旦正确设置了解析器[如此处所述](http://static.springsource.org/spring/docs/2.5.x/reference/mvc.html#mvc-multipart)，并且将提交的 HTML 表单配置为`enctype="multipart/form-data"`，你就可以在转换操作中轻松处理文件上传。

|[[note]（images/note.png）|Note|
|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|下面的文件上载示例与使用 JSF 的 WebFlow 无关。有关如何使用 JSF 上载文件的详细信息，请参见[第 13.8 节，“使用 JSF 处理文件上传”](spring-faces.html#spring-faces-file-upload)。|    |

给定一种形式，如：

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

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;
	}
}
			
```

你可以使用以下示例中的转换操作来处理上载：

```
<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 作为正常形式绑定过程的一部分，以便在执行转换操作期间可以对其进行处理。