Spring web flow - framework pro management toku web aplikace

6. April 2007

Dnes existuje spousta MVC frameworků, které vám dovedou zařídit skvělé (rozuměj flexibilní) routování. Světem webu dnes vládnou nice URL`s, přístupnost atd…V některých případech však požadujete něco zcela jiného. Například taková registrace uživatelů nebo odesílání objednávky. Takovéto procesy bývají rozděleny do několika kroků a bývá zcela klíčové zajistit, aby se uživatel do každého kroku procesu mohl dostat pouze námi definovanými cestami. Při odesílání objednáky z e-shopu je jeden z možných scénářů takovýto:

  1. klepnutí na tlačítko objednat
  2. ověření zda je uživatel přihlášený
    1. pokud je uživatel přihlášen jdeme dále
    2. pokud není přihlášen nabídneme přihlašovací fomulář (a zde opět mohou nastat dva stavy – uživatel již je nebo není zaregistrován)
  3. potvrzení objednávky
  4. objednávka je odeslána

Během tohoto procesu je nutné nějak ošetřit, že jsou nastaveny všechny potřebné údaje a jednoduše řešeno – systém je v takovém stavu v jakém ho potřebujeme mít. Můžeme zvolit svoje proprietární řešení a vše si ošetřovat například pomocí session nebo použít SWF. Pomocí SWF můžeme nadefinovat UI flows, a také jednotlivé stavy systému, možné přechody z jednoho stavu do druhého a akce a podmínky, které tento přechod provází. Pomocí SWF vlastně provádíme abstrakci našich UI flows a zvyšujeme tím jejich přenositelnost. Veškerá logika pro řízení toku je soustředěna na jednom místě a ne rozeseta na x místech kódu.

Pár střípků z architektury SWF

Asi nemá cenu si detailně popisovat celý SWF. Od toho je referenční příručka. Uvede zde pár základních faktů.

SWF lze integrovat do spousty stávajících frameworků (Struts, JSF, Spring MVC, Portlet MVC). SWF se skládá z těchto knihoven:

  • spring-webflow (jádro frameworku)
  • spring-core (různé helper a utility třídy používané jádřem systému)
  • spring-binding (Spring data binding framework)
  • commons-logging (asi není třeba představovat – logovací framework z dílny Jakarta)
  • OGNL (jazyk pro práci s objekty)

Flow lze definovat dvěma způsoby:

  • přímo v Java API
  • v XML souboru

Flow lze do sebe libovolně vnořovat. Slouží k tomu element subflow-state.

Lze vytvářet takzvané globální přechody, které lze použít kdykoliv. Definují se pomocí elementu global-transitions.

V rámci SWF si můžeme ukládat proměnné do několika různých scopů:

request
Mají platnost pouze pro daný požadavek.
flash
Platí dokud uživatel neopustí aktuální state.
flow
Platí pro celý flow.
conversation
Platí po dobu životnosti mateřského flow.

Použití základních elementů pro stavbu flow bude vysvětleno na příkladu dále v článku.

Jak SWF funguje?

Pouť UI flow se začíná ve start-state. Na další kroky procesu se můžete dostat dvěma způsoby:

  • klepnutím na odkaz
  • stisknutím tlačítka formuláře

V případě odkazu je nutné připojit do adresy požadavku speciální parametru s názvem _flowExecution­Key. Podle něj pak aplikace pozná o jaké flow se jedná a zajistí se tím stavovost aplikace. Dalším nutným parametrem je _eventId, což je id stavu, na který chceme přejít. Jednoduchý příklad:

<a href="<c:url value="/spring-web-flow-test.htm?_flowExecutionKey=${flowExecutionKey}&amp;_eventId=step2"/>">Pokračovat</a>

Pokud použijeme formulář bude kód analogický:

<form method="post" action="/spring-web-flow-test.htm">
...
<input type="hidden" name="_flowExecutionKey" value="<c:out value="${flowExecutionKey}" />" />
<input type="submit" name="_eventId_step2" value="Pokračovat" />
</form>

Za zmínku zde stojí pouze hodnota atributu name u odesílacího tlačítka formuláře – _eventId_step2. SWF si z něj „vydestiluje“ step2.

Pokud se pokusíte zmodifikovat hodnotu kteréhokoliv z těchto dvou parametrů SWF jednoduše vyhodí výjimku – buď nenajde probíhající flow s danou hodnotou parametru _flowExecutionKey a nebo nenajde přechod, který je definován pomocí zmodifikovaného parametru _eventId. SWF je také imunní pomocí pomocí kopírování URL. Po nějaké době dojde k expiraci flow s daným _flowExecutionKey a určitě tušíte, že pokud ho použijete dojde opět k vyhození výjimky.

Integrace se Spring Framework 1.2

<!-- Launches new flow executions and resumes existing executions: Spring 1.2 config version -->
<bean id="flowExecutor" class="org.springframework.webflow.config.FlowExecutorFactoryBean">
        <property name="definitionLocator" ref="flowRegistry" />
        <property name="executionAttributes">
                <map>
                        <entry key="alwaysRedirectOnPause">
                                <value type="java.lang.Boolean">false</value>
                        </entry>
                </map>
        </property>
        <property name="repositoryType" value="CONTINUATION" />
</bean>

<!-- Creates the registry of flow definitions for this application: Spring 1.2 config version -->
<bean id="flowRegistry" class="org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean">
        <property name="flowLocations">
                <list>
                        <value>/WEB-INF/flows/my-flow.xml</value>
                </list>
        </property>
</bean>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
                <list>
                        <ref bean="localeChangeInterceptor" />
                </list>
        </property>
        <property name="mappings">
                <props>
                        <prop key="/marketing/brands-assets/brands-assets.htm">
                                myFlowController
                        </prop>
                </props>
        </property>
</bean>

<bean name="myFlowController" class="org.springframework.webflow.executor.mvc.FlowController">
        <property name="flowExecutor" ref="flowExecutor" />
        <property name="defaultFlowId" value="my-flow" />
</bean>

Atribut alwaysRedirec­tOnPause definuje zda má při přechodu ze z jednoho stavu do druhé dojít k redirektu. Je vhodné jej nastavit na true v případě, že chceme zabránit opakovanému odesílání formulářů. Pomobí beany flowRegistry provedeme registraci nadefinovaných flow. Beana urlMapping ukazuje jakým na kterou URL je naše flow navázané. Beana myFlowController slouží pro definici vlastního controlleru. Důležitá je hodnota property defaultFlowId, která říká jaké flow se má použít.

Definice našeho flow – souboru my-flow.xml:

<?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-state idref="peopleOverview" />

        <view-state id="peopleOverview" view="people-overview">
                <render-actions>
                        <bean-action bean="peopleService" method="findAllPeople">
                                <method-result name="people" />
                        </bean-action>
                </render-actions>
                <transition on="select" to="setupFormCustom">
                        <action bean="peopleFormAction" method="selectPerson" />
                </transition>
        </view-state>

        <action-state id="setupFormCustom">
                <action bean="peopleFormAction" method="setupFormCustom" />
                <transition on="success" to="editPerson" />
        </action-state>

        <view-state id="editPerson" view="person-edit">
                <transition on="submit" to="savePerson">
                        <action bean="peopleFormAction" method="bindAndValidate" />
                </transition>
                <transition on="error" to="editPerson" />
        </view-state>

        <action-state id="savePerson">
                <action bean="peopleFormAction" method="savePerson" />
                <transition on="success" to="peopleOverview" />
        </action-state>
</flow>

Takto vypadá naše flow ve Spring IDE Web Flow Visualizeru.

Uvedené flow by se nechalo slovy vyjádřit asi takto: Na začátku se uživateli zobrazí stav peopleOverview, na kterém je seznam uživatelů. Model pro tuto stránku bude získán z třídy peopleService a její metody findAllPeople. peopleService je klasická springovská servisní třída. Tak vrací kolekci objektů typu Person. Takto kolekce bude co kontextu požadavku umístěna pod názvem people. Z této stránky bude možný jediný přechod a to na state s názvem setupFormCustom, přičemž ještě před přechodem se zavolá metoda selectPerson peopleFormAction, která uloží idečko vybraného člověka do flow kontextu. State setupFormCustom zavolá metodu setupFormCustom z třídy peopleFormAction, která provede nastavení nastavení formbacking objektu pro editaci. Při odesílání zeditovaných údajů se zavolá metoda bindAndValidate, která zavolá případný připojený validator. Pokud třída projde validací dojde k přechodu do stavu savePerson, kde se náš objekt uloží. Poté dojde k přesměrování zpět na úvodní stránce s přehledem uživatelů.

Prosím nehledejte na příkladu chybičky. V některých ohledech je přitažen za vlasy, avšak jako Hello World příklad myslím zcela vyhovuje. Tento příklad jsem zvolil abych ukázal SWF při editaci objektů. K tomu musíme využít třídu dědící z org.springfra­mework.webflow­.action.FormAc­tion.

Spoustu příkladů je přímo součásti distribuce SWF.

Kód třídy peopleFormAction vidíte zde:

package cz.vavru.spring.webflow.test.action;

import org.springframework.webflow.action.FormAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;

import cz.vavru.spring.webflow.test.PeopleService;
import cz.vavru.spring.webflow.test.business.vo.Person;

public class PeopleFormAction extends FormAction {
        private PeopleService peopleService;
        private Person person;

        public void setPeopleService(PeopleService peopleService) {
                this.peopleService = peopleService;
        }

        public CoopCountryGroupsFormAction() {
                setFormObjectName("form");
                setFormObjectClass(Person.class);
                setFormObjectScope(ScopeType.FLOW);
                setFormErrorsScope(ScopeType.FLOW);
        }

        public Event selectPerson(RequestContext context) throws Exception {
                int personId;
                try {
                        personId = Integer.parseInt(context.getRequestParameters().get("personId"));
                } catch (NumberFormatException ex) {
                        personId = 0;
                }
                context.getFlowScope().put("personId", new Integer(personId));

                return success();
        }

        public Event setupFormCustom(RequestContext context) throws Exception {
                int personId = Integer.parseInt(context.getFlowScope().get("personId"));
                if (personId > 0) {
                        person = peopleService.getPersonById(personId);
                } else {
                        person = new Person();
                }
                BeanUtils.copyProperties(person, (Person) getFormObject(context));
                return success();
        }

        public Event savePerson(RequestContext context) throws Exception {
                peopleService.saveOrUpdatePerson((Person) getFormObject(context));
                return success();
        }
}

Několik ukázek z real-life projektu

Pohled na Eclipse IDE s otevřeným projektem. Ve view Spring Beans vidíme seznam XML souborů z aplikačního kontextu Spring Frameworku a také seznam SWF souborů s definicí jednotlivých flows.

Zobrazení vybraného flow pomocí Spring IDE pluginu.

Ukázka možností nastavení vybraného view-state.

Závěr

Pokud programujete aplikaci, která obsahuje business procesy skládající se z několika kroků určitě použití SWF zvažte. Z vlastní praxe mohu říci, že se osvědčilo. A teď když Spring IDE obsahuje i Web Flow Visualizer to platí dvojnásob – graf, který produkuje je pro člověka znajícího UML intuitivní a hlavně mu bude rozumět i váš šéf (možná dokonce i zákazník). Ale abych jenom nechválil – SWF Visualizer má i své mušky. Z mých asi deseti vytvořených flow tři jednoduše neotevře. SWF má výborně zpracovanou referenční příručku.

Odkazy

Článek patří do kategorie: Java, Spring Framework

4 Komentářů Přidat komentář

  • 1. Ladislav Thon  |  11. April 2007 v 14.33

    Asi se sluší říct, že v SWF se nedefinují business procesy, ale UI flows, i když si můžou jedna k jedné odpovídat. Jinak hezký tutoriálek, k odkazům bych doplnil pěkný web zaměřený na SWF tak říkajíc od zdroje: <a>http://www­.ervacon.com/pro­ducts/swf/index­.html.

  • 2. Vlasta  |  11. April 2007 v 14.40

    [1] Máte samozřejmě pravdu. Nedostatek opravím.

  • 3. Zdenek  |  28. August 2007 v 13.49

    Zajimavy tutorial:
    http://www.sprin­gframework.or­g/…by-step.html

  • 4. 3asparagus&hellip  |  13. January 2022 v 2.53

    3questionable…

Přidat komentář

Povinné

Povinné, skryté

Security Image Povinné
Opište text z obrázku

Povolené HTML značky:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Odkazovat na tento článek  |  Přihlásit se k odběru těchto komentářů přes RSS Feed


Kalendář

April 2024
M T W T F S S
« Jan    
1234567
891011121314
15161718192021
22232425262728
2930  

Poslední články

Locations of visitors to this page