Creating A Spring Web Flow JSF Project From Scratch
(Updated – 9 August 2010 – This was written in my pre-Maven days and after a few requests for working source, I’ve built the same project using Maven which can be downloaded. Just unzip the maven project, go to the directory in the command line and type mvn jetty:run
to start the server and deploy the project. Navigate to http://localhost:8080/swfproject/home.jsf or http://localhost:8080/swfproject/spring/testFlow to see the pages demonstrated in the tutorial.
I recently had to start another project using Spring Web Flow and found myself banging my head against a brick wall to get the web flow stuff set up and to request the page properly. As a result, I decided to write up my results as a quick how-to for other developers should they find themselves in the same situation and also as a reference for myself the next time I need to start a Spring Web Flow project using Spring Faces from scratch.This article is meant more of a “here’s-how” as opposed to a “how-to” or an “explain-why” so we’ll move at a quick pace with little explanation.
For the IDE, I used Eclipse 3.4.1 with Spring IDE plugins version 2.2.1, with Spring 2.5.6 (with dependencies) and Spring Web Flow 2.0.5. You should be able to use SWF 2.0.7 without any problems. For dependencies, I mostly used the ones provided with Spring except for a few, the details of which are included below. Hibernate was used as the JPA implementation and it was all deployed on Tomcat 6.0.18.
Getting Started
U started by installing Eclipse and then the Spring plugins. I created a new workspace, and added Tomcat as a server, and created a new dynamic web project. I right clicked on the project to add the Spring nature to the project.
The project will be arranged with multiple Spring configuration files for the database, spring web flow and the main applicationContext.xml
to include them in. Flows will be in a directory under /WebContent/WEB-INF/flows
.
I started off setting web.xml
up with the faces pieces and the Spring MVC dispatcher servlet.
<!-- The main config file for this Spring web application --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <!-- Use JSF view templates saved as *.xhtml, for use with Facelets --> <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.xhtml</param-value> </context-param> <!-- Enables special Facelets debug output during development --> <context-param> <param-name>facelets.DEVELOPMENT</param-name> <param-value>true</param-value> </context-param> <!-- Causes Facelets to refresh templates during development --> <context-param> <param-name>facelets.REFRESH_PERIOD</param-name> <param-value>1</param-value> </context-param> <!-- Loads the Spring web application context --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Serves static resource content from .jar files such as spring-faces.jar --> <servlet> <servlet-name>Resources Servlet</servlet-name> <servlet-class> org.springframework.js.resource.ResourceServlet </servlet-class> <load-on-startup>0</load-on-startup> </servlet> <!-- Map all /resources requests to the Resource Servlet for handling --> <servlet-mapping> <servlet-name>Resources Servlet</servlet-name> <url-pattern>/resources/*</url-pattern> </servlet-mapping> <!-- The front controller of this Spring Web application, responsible for handling all application requests --> <servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all /spring requests to the Dispatcher Servlet for handling --> <servlet-mapping> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <url-pattern>/spring/*</url-pattern> </servlet-mapping> <!-- Just here so the JSF implementation can initialize, *not* used at runtime --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Just here so the JSF implementation can initialize --> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.jsf</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
At the top of this web.xml
file, we indicate that our primary Spring bean config file is called \WEB-INF\applicationContext.xml
so we navigate to that folder (it’s in the WebContent folder) and right click and add a new Spring Bean Definition. We also add two other Spring Bean definitions in the same place called dbConfig.xml
and flowConfig.xml
. These files are defined below :
/WebContent/WEB-INF/applicationContext.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"> <import resource="dbConfig.xml" /> <import resource="flowConfig.xml" /> </beans>
/WebContent/WEB-INF/dbConfig.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="true" /> <property name="generateDdl" value="true" /> <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" /> </bean> </property> <property name="dataSource" ref="dataSource" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/dbName" /> <property name="username" value="someUser" /> <property name="password" value="somePassword" /> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="dataSource" ref="dataSource" /> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven /> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> </beans>
/WebContent/WEB-INF/flowConfig.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" xmlns:faces="http://www.springframework.org/schema/faces" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.0.xsd"> <!--Executes flows: the central entry point into the Spring Web Flow system--> <webflow:flow-executor id="flowExecutor"> <webflow:flow-execution-listeners> <webflow:listener ref="jpaFlowExecutionListener" /> </webflow:flow-execution-listeners> </webflow:flow-executor> <!-- The registry of executable flow definitions --> <webflow:flow-registry id="flowRegistry" flow-builder-services="facesFlowBuilderServices"> <webflow:flow-location path="/WEB-INF/flows/testFlow/testFlow.xml"></webflow:flow-location> </webflow:flow-registry> <!-- Configures the Spring Web Flow JSF integration --> <faces:flow-builder-services id="facesFlowBuilderServices" development="true" /> <!-- Installs a listener that manages JPA persistence contexts for flows that require them --> <bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener"> <constructor-arg ref="entityManagerFactory" /> <constructor-arg ref="transactionManager" /> </bean> <!-- Maps request URIs to controllers --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /testFlow=flowController </value> </property> <property name="defaultHandler"> <!-- Selects view names to render based on the request URI: e.g. /main selects "main" --> <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> </property> </bean> <!-- Handles requests mapped to the Spring Web Flow system --> <bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController"> <property name="flowExecutor" ref="flowExecutor" /> </bean> </beans>
Since we are using JPA, we need to include a persistence.xml
file to the classpath in src/META-INF/persistence.xml
.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="default" transaction-type="RESOURCE_LOCAL" /> </persistence>
Now we need to add a faces-config.xml
in the same location.
/WebContent/WEB-INF/faces-config.xml
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> </application> </faces-config>
We’ll also add a html page that redirects to a jsf page immediately. Since we are using facelets, we’ll also throw in a template to work from. In the WebContent folder, we’ll add the templates
directory to contain our page layout.
WebContent\templates\template.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title><ui:insert name="title">untitled</ui:insert></title> </head> <body> <h:messages globalOnly="true" /> <ui:insert name="title">Title Here</ui:insert> <ui:insert name="body" /> </body> </html>
Now to add our two pages that will initially launch is into a JSF page.
/WebContent/index.html
<html> <head> <meta http-equiv="Refresh" content="0; URL=home.jsf"> </head> </html> </textarea> <code>/WebContent/home.xhtml</code> <pre name="code" class="xml"> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="templates/template.xhtml"> <ui:define name="body"> <h:form> <h:outputText value="Hello From JSF!" /> </h:form> </ui:define> </ui:composition>
One more configuration file we need is for Log4j to get rid of the warnings that it has not been set up correctly. The properties file goes in the src
directory.
/src/log4j.properties
### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n log4j.rootLogger=info, stdout log4j.category.org.springframework=WARN log4j.category.org.hibernate=WARN
Now let’s look at libraries. There are a bunch of libraries that are needed since we haven’t added any yet.
Libraries located in /WebContent/WEB-INF/lib/ |
|
---|---|
antlr-2.7.6.jar | Spring Dependencies |
cglib-nodep-2.1_3.jar | Spring Dependencies |
commons-beanutils.jar | Spring Dependencies |
commons-collections.jar | Spring Dependencies |
commons-dbcp.jar | Spring Dependencies |
commons-digester.jar | Spring Dependencies |
commons-logging.jar | Spring Dependencies |
commons-pool.jar | Spring Dependencies |
dom4j-1.6.1.jar | Spring Dependencies |
hibernate3.jar | Spring Dependencies |
hibernate-annotations.jar | Spring Dependencies |
hibernate-commons-annotations.jar | Spring Dependencies |
hibernate-entitymanager.jar | Spring Dependencies |
javaee.jar | Obtained From Glassfish |
javassist-3.4.GA.jar | Spring Dependencies |
jboss-el.jar | Obtained From JBoss Seam |
jsf-api.jar | Downloaded Mojarra 1.2_11 |
jsf-facelets.jar | Obtained From JBoss Seam |
jsf-impl.jar – | Downloaded Mojarra 1.2_11 |
log4j-1.2.15.jar | Spring Dependencies |
mysql-connector-java-5.0.4-bin.jar | Downloaded from MySQL |
org.springframework.binding-2.0.5.RELEASE.jar | Spring Web Flow Download |
org.springframework.faces-2.0.5.RELEASE.jar | Spring Web Flow Download |
org.springframework.js-2.0.5.RELEASE.jar | Spring Web Flow Download |
org.springframework.webflow-2.0.5.RELEASE.jar | Spring Web Flow Download |
persistence.jar | Spring Dependencies |
slf4j-api-1.5.0.jar | Spring Dependencies |
slf4j-log4j12-1.5.0.jar | Spring Dependencies |
spring.jar | Spring Dependencies |
spring-webmvc.jar | Spring Dependencies |
One reason I downloaded the latest JSF version was because I was having problems with the JSF version I was using. The SpringBeanELResolver was being ignored at run-time, it didn’t even blink if I set it to an undefined class name, however the Spring Delegating variable resolver was working, but the IDE was saying it was deprecated. Once I upgraded to JSF 1.2_11, the EL resolver worked fine. I’m wondering if an old 1.1 JSF version had crept in there.
Depending on where you end up deploying your application (i.e.Glassfish), you may end up having to remove some of these libraries if they are already installed on the server. In this case, I was using Apache 6.0.18 with a clean install, and therefore with no libraries added to the server.
Creating a Flow
Now we should have a working application all ready to go. To test this, we’ll add a little code and a test page just to verify that everything is working ok. Create a new class called MessageHolder
in a package called swfproject
. This is a simple class that contains a string that can be set and retrieved from our pages.
/src/swfproject/MessageHolder.java
package swfproject; public class MessageHolder implements Serializable { private String text = "Hello From the Message Holder"; public String getText() { return text; } public void setText(String text) { this.text = text; } }
Now we define the bean in the /WEB-INF/applicationContext.xml
so we can call the bean from a regular jsf page, or a flow page.
<bean name="springMessage" class="swfproject.MessageHolder"> <property name="text" value="This was defined in Spring" /> </bean>
In the home.xhtml
page, we add the following line to the page, somewhere between the ui:define
tag on the page to display the message
Message is : #{springMessage.text}
If you start Tomcat, assuming you have already attached the project to the server if you are using an IDE, and go to http://localhost:8080/projectName/
you should redirect to home.jsf
and it should the message that was defined in Spring.
Now let’s add a simple flow called testFlow. If you look in the flowConfig.xml
file, there is already a mappings property where we already defined /testFlow=flowController
. This means that when this url is requested, the flowController
bean instance deals with it.
We need to create a new directory under /WebContent/WEB-INF/flows/
, and in there, we need to create a directory called testFlow
. In that, we add a page called testFlow.xhtml
and testFlow.xml
. This is the view and the flow defined respectively.
/WebContent/WEB-INF/flows/testFlow/testFlow.xml
Flow
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:faces="http://www.springframework.org/schema/faces" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd" start-state="testFlow"> <var name="flowMessage" class="swfproject.MessageHolder" /> <view-state id="testFlow"> <transition on="post" to="testFlow" /> </view-state> </flow>
/WebContent/WEB-INF/flows/testFlow/testFlow.xhtml
View
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="/templates/template.xhtml"> <ui:define name="body"> <h:form> <h:outputText value="This is a test flow" /> <br /> Message From Spring = #{springMessage.text}<br /> Message From Flow = #{flowMessage.text}<br /> <h:inputText value="#{flowMessage.text}" style="width:200px" /> <h:commandButton action="post" value="Update" /> </h:form> </ui:define> </ui:composition>
Now if you go to http://localhost:8080/app_name/spring/testFlow
(Remember to replace app_name
with your project name) you should see the page we have made as part of our flow. It displays the message from the springMessage
instance of the MessageHolder
that contains the message from spring. I now also displays the instance from the flow which contains the default message displayed since the flow variable hasn’t had the text property changed. Also, the URL should change to http://localhost:8080/app_name/spring/testFlow?execution=e3s1
or something similar with the execution on the end. This page demonstrates that we will have access to the spring beans, as well as access to variables defined in a flow which is where flowMessage
is defined. You can edit the message and click post to change the flowMessage text value. You can open the link up in two browser windows or tabs and see how the two values can be edited independently, and the flowMessage variable is scoped to the flow in the browser.
From this point, you can go ahead and move on with the application all you like. You can change the flow mappings, put in wildcarded flow locations, even get started with trying out Spring MVC (as I plan to). Hope you find this useful as a quick start guide to getting a JSF Spring Web Flow project up and running, as well as defining flows and calling them.
5 thoughts on “Creating A Spring Web Flow JSF Project From Scratch”
Comments are closed.
Very good quick start tutorial.
Is possible to integrate Your project with JSF 2.0 ?
Hi Andy,
I did the same thing step by step but in the flowconfig.xml ,it is showing errors
“cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element ‘webflow:flow-executor”
I am using Eclipse 3.5 Galileo, Kindly help on this
It looks like a versioning problem. In the flowConfig.xml document, you should have :
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
in the schema location at the top. Make sure it is version 2.0 since 1.0 does not have the flow executor in the same way. Beyond that, take a look at the maven download, that is working and copy from there.
i would like to test the webflow project in jbossenterprise application server. i am getting incomplete deploymentexception.i removed the dependencies from maven.
then copied the libraries in lib in webapp. Still unable to deploy on Jboss EAP 5.1