Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 42 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
42
Dung lượng
419,69 KB
Nội dung
So with knowledge of these basic building blocks in place, the challenge of implementing a flow definition becomes: 1. defining the states of the flow 2. defining the possible transitions between states and the event-driven criteria for those state transitions. Your First Flow In this section you will implement the example Purchase Product use case using Spring Web Flow. Chapter 12 will cover many of the decisions that the author has made and reevaluate some of those decisions regarding how this example works within Spring MVC. This section will not cover Spring MVC itself, as that is sufficiently covered elsewhere in this book. For now, let’s assume you have a working Spring MVC project that can be built and deployed onto a servlet container. Installing Spring Web Flow Instructions for downloading and installing Spring Web Flow can be found at http:// opensource2.atlassian.com/confluence/spring/display/WEBFLOW/Home. Proposed Flow Directory Structure From our experience it is best to partition your Spring Web Flow configuration information into file fragments that are responsible for their own concerns. Figure 11-5 is an example of a prudent directory structure for managing Spring Web Flow configuration artifacts. Figure 11-5. Suggested directory layout your-domain-servlet.xml flows.xml Standard Spring MVC-servlet.xml Infrastructure common across *all* Spring Web Flows WEB-INF a-flow.xml Definition of first web flow x-flow.xml Definition of xth web flow a-context.xml Bean Factory for first web flow x-context.xml BeanFactory for xth web flow WEB-INF/flows CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 319 584X_Ch11_FINAL 1/30/06 1:05 PM Page 319 The Purchase Product Flow Definition A flow definition can be engineered in a number of ways. Many users use XML to define their flows, as XML is a human readable and highly toolable format. However, you may also define flows in Java (by extending AbstractFlowBuilder) or with your own custom format by imple- menting a custom FlowBuilder. For the Purchase Product example web flow, you will use XML. Recall the graphical depic- tion of the flow definition in Figure 11-4. Implementing the First Step:View States The first step of this flow is to enter the purchase information, which requires the user to par- ticipate in the flow by providing the following bits of information: • The price at which the product should be sold • The quantity of products that are to be sold for this order Since this is the first step of the flow, it is designated as the start state. Since it is a step where the user is involved, it is a view state. A view state will select a view to render to allow the user to participate in the flow. See Listing 11-1. Listing 11-1. /WEB-INF/flows/purchase-flow.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE flow PUBLIC "-//SPRING//DTD WEBFLOW 1.0//EN" "http://www.springframework.org/dtd/spring-webflow-1.0.dtd"> <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> … </view-state> </flow> The preceding instruction means, “When an execution of this flow starts, enter the enterPurchaseInformation state. Then select the purchaseForm view for display to the user, and pause the flow execution until a user event is signaled.” Transitions As it stands, the preceding view-state definition is incomplete. Recall that all transitionable state types, which include the view state, must define at least one transition that leads to another state. Also recall that a transition is triggered by the occurrence of an event. A view-state event is triggered by the user to communicate what action the user took. For example, the user may press the “submit” or “cancel” button. So for a view state, the set of transitions define the sup- ported user events you wish to respond to for that state and how you wish to respond to them, as defined in Listing 11-2. CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW320 584X_Ch11_FINAL 1/30/06 1:05 PM Page 320 Listing 11-2. /WEB-INF/flows/purchase-flow.xml Containing Transitions <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <transition on="submit" to="requiresShipping"> <transition on="cancel" to="cancel"/> </view-state> </flow> The preceding transition instructions can be read, “On the occurrence of the submit event, transition to the requiresShipping state; on the occurrence of the cancel event, transi- tion to the cancel state.” Actions At this point you have defined a simple view state that will display a form and respond to “submit” and “cancel” events. You have yet to define the target states of the above transitions, which is the next logical step. Before continuing, however, consider some requirements typical of most form views. Forms usually need to be prepared before their display; that is, it is often the case that view prerender logic needs to be executed. This logic might load the “backing form object” that will be edited in the form, or it might load a collection of objects from the database for display in a drop-down menu or select box. Similarly, when a form is submitted, there is typically submit or postback logic that needs to execute. This logic is usually concerned with data binding (the process of copying form input parameters into properties of the “backing form object”) and data validation (the process of validating the new state of the form object). In Spring Web Flow, you invoke arbitrary command logic such as prerender and postback logic by executing an action that implements the core org.springframework.webflow.Action interface, as shown in Listing 11-3. Listing 11-3. org.springframework.webflow.Action public interface Action { Event execute(RequestContext context); } The interface is simple, consisting of a single method. An Action is expected to execute arbitrary logic when invoked in the context of a request. Once execution has completed, a result event (or outcome) is returned which the calling flow may respond to. An Action can do whatever you want it to do, and we’ll cover a number of out-of-the-box implementations in this book. What is important to understand now is the Action is the core construct for executing application code from a flow, and there are many opportunities to execute Actions within the life cycle of a flow. Table 11-4 provides the available execution points within a flow life cycle. CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 321 584X_Ch11_FINAL 1/30/06 1:05 PM Page 321 Table 11-4. Action Execution Points Within the Flow Life Cycle Point Description On flow start Execute one or more “start actions” when a flow starts. On flow end Execute one or more “end actions” when a flow ends. On state enter Execute one or more “entry actions” when a state is entered. On state exit Execute one or more “exit actions” when a state is exited. Before transition Execute one or more “transition actions” before executing a transition. In this case, you are interested in executing view prerender logic when the enterPurchaseInformation state is entered. Then, on execution of the submit transition you are interested in executing data binding and validation postback logic. See Listing 11-4. Listing 11-4. /WEB-INF/flows/purchase-flow.xml Containing Entry Actions <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <entry-actions> <action bean="formAction" method="setupForm"/> </entry-actions> <transition on="submit" to="requiresShipping"> <action bean="formAction" method="bindAndValidate"/> </transition> <transition on="cancel" to="cancel"/> </view-state> </flow> The preceding instruction now reads, “When an execution of this flow starts, enter the enterPurchaseInformation state. When the state is entered, first execute the setupForm() method on the formAction. Then select the purchaseForm view for display to the user, and pause the flow execution until a user event is signaled.” The submit transition instruction now reads, “On the occurrence of the submit event, transition to the requiresShipping state if the bindAndValidate() method on the formAction executes successfully.” This gives you behavior typical of a form view state, executing prerender logic as part of a state entry action, and postback logic as part of a specific transition action. If the transition action returns a result other than success, the transition will not be allowed, and the state will be reentered. This allows us to respond to data binding and validation errors correctly by redisplaying the view so the user can review the errors and revise his edits. Action Bean Definitions At this point you have referenced an Action bean from the flow definition with the formAction identifier (<action bean="formAction" method="setupForm"/>). CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW322 584X_Ch11_FINAL 1/30/06 1:05 PM Page 322 However, you have not defined the mapping between that identifier and a specific Action implementation. This is where the existing Spring infrastructure comes in, as Spring Web Flow uses Spring to drive configuration of flow artifacts such as Action. Refer to Listing 11-5. Listing 11-5. /WEB-INF/flows/purchase-flow.xml Importing Spring Beans <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <entry-actions> <action bean="formAction" method="setupForm"/> </entry-actions> <transition on="submit" to="requiresShipping"> <action bean="formAction" method="bindAndValidate"/> </transition> <transition on="cancel" to="cancel"/> </view-state> <import resource="purchase-flow-context.xml"/> </flow> Listing 11-6 is the contents of /WEB-INF/flow/purchase-flow-context.xml. Listing 11-6. /WEB-INF/flow/purchase-flow-context.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="formAction" class="org.springframework.webflow.action.FormAction"> <property name="formObjectName" value="purchase"/> <property name="formObjectClass" value="purchase.domain.Purchase"/> <property name="formObjectScope" value="FLOW"/> <property name="validator"> <bean class="purchase.domain.PurchaseValidator"/> </property> </bean> </beans> By using the import element, you can pull in any number of files containing bean defini- tions that define artifacts local to the flow definition. These imported bean definitions also have full access to the beans defined in the parent WebApplicationContext, typically the DispatcherServlet context. In this case formAction corresponds to a singleton instance of org.springframework. webflow.action.FormAction. This action is a MultiAction implementation that provides a number of action methods related to form processing, including setupForm for executing form prerender logic and bindAndValidate for executing form postback logic. CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 323 584X_Ch11_FINAL 1/30/06 1:05 PM Page 323 When invoked, setupForm will create an instance of the purchase.domain.Purchase form object class and place it in flow scope under the name purchase. This automatically exposes the Purchase object to the views by that name, which will allow correct prepopulation fields based on default values. When bindAndValidate is invoked it will bind incoming request parameters to the existing purchase bean managed in flow scope. After successful data binding, the configured Validator will then validate the new state of the bean. ■Note FormAction is a very rich object and will be investigated further in Chapter 12. Testing the Flow Execution At this point you nearly have a syntactically correct flow definition whose execution can be unit tested outside of the container. By filling in temporary state “placeholders” for the unde- fined states, you’ll correct the remaining syntax errors. Refer to Listing 11-7. Listing 11-7. /WEB-INF/flows/purchase-flow.xml Adding End State placeholders <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <entry-actions> <action bean="formAction" method="setupForm"/> </entry-actions> <transition on="submit" to="requiresShipping"> <action bean="formAction" method="bindAndValidate"/> </transition> <transition on="cancel" to="cancel"/> </view-state> <end-state id="requiresShipping"/> <end-state id="cancel"/> <import resource="purchase-flow-context.xml"/> </flow> With the requiresShipping and cancel placeholder, when either the submit or cancel transi- tions are executed, the flow terminates. Once a test verifies that the enterPurchaseInformation controller logic executes successfully, you’ll properly implement the requiresShipping state, as well as the remaining states of the flow. Extending AbstractFlowExecutionTests How do you test the execution of the flow defined so far? Spring Web Flow ships support classes within the org.springframework.webflow.test package. This support includes conven- ient base classes for implementing flow execution tests, as well as mock implementations of core Web Flow constructs such as the RequestContext to support unit testing flow artifacts such as Actions in isolation. CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW324 584X_Ch11_FINAL 1/30/06 1:05 PM Page 324 In this case, execution of the preceding purchase flow needs testing. Specifically, the fol- lowing can be asserted: • When the flow starts, it transitions to the correct start state: enterPurchaseInformation. • After the enterPurchaseInformation state has entered: • The correct View is selected (purchaseForm). • The model data the View needs is provisioned correctly (an instance of a purchase bean is present). •On the occurrence of the cancel event, the flow execution ends. •On the occurrence of the submit event data binding and validation logic executes correctly. This is accomplished by writing a test that extends AbstractFlowexecutionTests. Refer to Listing 11-8. Listing 11-8. Test Class to Test the Example Flow public class PurchaseFlowExecutionTests extends AbstractXmlFlowExecutionTests { @Override // the location of the flow definition in the file system protected Resource getFlowLocation() { File flowDir = new File("src/webapp/WEB-INF"); return new FileSystemResource(new File(flowDir, "purchaseflow.xml")); } @Override // the location of the flow definition in the file system protected Resource getFlowLocation() { File flowDir = new File("src/webapp/WEB-INF"); return new FileSystemResource(new File(flowDir, "purchase-flow.xml")); } // test that the flow execution starts as expected public void testStartFlow() { ViewSelection selectedView = startFlow(); assertCurrentStateEquals("enterPurchaseInformation"); assertModelAttributeNotNull("purchase", selectedView); assertViewNameEquals("purchaseForm", selectedView); } // test a successful submit, including data binding public void testSubmitPurchaseInformation() { testStartFlow(); Map parameters = new HashMap(2); parameters.put("price", "25"); parameters.put("quantity", "4"); CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 325 584X_Ch11_FINAL 1/30/06 1:05 PM Page 325 ViewSelection selectedView = signalEvent("submit", parameters); Purchase purchase = (Purchase)selectedView.getModel().get("purchase"); assertEquals("Wrong price" new MonetaryAmount("25"), purchase.getAmount()); assertEquals("Wrong quantity", 4, purchase.getQuantity()); assertFlowExecutionEnded(); } } The preceding test ensures that the controller logic implemented thus far within the flow definition works as expected. The test can also serve as a convenient way to test the execution of the use case from the web tier down. As additional states are added to the flow, you simply add additional test methods that signal events that drive transitions to those states and verify that the respective state behavior executes correctly. Decision States Recall that the next step in this sample flow is to optionally allow the user to enter product shipping information. In other words, there exists some condition that determines whether or not shipping information is required for a given flow execution. The decision state (see Listing 11-9) is designed to handle this type of situation, where a condition needs to be evaluated to drive a state transition. A decision state is a simple, indem- potent routing state. Listing 11-9. /WEB-INF/flows/purchase-flow.xml Containing a Decision State <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <entry-actions> <action bean="formAction" method="setupForm"/> </entry-actions> <transition on="submit" to="requiresShipping"> <action bean="formAction" method="bindAndValidate"/> </transition> <transition on="cancel" to="cancel"/> </view-state> <decision-state id="requiresShipping"> <if test="${flowScope.purchase.shipping}" then="enterShippingDetails" ➥ else="placeOrder"/> </decision-state> <view-state id="enterShippingDetails" view="shippingForm"> <transition on="submit" to="placeOrder"> <action bean="sellItemAction" method="bindAndValidate"/> </transition> </view-state> <import resource="purchase-flow-context.xml"/> </flow> CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW326 584X_Ch11_FINAL 1/30/06 1:05 PM Page 326 As you can see, if the shipping property of the purchase bean in flow scope evaluates to true, the flow will transition to the enterShippingDetails state; otherwise, the flow will transi- tion to the placeOrder state. In this scenario the decision-state evaluation criteria is an expression defined within the flow definition. Had the decision criteria been more complex, it could have been made in Java application code. You’ll see how to do this in Chapter 12. ■Note You’ll learn how to invoke methods on business objects to drive decision-state decisions in Chapter 12. Action States Once all information about the product purchase has been collected from the user and vali- dated, the purchase order can be submitted. The processing of the purchase order is the first time in this flow where the business tier needs to be invoked, within a transactional context. The action state is designed to invoke application code, and perhaps code that is non- indempotent (it should not be repeated). When an action state is entered, one or more actions are invoked. What these actions do is up to you. In this case, you are interested in calling the placeOrder() method on an existing OrderClerk business façade. See Listing 11-10. Listing 11-10. OrderClerk Interface @Transactional public interface OrderClerk { void placeOrder(Purchase purchase); } To do this, you simply instruct the flow to call the placeOrder() method for you when the action state is entered, as shown in Listing 11-11. Listing 11-11. /WEB-INF/flows/purchase-flow.xml Containing placeOrder Action State <flow start-state="enterPurchaseInformation"> <view-state id="enterPurchaseInformation" view="purchaseForm"> <entry-actions> <action bean="formAction" method="setupForm"/> </entry-actions> <transition on="submit" to="requiresShipping"> <action bean="formAction" method="bindAndValidate"/> </transition> <transition on="cancel" to="cancel"/> </view-state> CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 327 584X_Ch11_FINAL 1/30/06 1:05 PM Page 327 <decision-state id="requiresShipping"> <if test="${flowScope.purchase.shipping}" then="enterShippingDetails" ➥ else="placeOrder"/> </decision-state> <view-state id="enterShippingDetails" view="shippingForm"> <transition on="submit" to="placeOrder"> <action bean="sellItemAction" method="bindAndValidate"/> </transition> </view-state> <action-state id="placeOrder"> <action bean="orderClerk" method="placeOrder(${flowScope.purchase})"/> <transition on="success" to="showCostConfirmation"/> </action-state> <import resource=”purchase-flow-context.xml”/> </flow> In this case the action referenced is the orderClerk, which is simply a plain old Java object (POJO). The referenced OrderClerk implementation has no dependency on SWF and does not implement the Action interface—Spring Web Flow will adapt the placeOrder method to the Action interface automatically. As you can see, method argument expressions can also be specified. The preceding action-state definition means, “When the placeOrder state is entered, invoke the placeOrder() method on the orderClerk façade, passing it the purchase object from flow scope as an input argument; then, on a successful return (when no exception is thrown) transition to the showCostConfirmation state.” Action states are not limited to invoking just one action; you may invoke any number of actions as part of a chain. You will see how and when to do this in Chapter 12. ■Note You’ll learn more about Chain of Responsibility and Spring’s POJO-method-binding capability in Chapter 12. End States The last core state type needed to complete the example flow is the end state. End states sim- ply terminate the executing flow when entered. Once the execution of a flow is terminated, any allocated resources in flow scope are automatically cleaned up. The execution cannot “come back;” it is only possible to start a new, completely independent execution. ■Note The exception to this is if the ending flow is being used as a subflow, in which case the flow that spawned the subflow is expected to resume execution. For more information on subflows, consult Chapter 12. CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW328 584X_Ch11_FINAL 1/30/06 1:05 PM Page 328 [...]... like Spring MVC communicate with Spring Web Flow If Spring Web Flow is a proverbial black box, then FlowExecutionManager is the lid This “handover” to Spring Web Flow is accomplished by calling either the FlowExecutionManager.launch() or FlowExecutionManager.signalEvent() method, which instructs Spring Web Flow to initiate processing of an external user event The launch operation instructs Web Flow. .. top-level flow for which the FlowExecution is created (in our example this would be purchase -flow) If the flow spawns a subflow (e.g., shipping -flow) the root flow remains the same, but getActiveFlow() now returns the subflow definition (shipping -flow) FlowSessions This distinction between the root flow and the active flow is modeled by org.springframework webflow.FlowSession A stack of FlowSessions... rehydrate( FlowLocator flowLocator, FlowExecutionListenerLoader listenerLoader); } The interface that is extended (org.springframework.webflow.FlowExecutionContext, see Listing 12-14) provides contextual information about the executing flow Listing 12-14 org.springframework.webflow.FlowExecutionContext public interface FlowExecutionContext extends FlowExecutionStatistics { Flow getRootFlow(); Flow getActiveFlow()... a top-level flow, including any spawned subflows org.springframework.webflow.FlowSession Represents the execution of a single flow definition (not including subflows) org.springframework.webflow.RequestContext Represents the execution of a single request into Spring Web Flow triggered by the occurrence of an external event FlowExecutionListener Spring Web Flow defines a rich event model that allows... behavior and so on ■ For convenience Spring Web Flow provides an empty FlowExecutionListener implementation, Tip org.springframework.webflow.execution.FlowExecutionListenerAdapter Listeners are attached to a FlowExecution via an org.springframework.webflow.execution FlowExecutionListenerLoader The FlowExecutionListenerLoader infrastructure allows you to define which FlowExecutionListeners apply to which flow. .. between the view and Spring Web Flow can be visualized as shown in Figure 11-6 584X_Ch11_FINAL 1/30/06 1:05 PM Page 333 CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW _flowId=purchase -flow View View View _flowExecutionId= (purchase -flow, enterPurchaseInformation) _flowExecutionId= (purchase -flow, enterPurchaseInformation) _eventId=submit Create a new FlowExecution for the flow named “purchase -flow Execute... understanding of how Spring Web Flow works and be able to extend the framework to meet your needs Business Logic and Flows Chapter 11 introduced the Spring Web Flow framework and walked through the implementation of an example flow It also discussed how Spring Web Flow should not be treated as a golden hammer One area where you need to be especially considerate of this point is that of logic; how much and. .. a flow Table 12-5 Main Artifacts Used for the Execution of a Flow Artifact Description org.springframework.webflow.ExternalContext Provides access to the external environment in which Spring Web Flow is executing org.springframework.webflow.ViewSelection Defines a logical view selected for rendering org.springframework.webflow.execution.FlowExecution Represents the entire execution of a top-level flow, ... between calling either a top-level flow or an inline flow as a subflow An inline flow by definition must simply be declared (and fully contained) within the calling flow ■ Subflows are retrieved via the flowRegistry mechanism, while inline flows are declared within the Tip same flow Listing 12-10 /WEB- INF/flows/purchase -flow. xml with the Shipping Flow As an Inline Flow ... class="org.springframework.webflow.manager .mvc FlowController"> Spring MVC will now route requests for the /purchase.htm URL to the org.springframework webflow .mvc. FlowController As you can see, the Controller needs a reference to a flowRegistry, which contains the flow definitions that are eligible for execution FlowRegistry A FlowRegistry (see Listing . your-domain-servlet.xml flows.xml Standard Spring MVC- servlet.xml Infrastructure common across *all* Spring Web Flows WEB- INF a -flow. xml Definition of first web flow x -flow. xml Definition of xth web flow a-context.xml Bean. for first web flow x-context.xml BeanFactory for xth web flow WEB- INF/flows CHAPTER 11 ■ INTRODUCTION TO SPRING WEB FLOW 3 19 584X_Ch11_FINAL 1/30/06 1:05 PM Page 3 19 The Purchase Product Flow Definition A. container. Installing Spring Web Flow Instructions for downloading and installing Spring Web Flow can be found at http:// opensource2.atlassian.com/confluence /spring/ display/WEBFLOW/Home. Proposed Flow Directory