Spring Web Flow: un’introduzione

Un’introduzione al framework Spring Web Flow che ci permette di definire un flusso di navigazione nella nostra applicazione web

Spring Web Flow

Fino ad oggi il pattern MVC è stato alla base della grande maggioranza delle web application sviluppate in ambito Java. Su questo pattern molti framework hanno costruito il loro successo, tra i quali Struts e successivamente SpringMVC. Questi framework si pongono come primo obiettivo quello di rendere modulari gli strati di Model, View e Controller.

Spesso però, durante lo sviluppo delle nostre applicazioni, ci siamo trovati di fronte alla gestione di un flusso applicativo, cioè un insieme di pagine che determinano il flusso della nostra applicazione. E’ proprio in questa problematica che s’inserisce un framework come Spring Web Flow.  SWF nasce da un’idea di Keith Donald e oggi è sviluppato dalla SpringSource, società che sviluppa l’intero portfolio di Spring.

Concetti base

Come accennato precedentemente, Spring Web Flow mira alla gestione dei flussi applicativi. Ma cos’è un flusso? Un flusso, che nella terminologia di SWF è chiamato Flow, consiste in una sequenza di passi che possono essere eseguiti in diversi contesti e possono essere riutilizzati e composti a piacimento.

Flusso di Spring Web Flow

Figura 1 – esempio di flusso applicativo

 

I passi che compongono il Flow vengono chiamati States, che possono essere del tipo: view-state, decision-state, subflow-state ed end-state. Lo state che generalmente viene più utilizzato è il view-state e corrisponde a una view che può generare eventi. Un evento, gestito dallo stesso state, dà il via a una Transition, che permette di passare dallo stato attuale del flusso a quello successivo. Durante l’esecuzione di un Flow è possibile definire delle azioni che possono essere eseguite in qualsiasi momento all’interno dello stesso e dei suoi state o transition. Queste sono:

On flow start: viene eseguita prima che il flusso abbia inizio (on-start)
On state entry: prima dell’entrata in uno state (on-entry)
On view render: prima del rendering della view (on-render)
On transition execution: prima che la transizione venga eseguita
On state exit: prima dell’uscita dallo state corrente (on-exit)
On flow end: prima che il flow corrente finisca (on-end)

Ma come si compone un Flow? Un flusso è normalmente gestito attraverso un file XML o per specifiche necessità anche attraverso le API messe a disposizione dal framework.

Scriviamo la prima applicazione

Iniziamo a sviluppare la prima, semplice, applicazione, utilizzando SWF integrato con SpringMVC (SWF può essere integrato anche con Struts, JSF, Portlet, etc.). L’applicazione si baserà sul flusso definito in figura1. Prima d’iniziare a scrivere il file per la definizione del flusso, è necessario procedere con la configurazione dell’applicazione, partendo da SpringMVC.

Estratto del web.xml:

Quanto definito nel web.xml, è semplicemente la configurazione di SpringMVC, che si occupa di gestire tutte le chiamate effettuate verso l’url pattern “/spring/*”, attraverso la servlet front controller messa a disposizione dal framework MVC. Le prime tracce di SWF, le troviamo nella seguente configurazione

mvc-config.xml:

Analizzando il file mvc-config.xml, la prima cosa che notiamo è la definizione di un bean con id “flowMappings” che fa riferimento alla classe org.springframework.web.servlet.handler.SimpleUrlHandlerMapping. Questa classe si occupa di mappare, tramite la property “mappings”, tutti gli URL e il corrispondente Flow Handler per gestire la richiesta di esecuzione di un flusso. La configurazione è del tipo PATH=HANDLER_BEAN_NAME. Nel nostro caso al path /library/bookCreation corrisponde il controller flowController, definito come:

Il FlowController è un adapter che dovrebbe essere sufficiente per gestire le richieste più comuni e che non hanno bisogno di gestioni mirate per l’esecuzione di un flow. Per esigenze specifiche, per cui le regole di default non sono più sufficienti, SWF ci mette a disposizione anche la possibilità di creare un proprio Flow Handler, che verrà poi configurato per uno specifico path.

Questo Flow Handler dovrà implementare org.springframework.webflow.mvc.servlet.FlowHandler, e anche in questo caso il framework ci facilita nell’implementazione di questa interfaccia grazie alla classe org.springframework.webflow.mvc.servlet.AbstractFlowHandler. Questa classe, implementa tutti i metodi dell’interfaccia lasciandoli vuoti, evitando in questo modo di dovere fare l’override di tutti i metodi dell’interfaccia FlowHandler, anche di quelli il cui comportamento di default è sufficiente. I metodi che è possibile implementare sono i seguenti:

  • getFlowId(HttpServletRequest) : per definire l’id del Flow da eseguire. Di default viene recuperato direttamente dall’URL, come ultimo elemento dell’URI, prima dei parametri. Se con le servlet questo metodo è del tutto facoltativo, con le portlet dev’essere implementato obbligatoriamente, poiché attualmente è l’unico modo per risalire all’id del flow che si vuole eseguire. Questa “limitazione” è dovuta al meccanismo dei portlet container che si occupano direttamente della gestione dell’url
  • createExecutionInputMap(HttpServletRequest) : per estrarre alcuni parametri dalla request e utilizzarli come parametri di input del flow. Per default tutti i parametri della request vengono utilizzati come parametri di input
  • handleExecutionOutcome: se si vuole effettuare una redirect ad una particolare risorsa alla fine dell’esecuzione di un flow
  • handleException: per un miglior controllo delle eccezioni non gestite all’interno del flow

 

Il FlowController, funge da adapter tra SpringMVC e il FlowExecutor. Il FlowExecutor è il vero e proprio engine dell’esecuzione dei flows. Questa interfaccia, la cui implementazione è nella classe org.springframework.webflow.executor.FlowExecutorImpl, contiene tutti i riferimenti per iniziare l’esecuzione di un nuovo flow (launchExecution()), per metterlo in pausa (createPausedResult()) o per riprenderne l’esecuzione (resumeExecution())

Di seguito il file di configurazione di Spring Web Flow

In questo file, oltre a trovare la definizione per la creazione di un FlowExecutor, creato direttamente dal framework e injected direttamente nel nostro FrontController, troviamo anche la configurazione per il org.springframework.webflow.definition.registry.FlowDefinitionRegistry, che funziona da memory repository per le definizioni dei flussi dell’applicazione. Oltre ai file di configurazione dei flussi, è possibile indicare l’attributo flow-builder-services per definire i servizi da utilizzare per la costruzione e l’esecuzione dei flows. E’ possibile configurare quattro tipi di servizi:

  • conversion-service
  • formatter-registry
  • expression-parser
  • view-factory-creator

 

Il view-factory-creator si occupa di creare view factories e di conseguenza il sottostrato ViewResolver e View per la risoluzione e il rendering del nostro view name. Nella nostra applicazione d’esempio, la visualizzazione delle pagine jsp e della loro composition è gestita da Tiles. Infine, di seguito, troviamo il file parent di tutte le configurazioni che verrà caricato, come parametro nel ServletContext, allo startup della nostra applicazione. In questo modo il framework, avrà accesso a tutte le configurazioni finora descritte.

web-application-config.xml:

 

Figura2 – Struttura dei file di configurazione dell’application Library SWF

Figura2 – Struttura dei file di configurazione dell’application Library SWF

File per la configurazione di Tiles:

 

Il Flow

Dopo aver esplorato le configurazioni preliminari, necessarie al funzionamento della nostra prima applicazione SWF, scendiamo nel dettaglio di una definizione di un flusso. Il flow utilizzato per la nostra applicazione (vedi figura1), si presenterà come la navigazione delle pagine mostrata in figura3.

Figura 3

Figura 3

Di seguito il flusso in formato xml (utilizzando SpringIDE è possibile definire un flusso anche graficamente, ma al momento della scrittura di questo articolo, la versione 2.x di Web Flow non è supportata)

bookCreation.xml:

addressCreation.xml:

In questa prima applicazione si è scelto di concentrarsi soprattutto sull’aspetto di Flow, senza occuparci del binding con un model, della validazione o di altri aspetti che verranno trattati in articoli successivi. Una caratteristica importante da sottolineare è che il flow addressCreation.xml non ha nessun legame con il flow bookCreation.xml, ed esso stesso può essere eseguito come flusso indipendente da altri o ancora una volta come sub-flow di un altro flusso.

Questo ci da un’idea di quanto possa essere conveniente creare dei flussi facilmente riusabili all’interno della nostra applicazione. Con la versione 2.x del framework è stata aggiunto il concetto di ereditarietà tra le configurazioni dei flussi, aumentando le possibilità di riuso. Tornando alla definizione del nostro flusso, iniziamo subito con il dire che lo > di figura1, a partire da SWF 2.0 (nella versione precedente esisteva un tag ) viene considerato il primo > del flusso, in questo caso “enterBookData”, se non indicato come attributo del tag .

Da qui ha inizio il nostro flow. L’attributo view sta ad indicare il nome della view che il sottostrato composto dalle classi di tipo ViewResolver e View, andranno a tradurre con il nome della pagina da visualizzare. In questo caso, avendo deciso di utilizzare Tiles per la composizione delle nostre jsp, il file delle definitions ci indica che la pagina da visualizzare è la layout.jsp, che a sua volta include la bookCreation.jsp. A questo punto il nostro FlowExecutor, dopo aver visualizzato la pagina indicata dall’attributo view, mette in pausa il flow in attesa del verificarsi di un evento

Come possiamo vedere da questo estratto di codice della nostra pagina jsp, l’evento viene eseguito dal submit di un form (ma è possibile farlo anche con un semplice href) e l’evento è indicato dal _eventId_save. A questo punto l’engine riprende l’esecuzione del flusso invocando il metodo resumeExecution() e dallo stato attuale, sull’evento save, effettua una transition verso il nuovo stato enterAuthorData

Il view-state enterAuthorData si comporta esattamente come il precedente. Quello che è interessante notare, invece, è lo stato a cui si arriva in seguito alla transition save dello state enterAuthorData. In questo caso ci troviamo di fronte a un >. Per questo tipo di state non viene definita una transition, ma il flusso viene indirizzato dal risultato della if. Nella nostra applicazione si è deciso di controllare il parametro che si trova nella request, popolata con i parametri della pagina authorCreation.jsp

Nel caso si scelga di acquisire anche i dati dell’indirizzo dell’autore, si procederà verso lo state enterAddressData altrimenti verso lo state confirmation

A questo punto rimangono da analizzare gli ultimi due state che troviamo nella nostra applicazione: sub flow-state ed end-state. Nel caso il parametro nella request addressInfo sia true, il FlowExecutor ci rimanderà allo state enterAddressData, che abbiamo deciso di rappresentare come sotto-flusso. In questo modo è possibile riutilizzarlo in qualsiasi flusso abbia bisogno dell’acquisizione di un indirizzo. E’ importante notare come l’annidamento dei sotto flussi sia libero, non ci sono pertanto limitazioni sul numero dei sub-flow nested.

L’esecuzione di un sub-flow avviene esattamente con avviene quella del flusso chiamante, a differenza che quando questo flusso raggiunge l’end-state il flusso padre riprende l’esecuzione esattamente nello state a cui era rimasto. Nel sub-flow è possibile anche definire dei parametri di input e/o di output per e dal sub flow, attraverso i tag input e output definiti rispettivamente nel flusso padre al momento dell’invocazione al sub-flow e nel end-state quando il sotto flusso termina l’esecuzione. Come appena accennato, quando unend-state viene raggiunto, il flusso in questione è terminato.

Nel caso di un flusso padre di tutti i flussi, siamo al termine del nostro flusso applicativo e tutte le variabili mantenute nel flowScope, il cui ciclo di vita è valido dallo start-state al end-state del flusso padre, vengono rimosse automaticamente dal framework, senza nessun intervento di codice da parte dello sviluppatore.

Conclusioni

In questo primo articolo, abbiamo visto quali sono le caratteristiche fondamentali di SWF e abbiamo lasciato fuori quelle che sono le sue vere potenzialità (persistenza, binding, validation, ajax, security, etc.). In definitiva SWF s’integra facilmente con tutti i framework basati sul pattern MVC e su quelli più recenti come JSF. Dalla versione 2 del framework, sono stati aggiunti due nuovi moduli, quali Spring Javascript (attualmente si basa solo sul framewok Dojo, ma sono in cantiere anche implementazioni per altri js frameworks) e Spring Faces che contiene il supporto di Spring per JSF.

Risorse

http://en.wikipedia.org/wiki/Model-view-controller
http://www.springframework.org/webflow
http://www.springframework.org/
http://tiles.apache.org/

Una risposta

  1. aprile 8, 2013

    […] Link al primo articolo della serie […]

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *