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.
<?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:p="http://www.springframework.org/schema/p" 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.3.xsd"> <!-- Enables FlowHandlers --> <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter" p:flowExecutor-ref="flowExecutor" /> <!-- 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" p:viewResolvers-ref="tilesViewResolver" /> <!-- Installs a listener to apply Spring Security authorities --> <bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" /> <!-- Used in 'create' action-state of Person Flow --> <bean id="personBean" class="org.springbyexample.web.orm.entity.Person" scope="prototype" /> </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 mvc:annotation-driven configures annotation-based handlers for the controllers. The mvc:view-controller element sets explicit mappings to the index page, login page, and logout page. None of which needs to go through a controller for rendering.
The tilesViewResolver in the Spring Web Flow example is the AjaxUrlBasedViewResolver
,
which is able to handle rendering fragments of a Tiles context. It's viewClass property set
to use FlowAjaxDynamicTilesView
. This example uses AJAX to populate just the body of the page on a form submit.
Also, Spring by Example's Dynamic Tiles Spring MVC Module
is used to reduce the Tiles configuration.
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="org.springbyexample.web.servlet.mvc" /> <!-- URL to flow mapping rules --> <bean id="flowMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" p:order="0"> <property name="mappings"> <value>/person.html=personFlowHandler</value> </property> </bean> <mvc:annotation-driven /> <mvc:view-controller path="/index.html" /> <mvc:view-controller path="/login.html" /> <mvc:view-controller path="/logoutSuccess.html" /> <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" p:definitions="/WEB-INF/tiles-defs/templates.xml" /> <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:viewClass="org.springbyexample.web.servlet.view.tiles2.DynamicTilesView" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basenames="messages" /> <!-- Declare the Interceptor --> <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="locale" /> </mvc:interceptors> <!-- 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 'create' action-state, otherwise it goes to the 'editPerson' action-state. In 'create' the personBean bean, which is scoped as a prototype bean (new instance for each call), is called and the value is put into 'flowScope' under 'person. The evaluation is performed using the Spring Expression Language (Spring EL), which has been used by Spring Web Flow since version 2.1. The 'edit' action-state uses the Spring Data JPA person repository to look the person record based on the id in the edit URL.
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 repository. 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).
<?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="create" else="edit" /> </decision-state> <action-state id="create"> <evaluate expression="personBean" result="flowScope.person" /> <transition to="personForm" /> </action-state> <action-state id="edit"> <evaluate expression="personService.findById(id)" result="flowScope.person" /> <transition to="personForm" /> </action-state> <view-state id="personForm" model="person" view="/person/form"> <transition on="save" to="save"> <evaluate expression="personService.save(person)" /> <evaluate expression="personService.find()" result="flowScope.persons" /> </transition> <transition on="cancel" to="cancel" bind="false"> <evaluate expression="personService.find()" result="flowScope.persons" /> </transition> </view-state> <end-state id="save"/> <end-state id="cancel"/> </flow>