Communication in Spring Webflow 2

This article tries to complement the reference documentation with examples on variables, scopes and flows in Spring Webflow 2. It shows different ways to share data between controllers and views that form a flow. The article is divided into the following sections:

The example explained here uses Spring Web flow 2.3.1 and Spring 3.0.5 versions. You can get the source code my github webflow repository, with all the examples shown in this article.

1   Setting flow variables

There are different ways of setting variables and storing them into the flow scope, where they will then be accessible to controllers and views.

1.1 var element (variable)

The var element instantiates a class and stores it in the flow scope. Since the instance will be stored between flow requests, the class should implement java.io.Serializable. It does not allow you to assign a value, making this element useless for classes without a default constructor (it will crash at runtime with a nice NoSuchMethodException). In that case, you can use the set element (see section 3).

Defining a variable:

<var name="car" class="xpadro.tutorial.webflow.model.Car"/>

You can use this variable in the flow definition, for example, passing it as a parameter to a controller:

<var name="car" class="xpadro.tutorial.webflow.model.Car"/>

<action-state id="startAssembly">
    <evaluate expression="carFactory.paintCar(car)"/>
	...

The method will receive the car parameter:

public Event paintCar(Car car) {
    ...

Since it is automatically stored in the flow scope, you can also retrieve the value from the request context:

public Event myControllerMethod(RequestContext context) {
    Car car = (Car) context.getFlowScope().get("car");
    ...

Or use it in the view using for example ${car.color}

1.2 At flow starting point

If your class has no default constructor or you want to retrieve the instance from a service, the element var won’t be enough. You can use a controller instead:

<on-start>
    <evaluate expression="customerService.getPreferences()" result="flowScope.preferences"/>
</on-start>

In this sample, the information could be retrieved from a service. The result attribute will get the object returned by the controller and store it in the flow scope.

You can also use this approach when entering a state (on-entry) or before rendering a view (on-render).

1.3 Anywhere in the flow

Since you have access to the Web flow RequestContext when invoking a controller, you can set a flow scoped variable as shown below:

public Event myControllerMethod(RequestContext context) {
    context.getFlowScope().put("car", new Car());
    ...

 

2 Setting attribute values

The set element allows you to define an attribute and specify a scope. This element takes the following attributes:

For example, you can use it at the beginning of a flow (on-start) or when entering into a state (on-entry). The following sample show how to assign it a specified value when the flow starts:

<on-start>
    <set name="flowScope.factoryId" value="5061" type="java.lang.Integer"/>
</on-start>

Or you could set it when launching a transition:

<transition on="success" to="nextState">
    <set name="flowScope.factoryId" value="5061" type="java.lang.Integer"/>
</transition>

The set element not only allows you to define objects, but also String values. For example, if you have a bean named ‘myBean’, the first set element in the following sample will retrieve the Car instance from the bean. Then, it will store it in the request scope with the name ‘carObject’. On the other hand, the second set element will store in the request scope an attribute named ‘carString’. This attribute contains the String ‘myBean.car’.

<transition on="success" to="addMechanics">
    <set name="requestScope.carObject" value="myBean.car"/>
    <set name="requestScope.carString" value="'myBean.car'"/>
</transition>

You can also use implicit variables when setting the value of the set element (see section 10 for a list of these variables):

<set name="requestScope.carBean" value="flowScope.car"/>

 

3 Using additional scopes

The RequestContext interface contains access to all the other scopes defined in Spring Web flow: request, flash, view, flow and conversation.

You can also access these scopes at flow definition level by using implicit EL variables, which are: requestScope, flashScope, viewScope, flowScope and conversationScope.

For other scopes, you can use the external context:

At flow definition using implicit variables:

<set name="externalContext.sessionMap.factoryId" value="5061" type="java.lang.Integer"/>

Or at controller level through RequestContext interface:

context.getExternalContext().getSessionMap().put("factoryId", 5061);

 

4 Communication with sub flows

When invoking a sub flow from the main flow, you can pass it input attributes. Once the sub flow has finished, it may return output attributes.

Input:
The input element allows you to send parameters to the sub flow. When the sub flow starts, these input attributes are stored in the flow scope of the sub flow. You will need to define the input element in the sub flow, using the same name attribute as used in the main flow.

Output:
Once the sub flow ends, the main flow can receive output parameters. These output parameters are defined within sub flow’s end-states. When the execution returns to the main flow. output parameters will be available as attributes inside the launched event.

Main flow: invoking a sub flow

<subflow-state id="addMechanics" subflow="mechanics-flow">
    <input name="currentCar" value="requestScope.carInstance1" type="xpadro.tutorial.webflow.model.Car"/>
    <input name="preferences" value="flowScope.preferences" type="java.util.Map"/>
    <transition on="mechanicsSuccess" to="carValidationPhase1">
        <set name="flowScope.mechanics" value="currentEvent.attributes.mechanics" />
    </transition>
    <transition on="mechanicsFail" to="assemblyFailed"/>
</subflow-state>

 

When returning to the main flow, you will need to define an attribute with the value returned by the sub flow in order to make it accessible to following states.

You could also invoke a controller that would set the value and store it in the needed scope:

<transition on="mechanicsSuccess" to="assemblyFinalized">
    <evaluate expression="carFactory.setOutcome(currentEvent.attributes.mechanics)" />  
</transition>

Sub flow definition

<input name="currentCar" type="xpadro.tutorial.webflow.model.Car"/>
<input name="preferences" type="java.util.Map"/>
    
<action-state id="setMechanics">
    <evaluate expression="carFactory.addMechanics"/>
    <transition on="success" to="mechanicsSuccess"/>
    <transition on="fail" to="mechanicsFail"/>
</action-state>
	
<end-state id="mechanicsSuccess">
    <output name="mechanics" value="'success'"/>
</end-state>
	
<end-state id="mechanicsFail">
    <output name="mechanics" value="'fail'"/>
</end-state>

 

There are other options to pass information to a subflow which consists in the following:

<transition on="success" to="addMechanics">
    <set name="requestScope.carInstance" value="car"/>
</transition>

 

5 Communication with other flows

You have two options of passing data to another flow which is not related to the current flow:

  1. Session scoped attributes
  2. URL Request parameters

If you choose the second option, you can do it the following way:

In the view:

<a href="otherflow-flow?otherParam=otherValue">other flow</a>

When starting the other flow, you can use the requestParameters implicit variable to retrieve the value and store it in the needed scope.

<on-start>
    <set name="flowScope.urlParam" value="requestParameters.otherParam" type="java.lang.String" />
</on-start>

You could also do it in the controller:

<on-start>
    <evaluate expression="startFlowController.start"/>
</on-start>
public Event start(RequestContext context) {
    ParameterMap map = context.getExternalContext().getRequestParameterMap();
    context.getFlashScope().put("otherParam", map.get("otherParam"));
    ...

Or use the requestParameterMap directly through the getRequestParameters shortcut method:

ParameterMap map = context.getRequestParameters();
context.getFlashScope().put("otherParam", map.get("otherParam"));

 

6 Launching events with attributes

When exiting a controller, it is possible to add attributes to the current event. To do that you need to generate an attribute map.

<action-state id="action1">
    <evaluate expression="myFirstController.setAttribute()"/>
    <transition on="done" to="action2"/>
</action-state>

<action-state id="action2">
    <evaluate expression="mySecondController.getAttribute(currentEvent.attributes.testAttribute)"/>
    <transition on="done" to="myView"/>
</action-state>

The controller which launches the event adds the attribute as follows:

public Event setAttribute() {
    Map<String,String> map = new HashMap<String,String>();
    map.put("testAttribute", "test");
    AttributeMap attributeMap = new LocalAttributeMap(map);
    return new Event(this, "success", attributeMap);
}

 

7 Accessing the request context

If you want to invoke methods from classes other than controllers, like beans, it is possible to retrieve the web flow request context. There are two ways:

Passing the request context as a parameter to the bean method:

<action-state id="carValidationPhase1">
    <evaluate expression="supportBean.validateCarColor(flowRequestContext)"/>
    <transition on="success" to="carValidationPhase2"/>
</action-state>
public String validateCarColor(RequestContext context) {
    Car car = (Car) context.getFlowScope().get("car");
    ...

Or you can use the RequestContextHolder class:

<action-state id="carValidationPhase2">
    <evaluate expression="supportBean.validateCarMechanics()"/>
    <transition on="success" to="assemblyFinalized"/>
</action-state>
public String validateCarMechanics() {
    RequestContext context = RequestContextHolder.getRequestContext();
    Car car = (Car) context.getFlowScope().get("car");
    ...

 

8 Accessing Spring beans

If you need to retrieve beans from the Spring context, you can implement a utility class. The following method accesses the Spring context through the web flow request context.

public <T> T getBean(String name, Class<T> clazz) {
    RequestContext context = RequestContextHolder.getRequestContext();
    return context.getActiveFlow().getApplicationContext().getBean(name, clazz);
}

 

9 Using implicit variables

There’s a full list of all available implicit variables at the web flow reference documentation

springsource web flow reference (el-variables)