论坛首页 Java版 Webwork

spring框架web流学习笔记

浏览 2231 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
最后更新时间:2005-07-13
第一步是配置,在配置文件中做4个声明:

[code:1]<webflow id="orderFlow" start-state="setupForm">
<!-- The initial state -->
<action-state id="setupForm">
<action bean="formAction"/>
<transition on="success" to="personalDetailsView"/> <!-- Continue the initial state -->
</action-state>

<!-- The initial or error state, depending on where we're coming from -->
<view-state id="personalDetailsView" view="enterPersonalDetails">
<transition on="submit" to="bindAndValidatePersonalDetails"/> <!-- To the bat mobile! -->
</view-state>

<!-- The submission state -->

<action-state id="bindAndValidatePersonalDetails">
<action bean="formAction" method="bindAndValidate"/>
<transition on="success" to="submissionSuccessful"/> <!-- To the success state -->
<transition on="error" to="personalDetailsView"/> <!-- To the error state -->
</action-state>

<!-- The success state -->
<end-state id="submissionSuccessful" view="orderComplete"/>
</webflow>[/code:1]

下面产生触发:
[code:1]<form name="orderForm" method="POST">
<fieldset>
<legend>Order form</legend>

<input type="hidden" name="_flowExecutionId" value="<c:out value="${flowExecutionId}"/>"/>
<input type="hidden" name="_eventId" value="submit"/>
<!-- other form fields here -->
<input type="submit" value="Go ahead and buy now!"/>
</fieldset>

</form>[/code:1]

定义formAction

[code:1]<bean name="formAction" class="org.springframework.web.flow.action.FormAction">
<property name="formObjectName"><value>order</value></property>
<property name="formObjectClass"><value>example.OrderForm</value></property>

<property name="validator">
<bean class="example.OrderFormValidator"/>
</property>
</bean>[/code:1]

Action类

[code:1]package example;

public class OrderForm {
private String firstName = null;
private String lastName = null;
private int age = 0;

public void setFirstName(String firstName) { this.firstName = firstName; }
public String getFirstName() { return this.firstName; }

public void setLastName(String lastName) { this.lastName = lastName; }
public String getLastName() { return this.lastName; }

public void setAge(int age) { this.age = age; }
public int getAge() { return this.age; }
}[/code:1]

录入校验类:

[code:1]package example;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import org.apache.commons.lang.StringUtils;

public class OrderFormValidator implements Validator {
public boolean supports(Class clazz) { return OrderForm.class.equals(clazz); }

public void validate(Object o, Errors errors) {
OrderForm orderForm = (OrderForm)o;

if (StringUtils.isBlank(orderForm.getFirstName())) {
errors.rejectValue("firstName", null, "First name must not be empty");
}
if (orderForm.getFirstName().length() > 30) {
errors.rejectValue("firstName", null, "First name should not be longer than 30 characters");
}
if (StringUtils.isBlank(orderForm.getLastName())) {
errors.rejectValue("lastName", null, "Last name must not be empty");
}
if (orderForm.getLastName().length() > 50) {
errors.rejectValue("lastName", null, "Last name should not be longer than 50 characters");
}
if (orderForm.getAge() < 18) {
errors.rejectValue("age", null, "Customers must be 18 years or older");
}
if (orderForm.getAge() > 120) {
errors.rejectValue("age", null, "We do not do business with the undead");
}
}
}[/code:1]

提交表单:

[code:1]<form name="personalDetailsForm" method="POST">
<fieldset>
<legend>Personal details</legend>
<input type="hidden" name="_flowExecutionId" value="<c:out value="${flowExecutionId}"/>"/>

<input type="hidden" name="_eventId" value="submit"/>
<spring:bind path="order.firstName">
<label>First name
<input type="text" name="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>

</label>
<c:if test="${status.error}">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</c:if>
<br/>

</spring:bind>
<spring:bind path="order.lastName">
<label>Last name
<input type="text" name="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>

</label>
<c:if test="${status.error}">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</c:if>
<br/>

</spring:bind>
<spring:bind path="order.age">
<label>Age
<input type="text" name="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>

</label>
<c:if test="${status.error}">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</c:if>
<br/>

</spring:bind>
<input type="submit" value="Go ahead and buy now!"/>
</fieldset>
</form>[/code:1]

太多了,下面给出英文的文档了

package example;

public class OrderForm {
private String firstName = null;
private String lastName = null;
private int age = 0;
private int quantity = 0;
private String size = null;
private double unitPrice = 20;

public void setFirstName(String firstName) { this.firstName = firstName; }
public String getFirstName() { return this.firstName; }

public void setLastName(String lastName) { this.lastName = lastName; }
public String getLastName() { return this.lastName; }

public void setAge(int age) { this.age = age; }
public int getAge() { return this.age; }

public void setQuantity(int quantity) { this.quantity = quantity; }
public int getQuantity() { return this.quantity; }

public void setSize(String size) { this.size = size; }
public String getSize() { return this.size; }

public double getTotal() { return unitPrice * quantity; }
}

Next we extend OrderFormValidator. We create two validation methods: one to validate "firstName", "lastName" and "age" and one to validate "quantity" and "size".

package example;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import org.apache.commons.lang.StringUtils;

public class OrderFormValidator implements Validator {
public boolean supports(Class clazz) { return clazz.equals(OrderForm.class); }

public void validate(Object o, Errors errors) {
validatePersonalDetails((OrderForm)o, errors);
validateOrderDetails((OrderForm)o, errorrs);
}

public void validatePersonalDetails(OrderForm orderForm, Errors errors) {
if (StringUtils.isBlank(orderForm.getFirstName())) {
errors.rejectValue("firstName", null, "First name must not be empty");
}
if (orderForm.getFirstName().length() > 30) {
errors.rejectValue("firstName", null, "First name should not be longer than 30 characters");
}
if (StringUtils.isBlank(orderForm.getLastName())) {
errors.rejectValue("lastName", null, "Last name must not be empty");
}
if (orderForm.getLastName().length() > 50) {
errors.rejectValue("lastName", null, "Last name should not be longer than 50 characters");
}
if (orderForm.getAge() < 18) {
errors.rejectValue("age", null, "Customers must be 18 years or older");
}
if (orderForm.getAge() > 110) {
errors.rejectValue("age", null, "We do not do business with the undead");
}
}

public void validateOrderDetails(OrderForm orderForm, Errors errors) {
if (!(orderForm.getSize().equals("S")
|| orderForm.getSize().equals("M")
|| orderForm.getSize().equals("L");
|| orderForm.getSize().equals("XL"))) {
errors.rejectValue("size", null, "Size must be S, M, L or XL");
}
if (orderForm.getQuantity() < 0) {
errors.rejectValue("quantity", null, "Quantity cannot be negative");
}
}
}


We have split the validation of OrderForm instances into two methods. This allows us to either validate these two group separately or validate OrderFrom instances entirely. Now lets make some changes to the page flow:

<webflow id="orderFlow" start-state="setupForm">
<!-- The initial state -->
<action-state id="setupForm">
<action bean="formAction"/>

<transition on="success" to="personalDetailsView"/> <!-- Continue the initial state -->
</action-state>

<!-- The initial or personal details error state, depending on where we're coming from -->
<view-state id="personalDetailsView" view="enterPersonalDetails">
<transition on="submit" to="bindAndValidatePersonalDetails"/> <!-- To the bat mobile! -->

</view-state>

<!-- The personal details submission state -->
<action-state id="bindAndValidatePersonalDetails">
<action bean="formAction" method="bindAndValidate">
<property name="validatorMethod" value="validatePersonalDetails"/> <!-- validate personal details properties -->

</action>
<transition on="success" to="orderDetailsView"/> <!-- To the order details view state -->
<transition on="error" to="personalDetailsView"/> <!-- To the personal details error state -->
</action-state>

<!-- The order details view or order details error state, depending on where we're coming from -->
<view-state id="orderDetailsView" view="enterOrderDetails">
<transition on="buy" to="bindAndValidateOrderDetails"/> <!-- To the order details submission state -->
</view-state>

<!-- The order details submission state -->

<action-state id="bindAndValidateOrderDetails">
<action bean="formAction" method="bindAndValidate">
<property name="validatorMethod" value="validateOrderDetails"/> <!-- validate order details properties -->
</action>
<transition on="success" to="submissionSuccess"/> <!-- To the success state -->

<transition on="error" to="orderDetailsView"/> <!-- To the order details error state -->
</action-state>

<!-- The success state -->
<end-state id="submissionSuccessful" view="orderComplete"/>
</webflow>


Adding a page to the page flow has added a number of extra states yet the page flow remains very readable. There are now two submission states, personal detail submission state and order details submission state, that call the relevant method on the validator.

We need to change the scope of the FormAction instance since we want to keep the instances of the form object available across the two forms:

<bean name="formAction" class="org.springframework.web.flow.action.FormAction">
<property name="formObjectScope"><value>flow</value></property>

<property name="formObjectName"><value>order</value></property>
<property name="formObjectClass"><value>example.OrderForm</value></property>
<property name="validator">

<bean class="example.OrderFormValidator"/>
</property>
</bean>

The only thing missing now is the HTML form for the second page:

<form name="orderDetailsForm" method="POST">
<fieldset>
<legend>Order details</legend>
<input type="hidden" name="_flowExecutionId" value="<c:out value="${flowExecutionId}"/>"/>

<input type="hidden" name="_eventId" value="buy"/>
<spring:bind path="order.quantity">
<label>Quantity
<input type="text" name="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>

</label>
<c:if test="${status.error}">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</c:if>
<br/>

</spring:bind>
<spring:bind path="order.size">
<!-- convert this input field to a list -->
<label>Size
<select name="<c:out value="${status.expression}"/>">

<c:forTokens var="size" items="S,M,L,XL" delims=",">
<option value="<c:out value=""/>" <c:if test="true">selected</c:if>><c:out value=""/></option>
</c:forTokens>
</select>

</label>
<c:if test="${status.error}">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</c:if>
<br/>

</spring:bind>
<input type="submit" value="Go ahead and buy now!"/>
</fieldset>
</form>

In just a few moments we have added a page to the page flow. The page flow mechanism provided by Web Flow does all the hard work for us. There are just two things left to complete the functionality of this example. First we want to navigate from the order details form screen to the personal details form screen. Secondly, if the user sets the quantity to "0" in the order details form we want to return to that page after submission and display a message saying that the order has been cancelled. There are still more ways to improve these screens, but we'll stick to these two enhancements in this article.

Adding a navigation from the the order details form screen to the personal details form screen is trivial. As you may have understood by now two thing need to be added to this end: a transition from the "orderDetailsView" state to the "personalDetailsView" state and an anchor in the order details view. Lets start by changing the "orderDetailsView" state:

...

<!-- The order details view or order details error state, depending on where we're coming from -->
<view-state id="orderDetailsView" view="enterOrderDetails">
<transition on="buy" to="bindAndValidateOrderDetails"/> <!-- To the order details submission state -->

<transition on="toPersonalDetails" to="personalDetailsView"/> <!-- To the personal details view -->
</view-state>

...

Next we can add this anchor to the order details view:

<a href="?_flowExecutionId=<c:out value="${flowExecutionId}"/>&_eventId=toPersonalDetails">

Note we're using an anchor which is more appropriate than a form. The last thing we need to add is a conditional transition in case the quantity in the order details form is "0". We only want a cancelled order state when the quantity was not "0" before. If the quantity changes afterwards the order state should no longer be cancelled. The best place to implement this logic is in the setQuantity method of the OrderForm class:

...

private boolean cancelled = false;

/**
* <p>Sets the "cancelled" property to true if quantity is "0" and was not "0" before,
* otherwise "cancelled" will be set to false.
*
* @param quantity the quantity
*/
public void setQuantity(int quantity) {
if (quantity != this.quantity) {
this.cancelled = (quantity == 0);
this.quantity = quantity;
}
}

public boolean getCancelled() { return this.cancelled; }

...


Next we need to test the value of the "cancelled" property of OrderForm instances to determine where the page flow should go to if the order detail form is submitted:

...

<!-- The order details submission state -->
<action-state id="bindAndValidateOrderDetails">
<action bean="formAction" method="bindAndValidate">

<property name="validatorMethod" value="validateOrderDetails"/> <!-- validate order details properties -->
</action>
<transition on="success" to="testQuantity"/> <!-- To the success state -->
<transition on="error" to="orderDetailsView"/> <!-- To the order details error state -->

</action-state>

<decision-state id="testQuantity">
<if test="${flowScope.order.cancelled}" then="orderDetailsView" else="submissionSuccess"/>
</decision-state>

...


Adding a decision state allows us to the test values of OrderForm instances. You have to use the name of the object in the flow scope which is the only drawback of this approach. Notice the "testQuantity" state sits in between the "bindAndValidateOrderDetails" state and the "orderDetailsView" and "submissionSuccess" states. The decision state clearly offers a lot of power in page flow design. Changing the order view to display a message in case the order is cancelled is the very last step to complete this example:

<spring:bind path="order.cancelled">
<c:if test="${status.value}">
<span class="orderCancelled">You order has been cancelled.</span>

</c:if>
</spring:bind>

While reuse is not part of the example in this article it's a powerful feature that's also offered by Spring Web Flow. At any state transition in a page flow a new page flow can be started. The current page flow will be paused and continues when the sub page flow has finished. The flow launcher example shipped with Spring Web Flow illustrates this functionality in more detail. This feature allows you to take your page flow designs to a new level.

Conclusion
You will find Spring Web Flow a very powerful page flow management tool that circumvents limitations of classic MVC frameworks. Struts and JSF integration are shipped with Spring Web Flow making its integration in your existing environment straightforward. A number of sample applications - including a Struts and JSF integration examples - are a good starting point to get your hands dirty.

From a strategic point of view the close integration with Spring's dependency injection makes adapting Spring Web Flow even more interesting. Both Spring and Spring Web Flow provide more value for money and more functionality out of the box than any other application framework.
   
最后更新时间:2005-07-15
我采用的是springMVC,在做webflow时遇到一个问题,

在jsp中有一个
[code:1]<INPUT type="hidden" name="_flowExecutionId" value="<%=request.getAttribute("flowExecutionId") %>">[/code:1]

如果这个隐藏域不存在,那么后台捕获的将是 null,且程序能够判断出这是一个起始页面并给出正确的流转。
但这个隐藏域如果事先就存在了(例子中就是事先存在的),那么后台捕获的将是 "null",然后似乎报一个 "null" 不存在的错误。

这个问题你遇到过么?
   
0 请登录后投票
论坛首页 Java版 Webwork

跳转论坛:
JavaEye推荐