2. Spring Configuration

Basic Spring Web Flow configuration with Tiles as the view resolver and the security flow execution listener. The webflow:flow-registry element registers the person flow. The person flow XML file is stored with the person form and search page. A flow specific message resources file (messages.properties) could also be put in this location.

/WEB-INF/webflow-config.xml
                
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:webflow="http://www.springframework.org/schema/webflow-config"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/webflow-config
                           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">

    <!-- Enables FlowHandlers -->
    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
        <property name="flowExecutor" ref="flowExecutor" />
    </bean>

    <!-- Executes flows: the entry point into the Spring Web Flow system -->
    <webflow:flow-executor id="flowExecutor">
        <webflow:flow-execution-listeners>
            <webflow:listener ref="securityFlowExecutionListener" />
        </webflow:flow-execution-listeners>
    </webflow:flow-executor>

    <!-- The registry of executable flow definitions -->
    <webflow:flow-registry id="flowRegistry"
            flow-builder-services="flowBuilderServices">
        <webflow:flow-location path="/WEB-INF/jsp/person/person.xml" />
    </webflow:flow-registry>

    <!-- Plugs in a custom creator for Web Flow views -->
    <webflow:flow-builder-services id="flowBuilderServices"
            view-factory-creator="mvcViewFactoryCreator" />

    <!-- Configures Web Flow to use Tiles to create views for rendering; Tiles allows for applying consistent layouts to your views -->
    <bean id="mvcViewFactoryCreator"
          class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
        <property name="viewResolvers" ref="tilesViewResolver" />
    </bean>

    <!-- Installs a listener to apply Spring Security authorities -->
    <bean id="securityFlowExecutionListener"
          class="org.springframework.webflow.security.SecurityFlowExecutionListener" />

</beans>
                
            

The handlers are configured so flows and annotation-based controllers can be used together. The url '/person.html' is mapped to the person flow in the flowMappings bean and assigned a custom flow handler, which redirects to the search page at the end of the flow and if an exception not handled by the flow occurs.

The tilesViewResolver in the Spring Web Flow examples is normally AjaxUrlBasedViewResolver with the viewClass property set to use FlowAjaxTilesView. This example isn't using AJAX to dynamically populate anything, so Dynamic Tiles Spring MVC Module is used to reduce the Tiles configuration.

/WEB-INF/webmvc-config.xml
                
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="interceptors" ref="localeChangeInterceptor" />
    </bean>

    <!-- Enables annotated POJO @Controllers -->
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

    <!-- Enables plain Controllers -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

    <!-- URL to flow mapping rules -->
    <bean id="flowMappings"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>/person.html=personFlowHandler</value>
        </property>
        <property name="order" value="0" />
    </bean>

    <!-- Enables convention-based request URL mapping to @Controllers (ex: /person/* maps to PersonController) -->
    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
        <property name="order" value="1" />
    </bean>

    <bean id="tilesConfigurer"
          class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/tiles-defs/templates.xml</value>
            </list>
        </property>
    </bean>

    <bean id="tilesViewResolver"
          class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass"
                  value="org.springbyexample.web.servlet.view.tiles2.DynamicTilesView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <value>messages</value>
        </property>
    </bean>

    <!-- Declare the Interceptor -->
    <bean id="localeChangeInterceptor"
          class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="locale" />
    </bean>

    <!-- Declare the Resolver -->
    <bean id="localeResolver"
          class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

</beans>
                
            

Custom flow for person handling create and edit. The decision-state checks if the id is null and if it is it goes to the 'createPerson' action-state, otherwise it goes to the 'editPerson' action-state. In 'createPerson' the personController's bean's newPerson() method is called and the value is put into 'flowScope' under 'person. The evaluation is performed using EL. Spring Web Flow supports OGNL and JBoss EL. The example uses JBoss El. The 'editPerson' action-state uses the Hibernate person DAO to look the person record based on the id in the edit URL.

[Note]Note

It would be better to have a prototype bean configured to get a new Person instance, but this was done to show some of the flexiblity you get from Spring Web Flow's EL support. See Spring Web Flow Subflow Webapp for an example using prototype beans to put a new Person instance in the flow scope.

Both create and edit forward to the 'personForm' view where the user has a save and cancel button. Both of these buttons are handled using the transition element. The 'save' transition saves the person using the person DAO. Then both save and cancel populate the latest search results and forward to end-state elements that have their view set to the person search page.

The flow is secured to the Spring Security role of 'ROLE_USER'. Which in this case is redundant since the entire webapp is secured to this role, but finer grained rules can make use of this and also it's good to secure the flow since they are reusable components (as subflows).

Person Flow (/WEB-INF/jsp/person/person.xml)
                
<?xml version="1.0" encoding="UTF-8"?>
<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-2.0.xsd">

    <secured attributes="ROLE_USER" />

    <input name="id" />

    <decision-state id="createOrEdit">
        <if test="id == null" then="createPerson" else="editPerson" />
    </decision-state>

    <action-state id="createPerson">
        <evaluate expression="personController.newPerson()"
                  result="flowScope.person" />
        <transition to="personForm" />
    </action-state>

    <action-state id="editPerson">
        <evaluate expression="personDao.findPersonById(id)"
            result="flowScope.person" />
        <transition to="personForm" />
    </action-state>

    <view-state id="personForm" model="person" view="/person/form">
        <transition on="save" to="savePerson">
            <evaluate expression="personDao.save(person)" />

            <evaluate expression="personDao.findPersons()"
                      result="flowScope.persons" />
        </transition>
        <transition on="cancel" to="cancelPerson" bind="false">
            <evaluate expression="personDao.findPersons()"
                      result="flowScope.persons" />
        </transition>
    </view-state>

    <end-state id="savePerson" />

    <end-state id="cancelPerson" />

</flow>