Présentation du Spring Web Flow

Exemple d'utilisation du Spring Web Flow

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Un processus de commande dans une e-boutique, une formulaire complexe d'enregistrement - quel est le point commun de ces deux éléments ? Les deux peuvent être composés de plusieurs étapes pendant lesquelles l'utilisateur peut effectuer les allers-retours différentes dans le but de modifier les données renseignées. Un des projets Spring facilite l'intégration de cette complexité dans le monde des applications web. Ce projet s'appelle Spring Web Flow. On l'emploiera pour définir un formulaire de plusieurs étapes grâce auquel l'utilisateur pourra s'inscrire au newsletter en précisant son adresse e-mail ainsi que ses préférences de réception.

Qu'est-ce que c'est Spring Web Flow ?

Il s'agit d'une extension proposée au Spring qui décompose une tâche en plusieurs étapes. Plusieurs points communs spécifiques à ce projet ressortent :
- le regroupement des flows; un flow est une tâche composée d'une série d'étapes que l'utilisateur doit accomplir pour considérer son action comme terminée. Par exemple, dans notre cas, le flow s'appellera "l'inscription au newsletter" et il sera terminé dès lors que l'utilisateur renseigne son adresse e-mail, indique ses préférences de réception et valide toutes les données.
- tous les flows ont un point de départ et un point d'arrivée déterminés
- l'utilisateur doit se déplacer entre les flows en un ordre spécifique
- les données ne sont pas soumises jusqu'à la dernière étape
- une fois la tâche terminée, il ne devrait pas être possible de la répéter par accident

Ce projet a pour but de répondre à la majorité des problématiques liées à la création des applications basées sur plusieurs étapes :
- éviter d'écrire tout le code pour sauvegarder les données dans la session
- faciliter la visualisation des flows
- renforcer le contrôle sur le processus
- résoudre le problème avec les données présentes après le click sur le button "retour" du navigateur et les données stockées réellement sur le serveur (il arrive que les données du côté de navigateur appartiennent au cache du navigateur et ne représentent pas les données qui se trouvent sur le serveur)
- éviter les problèmes d'accès concurrentiel dans le cas où plusieurs onglets d'un navigateur sont utilisés pour naviguer dans le processus

Comment intégrer Spring Web Flow au Spring ?

L'intégration du Spring Web Flow dans le Spring consiste d'abord à inclure le fichier de configuration de l'extension dans la liste des fichiers lus pour le contexte. Voici les deux beans importants du fichier de configuration des flows :

        <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter"> 
                <property name="flowExecutor" ref="flowExecutor" /> 
        </bean> 
        <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"> 
                <property name="flowRegistry" ref="flowRegistry" /> 
                <property name="order" value="0" /> 
        </bean> 

Le premier bean, FlowHandlerAdapter, permet au DispatcherServlet de savoir que certaines requêtes devront être traitées en tant que des flows. Le second, FlowHandlerMapping, relie les ressources de l'application à des flows. C'est grâce à lui que Spring va savoir quand un URL doit être géré par Sprng Web Flow et avec quelle configuration. Regardons maintenant les beans auxquels renvoient les propriétés des 2 beans présentés.

        <webflow:flow-executor id="flowExecutor"> 
                <webflow:flow-execution-listeners>
                    <webflow:listener ref="jpaFlowExecutionListener" />
                </webflow:flow-execution-listeners> 
        </webflow:flow-executor> 
        <webflow:flow-registry flow-builder-services="flowBuilderServices"  id="flowRegistry" base-path="/WEB-INF/flows/"> 
                <webflow:flow-location path="/newsletter-preferencies.xml" /> 
                <webflow:flow-location path="/newsletter-list.xml" /> 
                <webflow:flow-location path="/newsletter-summary.xml" />
        </webflow:flow-registry>
        <bean id="jpaFlowExecutionListener"  class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
            <constructor-arg ref="emf" />
            <constructor-arg ref="transactionManager" />
        </bean>
        <webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="viewFactoryCreator" />
        <bean id="viewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
	        <property name="viewResolvers" ref="viewResolver" />
        </bean>

Le flowExecutor est l'élément principal qui exécute les flows. Il enregistre ensuite des listeners et plus précisement un jpaFlowExecutionListener<. Grâce à lui, le flow peut interagir avec le fournisseur de persistence. Dans notre cas il s'agira de la classe org.springframework.webflow.persistence.JpaFlowExecutionListener qui prend 2 paramètres dans constructeur : une référence vers EntityManagerFactory et une autre vers TransactionManager. Pour savoir plus sur ces éléments, veuillez consulter l'article consacrée à la couche des données dans Spring.

Concernant la seconde référence, flowRegistry, il s'agit d'un registre qui contient les chemins vers les fichiers de configuration des flows. Il contient également une référence vers le bean flow-builder-services. Ce bean permet de définir un service qui sera invoqué à chaque fois qu'on créera des flows à partir des fichiers de registre. Dans notre cas, on personnalise view-factory-creator qui permettra de traduire l'attribut view des flows en vue adéquat (recherche par nom de modèle de vue et non pas par le fichier physique).

L'exemple d'utilisation du Spring Web Flow

Comment signaler au Spring de faire appel au Spring Web Flow ? Il suffit de donner l'adresse URL suivant : "/newsletter-preferencies?_eventId=credentials". Spring, grâce au paramètre _eventId sait qu'il s'agit de la vue avec l'identifiant "credentials" compris dans le fichier newsletter-preferencies.xml (première partie de l'URL). Voici le fichier en question :

afficher le code

Le fragment ci-dessus introduit beaucoup de nouvelles notions. On commencera par les balises connues par des gens qui utilisent HTML : <input />. Il s'agit des valeurs qui sont sauvegardées dans le conteneur flow. Grâce à cela, de différents flows peuvent utiliser ces valeurs. Cela s'avère particulièrement utile dans les situations où l'utilisateur retourne aux étapes précédentes du formulaire. Alors on peut lui pré-remplir les données qu'il a saisies pendant son premier passage sur ces pages.

L'attribut id de la balise <view-state /> correspond au paramètre _eventId utilisé dans le lien vers le premier flow. L'attribut view indique le nom du modèle de vue qui sera appelé pour afficher le flow. En ce qui concerne l'attribut model, il s'agit d'associer un modèle à la vue du flow. Cela permet non pas seulement un affichage plus simple (pour le constater, référez-vous à la partie consacrée à des formulaires dans Spring), mais aussi une validation.

La validation des modèles dans Spring Web Flow se base sur la méthode dont le nom est composé du mot validate et l'identifiant du <view-state />. En occurrence, le nom de la méthode de validation sera validateCredentials(ValidationContext context). La fonction prend en paramètre une instance ValidationContext grâce à laquelle est possible de gérer les messages d'erreur ou récupérer les informations sur l'utilisateur connecté.

Le premier enfant de la balise <view-state /> est <on-entry />. Il contient une expression exprimée en Spring Expression Language (SpEL) qui sera exécutée avant tout le traitement sur cette page. Dans notre cas, l'expression vérifie si l'on est un utilisateur connecté. Si c'est le cas, on passe l'instance de la classe UsernamePasswordAuthenticationToken dans le contrôleur du modèle afin de pré-remplir certains champs.

Le prochain enfant, <transition />, signale ce qui doit se passer en cliquant sur le bouton nommé _eventId_submit (correspond à l'attribut on="submit"). Grâce à l'attribut to, on indique le prochain <view-state /> qui doit s'afficher. L'attribut bind spécifie que l'association des données du formulaire au modèle doit être faite. Le dernier attribut, validate, détermine si les données du formulaire doivent être validées en fonction des règles spécifiées dans le validateur du modèle.

L'avant-dernier élément faisant partie de la structure de cette configuration est <subflow-state />. Grâce à lui, on peut lancer un autre flow en tant que sous-flow. Les attributs du <sublow-state /> permettent de lier l'appel défini dans <transition /> au sous-flow : id correspond à l'attribut to de la balise <transition /> tandis que subflow définit quel flow doit être appelé (en occurrence ce sera le flow placé dans le fichier newsletter-list.xml). Grâce à des <input /> précisés dans ce sous-flow, on peut placer des paramètres nommés email et preferencies dans le flow scope.

Le dernier élément du fichier s'appelle <end-state />. Il prend deux attributs : id et view. Ce dernier définit quelle vue doit être appelée si l'on se déplace à cette étape du processus. Le déplacement se fait via la relation entre les attributs to de l'élément <transition /> et id de l'<end-state />. Cette balise signale que le flow se termine et que toutes les données stockées dans flow scope seront perdues à un nouvel accès au processus.

Dans cet exemple on a également vu qu'on disposait des scopes différents. On peut également récupérer les éléments de la requête. Le premier cas de figure présent était conversationScope. Il représente toutes les variables partagées par toutes les flows. Ces variables sont initialisées quand le principal flow commence et détruites quand il se termine. Plus loin on remarque a présence du viewScope. Celui-là peut être utilisé uniquement dans <view-state /> et il est accessible uniquement pendant l'affichage de vue. D'autres scopes, non abordés dans cet exemple, existent :
- flashScope : il s'agit d'éléments qui sont initialisés au début du flow, supprimés à chaque affichage de vue et détruits à la fin du flow. Cela ressemble à des flash attributs présentés dans la partie consacrée à des contrôleurs dans Spring
- requestScope : il s'agit des variables qui sont définies au début du flow et détruites quand le flow retourne un résultat.
- flowScope : il stocke des éléments jusqu'à la fin d'un flow.

On l'a vu avec <input name="email" value="requestParameters.email" /> du sous-flow preferencies. La récupération des paramètres se fait donc avec l'expression requestParameters.NOM_DU_PARAMETRE.

Dans d'autres fichiers gardent les mêmes composants. C'est la raison pour laquelle on ne va pas les aborder dans cet article. Par contre, la chose qu'on va faire, est un schéma de fonctionnement des flows avec les balises correspondantes qui sont "appelées" au moment de transition.

Etape 1 : données de connexion (e-mail, mot de passe)

L'utilisateur saisit les informations qui vont lui servir à s'authentifier et, éventuellement, modifier les préférénces de son mail.

(1) ↓

Etape 2 : préférences de newsletter (catégories de livres, auteurs préférés...)

L'utilisateur peut (phase non obligatoire) spécifier les préférences des newsletters qu'il souhaitera à recevoir.

(2) ↓

Etape 3 : validation de la conformité des données

L'utilisateur peut, pour la dernière fois, consulter les informations renseignées sur les écrans précédents. Si elles lui paraissent correctes, il peut valider le formulaire. La validation provoquera l'inscription au newsletter. Dans le cas de non-validation, l'utilisateur peut retourner à l'étape dont les informations il souhaite modifier.

Voici les 3 scénarios que vous pouvez visualiser. La visualisation est représentée par les contenus des fichiers XML des flows :
- 1
- 2
- 3

Bartosz KONIECZNY Spring Web Flow

Une question ? Une remarque ?

*

*

Un conseil Zend Framework

Comment faciliter le travail entre notre application et l'Ajax ?

L'un des moyens est l'utilisation du ContextSwitch. Il permet de définir le type de réponse renvoyé.