Manual Básico de Spring WebFlow
Spring WebFlow es un Controlador
(http://es.wikipedia.org/wiki/Modelo_Vista_Controlador)
que orienta a procesos los flujos de navegación de una
aplicación definiéndolos mediante estados y
transiciones.
A medida que aplicamos Spring WebFlow a
nuestros proyectos, nos acostumbramos a sus virtudes y un día
nos damos cuenta de que no podríamos volver a trabajar como
antes. Más o menos como nos pasó cuando descubrimos
Struts o cualquier MVC. Y cuando más avanzamos, más
queremos y Spring es casi inagotable.
El objetivo de este tutorial es
facilitar el primer contacto con esta tecnología y analizar su
aplicabilidad, recomendar algunos links y comentaros nuestras
conclusiones.
Preparación del proyecto
Comenzaremos creando un proyecto y para este tutorial utilizaremos
Eclipse:
Seleccionamos
proyecto web:
Seleccionamos
un nombre y un servidor:
En
este paso no es necesario completar nada:
Y
completamos los nombres de las carpetas antes de dar a Finalizar:
A
continuación descargamos de
http://www.springframework.org/download
las distribuciones que se indican en rojo:
Copiamos en la carpeta /pruebaWebFlowSimple/web/WEB-INF/lib los
JAR los jar que se encuentran en los zip que descargamos y los
añadimos al proyecto:
Seleccionando
mediante “Add Jars” los que hemos descargado:
Configuración del contexto
Comenzaremos editando /pruebaWebFlowSimple/web/WEB-INF/web.xml
para dejarlo como se muestra a continuación:
<?xml
version=«1.0»
encoding=«ISO-8859-1»?>
<web-app
xmlns=«http://java.sun.com/xml/ns/j2ee»
xmlns:xsi=«http://www.w3.org/2001/XMLSchema-instance»
xsi:schemaLocation=«http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd»
version=«2.4»>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>swf-pruebaWebFlow.root</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>JspRedirector</servlet-name>
<jsp-file>/WEB-INF/test/jspRedirector.jsp</jsp-file>
</servlet>
<servlet>
<servlet-name>pruebaWebFlow</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/pruebaWebFlow-servlet-config.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JspRedirector</servlet-name>
<url-pattern>/JspRedirector</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>pruebaWebFlow</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<jsp-config>
<taglib>
<taglib-uri>http://www.adesis.com/cm/oi/2</taglib-uri>
<taglib-location>/WEB-INF/tld/adesis-cm-io-2.tld</taglib-location>
</taglib>
</jsp-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
También crearemos un fichero
/pruebaWebFlowSimple/web/WEB-INF/pruebaWebFlowSimple-servlet-config.xml
como se muestra a continuación:
<?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:flow=«http://www.springframework.org/schema/webflow-config»
xsi:schemaLocation=«
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd»>
<bean
name=«/asistente.flow»
class=«org.springframework.webflow.executor.mvc.FlowController»>
<property
name=«flowExecutor»
ref=«pruebaFlowExecutor»
/>
<property
name=«defaultFlowId»
value=«asistente-flow»
/>
<property
name=«cacheSeconds»
value=«5»
/>
</bean>
<bean
id=«viewResolver»
class=«org.springframework.web.servlet.view.InternalResourceViewResolver»>
<property
name=«prefix»
value=«/WEB-INF/jsp/»
/>
<property
name=«suffix»
value=«.jsp»
/>
</bean>
<flow:registry
id=«flowRegistry»>
<flow:location
path=«/WEB-INF/flows/**-flow.xml»
/>
</flow:registry>
<flow:executor
id=«pruebaFlowExecutor»
registry-ref=«flowRegistry»>
<flow:execution-attributes>
<flow:alwaysRedirectOnPause
value=«false»
/>
</flow:execution-attributes>
</flow:executor>
</beans>
Definición del flujo
Creamos un fichero
/pruebaWebFlowSimple/web/WEB-INF/flows/asistente-flow.xml como se
muestra a continuación:
<?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-1.0.xsd»>
<start-actions>
<action
bean=«formAction»
method=«setupForm»
/>
</start-actions>
<start-state
idref=«mostrarPaso1»
/>
<view-state
id=«mostrarPaso1»
view=«paso1»>
<transition
on=«siguiente»
to=«avanzaAPaso2»>
<action
bean=«formAction»
method=«bind»
/>
</transition>
</view-state>
<action-state
id=«avanzaAPaso2»>
<action
bean=«formAction»
method=«validarPaso1YPrepararPaso2»
/>
<transition
on=«success»
to=«mostrarPaso2»
/>
<transition
on=«error»
to=«mostrarPaso1»
/>
</action-state>
<view-state
id=«mostrarPaso2»
view=«paso2»>
<transition
on=«siguiente»
to=«avanzaAPaso3»>
<action
bean=«formAction»
method=«bind»
/>
</transition>
<transition
on=«anterior»
to=«mostrarPaso1»>
<action
bean=«formAction»
method=«bind»
/>
</transition>
</view-state>
<action-state
id=«avanzaAPaso3»>
<action
bean=«formAction»
method=«validarPaso2YPrepararPaso3»
/>
<transition
on=«success»
to=«mostrarPaso3»
/>
<transition
on=«error»
to=«mostrarPaso2»
/>
</action-state>
<end-state
id=«mostrarPaso3»
view=«paso3»
/>
<import
resource=«asistente-beans.xml»
/>
</flow>
Vemos cómo el flujo queda
determinado por estados desde donde se realizan transiciones a otros
estados. Los estados pueden ser de visualización o de
ejecución de operaciones, y utilizan beans definidos en
asistente-beans.xml:
<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-2.0.xsd»>
<bean
id=«formAction»
class=«es.autentia.tutoriales.manualBasicoSpringWebFlow.controller.action.AsistenteAction»>
<property
name=«formObjectScope»
value=«FLOW»
/>
<property
name=«formObjectName»
value=«form»
/>
<property
name=«formObjectClass»
value=«es.autentia.tutoriales.manualBasicoSpringWebFlow.controller.form.AsistenteForm»
/>
</bean>
</beans>
Definición de las clases
En el flujo se define el bean donde se
implementarán las acciones y el form que es un JavaBean donde
se almacenan o recuperan los datos que se le muestran al usuario y/o
que éste completa. Cuando en asistente-flow.xml se invoca al
método bind en una transición, se está indicando
a Spring que realice la copia de los valores de formulario enviados
desde el navegador del usuario al formulario del flujo.
package
es.autentia.tutoriales.manualBasicoSpringWebFlow.controller.form;
import
java.io.Serializable;
public
class
AsistenteForm implements
Serializable {
private
static
final
long
serialVersionUID
= 3849222383843518624L;
private
Integer numero1
= 1;
private
Integer numero2
= 2;
private
String mensaje;
public
String getMensaje() {
return
mensaje;
}
public
void
setMensaje(String mensaje) {
this.mensaje
= mensaje;
}
public
Integer getNumero1() {
return
numero1;
}
public
void
setNumero1(Integer numero1) {
this.numero1
= numero1;
}
public
Integer getNumero2() {
return
numero2;
}
public
void
setNumero2(Integer numero2) {
this.numero2
= numero2;
}
}
Crearemos también un Action como
el siguiente:
package
es.autentia.tutoriales.manualBasicoSpringWebFlow.controller.action;
import
org.springframework.webflow.execution.RequestContext;
import
org.springframework.webflow.action.FormAction;
import
org.springframework.webflow.execution.Event;
import
es.autentia.tutoriales.manualBasicoSpringWebFlow.controller.form.AsistenteForm;
public
class
AsistenteAction extends
FormAction {
public
AsistenteAction() {
setFormObjectClass(AsistenteForm.class);
}
@Override
public
Event setupForm(RequestContext context) throws
Exception {
AsistenteForm
form = (AsistenteForm) getFormObject(context);
form.setNumero1(1);
form.setNumero2(2);
form.setMensaje(«Introduzca
los números a sumar»);
return
super.setupForm(context);
}
public
Event validarPaso1YPrepararPaso2(RequestContext context)
throws
Exception {
AsistenteForm
form = (AsistenteForm) getFormObject(context);
Event
resultado = null;
if
(form.getNumero1() < 0 || form.getNumero2() < 0) {
form.setMensaje(«Los
números a sumar deben ser positivos»);
resultado
= error();
}
else
{
form.setMensaje(«El
resultado de la suma es «
+
(form.getNumero1() + form.getNumero2()));
resultado
= success();
}
return
resultado;
}
public
Event validarPaso2YPrepararPaso3(RequestContext context)
throws
Exception {
AsistenteForm
form = (AsistenteForm) getFormObject(context);
form.setMensaje(«Fin
de la operación»);
return
success();
}
}
En el mismo vemos que es muy simple
acceder a los datos que se le muestran o que introduce el usuario con
la ayuda del form indicado en la configuración.
Definición de las páginas
Las páginas son muy simples. En
/pruebaWebFlowSimple/web/WEB-INF/jsp/paso1.jsp vemos:
<html
xmlns=«http://www.w3.org/1999/xhtml»
xml:lang=«en»
lang=«en»>
<head>
<title>Asistente
sumador</title>
</head>
<body>
<h1>Asistente
sumador: Paso 1</h1>
<form>
<input
type=«hidden»
name=«_flowId»
value=«asistente-flow»
/>
<input
type=«hidden»
name=«_eventId»
value=«siguiente»
/>
<input
type=«hidden»
name=«_flowExecutionKey»
value=«${flowExecutionKey}«
/>
<p>${form.mensaje}</p>
<p>Primer
número: <input
type=«text»
name=«numero1»
value=«${form.numero1}«/></p>
<p>Segundo
número: <input
type=«text»
name=«numero2»
value=«${form.numero2}«/></p>
<input
type=«submit»
value=«siguiente»
/>
</form>
</body>
</html>
Donde en los campos del formulario se
indican el identificador de flujo, de evento y la clave de ejecución
que utiliza WebFlow para mantener el estado de un usuario. En
/pruebaWebFlowSimple/web/WEB-INF/jsp/paso2.jsp ponemos:
<html
xmlns=«http://www.w3.org/1999/xhtml»
xml:lang=«en»
lang=«en»>
<head>
<title>Asistente
sumador</title>
</head>
<body>
<h1>Asistente
sumador: Paso 2</h1>
<form>
<input
type=«hidden»
name=«_flowId»
value=«asistente-flow»
/>
<input
type=«hidden»
name=«_eventId»
value=«siguiente»
/>
<input
type=«hidden»
name=«_flowExecutionKey»
value=«${flowExecutionKey}«
/>
<p>${form.mensaje}</p>
<input
type=«submit»
value=«siguiente»
/>
<input
type=«submit»
value=«anterior»
onclick=«document.forms[0]._eventId.value=’anterior’;return
true;»/>
</form>
</body>
</html>
Y en
/pruebaWebFlowSimple/web/WEB-INF/jsp/paso3.jsp:
<html
xmlns=«http://www.w3.org/1999/xhtml»
xml:lang=«en»
lang=«en»>
<head>
<title>Asistente
sumador</title>
</head>
<body>
<h1>Asistente
sumador: Paso 3</h1>
<form>
<p>${form.mensaje}</p>
</form>
</body>
</html>
Ejecutando la aplicación
Lanzamos el servidor con la URL
http://localhost:8080/pruebaWebFlowSimple/asistente.flow
y veremos en funcionamiento el ejemplo del asistente sumador.
Una mejora que podemos implementar es utilizar el envío del
formulario mediante Post, para conservar limpia la url del navegador
del usuario.
Algunos links interesantes
-
Sitio oficial de Spring,
http://www.springframework.org/webflow -
Documentación oficial de Referencia ,
http://static.springframework.org/spring-webflow/docs/1.0.x/reference/index.html -
Introducción de Ervacom,
http://www.ervacon.com/products/swf/intro/index.html
Conclusiones
Tras utilizarlo en proyectos de envergadura concluimos que:
-
El Spring WebFlow es un
Controlador que orienta a procesos el flujo de navegación,
que no se debe confundir con un BPM (Ver
http://en.wikipedia.org/wiki/Business_process_management)
ya que el proceso de negocio puede diferir con la navegación
de su interfaz de usuario, los BPM son independientes de las
tecnologías de interfaz de usuario y muchos etcéteras.. -
Lo vemos especialmente útil
en aplicaciones con flujos de mediana a gran complejidad cuyo
mantenimiento sea relativamente periódico. -
Permite generar documentación
actualizada del flujo de navegación mediante una simple XSD. -
Puede convivir con otros MVC como
el Spring MVC o Struts. -
Securiza la ejecución de
flujos, evitnado re-call y ataques deny-of-service o
cross-site-scripting.
Desde Autentia contamos con los
conocimientos y experiencia para ayudarle a sacar la máxima
ventaja de las tecnologías más innovadoras y mejorar la
calidad de sus desarrollos software.
No dude en contactarse con nosotros
mediante www.autentia.com .
Este ejemplo no ha funcionado. No se si haya otro manual en donde este corregido el ejemplo
Bien, como introducción, no está mal.
Desde mi punto de vista, el nivel que ofreces es demasiado alto. Aún a riesgo de meter la pata, voy a mojarme en la siguiente afirmación: \\\»El objetivo de un tutorial no es exhibicionarse, sino compartir información\\\». Cualquier conducta que se salga de este cometido es un error en la enseñanza.
Saludos,
serias tan amable de pasar applicationContext.xml