Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  1/2008  Pfeil  Java/J2EE/JEE
suche: 
Dieser Artikel richtet sich an Java-Entwickler und Architekten im Bereich Web-Anwendungen.

Glossar

MVC
Model View Controller. MVC ist ein Paradigma für Benutzeroberflächen, das die getrennte Behandlung von Daten, deren Darstellung und die darauf wirkenden Benutzeraktivitäten propagiert.
JSF
JavaServer Faces. JSF ist ein standardisiertes Framework zur Entwicklung von Web-Anwendungen.
JSF Phase Listener
Ein Phase Listener klinkt sich in den JSF-Lebenszyklus ein und kann vor und nach jeder Phase innerhalb des Lebenszyklusses Logik ausführen.
JUnit
Ein Open Source Framework zur Durchführung von automatisierten Tests von Java-Programmen.
Scope
Der Scope einer Bean definiert ihre Lebensdauer,
d. h. wie lange die Bean im Speicher gehalten wird, bevor sie zur Garbage Collection freigegeben wird.


Ablaufsteuerung von Anwendungsfällen innerhalb von Java-basierten Web-Anwendungen

Spring Web Flow

Mit dem Spring Web Flow Framework sollen Probleme, die bei der Entwicklung von Java-basierten Web-Anwendungen auftreten können, wie z. B. fehlende Unterstützung der Browser-Funktionalitäten, der Vergangenheit angehören. Im folgenden Artikel wird eine kurze Einführung in das Framework gegeben und aufgezeigt, ob Spring Web Flow diesem ehrgeizigen Ziel gerecht wird.

Was ist Spring Web Flow?

Spring Web Flow ist ein Framework für die Ablaufsteuerung von Anwendungsfällen innerhalb von Web-Anwendungen. Ein erklärtes Ziel ist die Unterstützung der Browser-Navigation, die in der Web-Entwicklung immer wieder zu Problemen führt. Das Framework übernimmt dabei die Navigation zwischen den einzelnen Views und stellt darüber hinaus einen zusätzlichen Scope Container zur Verfügung.

Spring Web Flow kann in den gängigsten Web-Frameworks, wie Struts, JSF, Portlet MVC oder Spring MVC, integriert werden.

Spring Web Flow implementiert im Kern einen finiten Zustandsautomaten, der auf definierte Anfangs- und Endzustände angewiesen ist. Der Einsatz von Spring Web Flow bietet sich daher für Anwendungsfälle an, in denen feste Abläufe existieren. Es sollte nicht in Bereichen eingesetzt werden, in denen der Benutzer frei navigieren kann. In diesem Fall sollte die Standardnavigation des überlagerten Web-Frameworks eingesetzt werden.

Spring Web Flow ersetzt somit nicht die vorhandene Navigation, sondern erweitert diese, indem es sich als zusätzlicher Controller in die bestehende Infrastruktur eingliedert.

Um zu verhindern, dass ein Request durch einen Reload erneut gesendet wird, setzt Spring Web Flow das Post+Redirect+Get Pattern ein. Ein Redirect ist eine Weiterleitung auf eine andere Webseite, wobei der eigentliche Request/Response-Zyklus zweigeteilt wird. Nach dem POST Request wird durch den Redirect ein erneuter Browser Request via GET angestoßen. Somit erhält der Client eine stabile URL, die keinen erneuten Datenversand im Falle eines Reload durch den Browser ausführt.

Flow

<flow xmlns="http://www.springframework.org/schema/webflow" .../>

    <start-state idref="startingState"/>

    <view-state id="startingState" view="/xyz.xhtml">
        <transition on="submit" to="actionState"/>
    </view-state>

    <action-state id="actionState">
        <bean-action bean="beanName" method="methodName">
            <method-arguments>
                <argument expression="flowScope.expression"/>
            </method-arguments>
        </bean-action>
            <transition on="failure" to="startingState"/>
            <transition on="success" to="decisionState"/>
    </action-state>

    <decision-state id="decisionState">
        <if test="${flowScope.bean.property}"
            then="endState"
            else="startingState"/>
    </decision-state>

    <end-state id="endState" view="/abc.xhtml" />
</flow>
Abb. 1: Definition eines Flows per XML.

Der Ablauf der logisch zusammenhängenden Views wird in einem so genannten Flow definiert. Ein Flow kann als XML-Datei oder wahlweise mittels der Java-API realisiert werden. Ein Flow besteht in der Regel aus mehreren Zuständen (innerhalb von Web Flow als States bezeichnet), die nacheinander und in Abhängigkeit von der jeweiligen Benutzerinteraktion durchlaufen werden. Ein Beispiel eines per XML definierten Flows ist in der Abbildung 1 genauer aufgezeigt.

Innerhalb eines Flows stehen verschiedene States zur Verfügung. Zunächst muss jeder Flow einen Start State und einen End State besitzen. Der Start State definiert den Einstiegspunkt und aktiviert den jeweiligen Flow. Dieser bleibt so lange aktiv, bis ein End State erreicht wird. Die Angabe eines End States ist zwar nicht zwingend erforderlich, ist aber dringend zu empfehlen, da die sonst entstehenden Endlos-Flows unnötig Hauptspeicher binden.

Web Flow kennt 5 verschiedene Arten von States:

Ein View State definiert den Einstiegspunkt für den Benutzer und repräsentiert die eigentlichen JSP- bzw. XHTML-Dateien. Nur innerhalb eines View States wird der Flow angehalten und dabei auf ein Event vom Benutzer gewartet.

Alle anderen States werden automatisch durchlaufen, wie z. B. die Action States, die für den Aufruf der Geschäftslogik zuständig sind. Auf die zugehörigen Action-Klassen gehen wir im späteren Verlauf noch ein.

Auch Kontrollstrukturen können innerhalb eines Flows durch einen so genannten Decision State definiert werden. Dabei wird eine Variable auf einen beliebigen Zustand abgefragt und entsprechend reagiert, d. h. auf den zugehörigen State verwiesen (siehe Abbildung 1).

Durch einen Subflow besteht die Möglichkeit, andere Flow-Definitionen innerhalb des aktuellen Flows aufzurufen. Auf Subflows wird im Folgenden noch genauer eingegangen.

Beim Erreichen des End States wird der aktive Flow beendet und alle zugehörigen Beans werden automatisch aus dem Speicher entfernt.

Transition

Der Übergang zwischen den einzelnen States wird durch so genannte Transitions realisiert. Eine Transition fängt den Event eines Views oder eines Action States ab und leitet auf den State weiter, der in dem to-Attribut angegeben wurde. Existiert innerhalb eines States keine passende Transition zu einem Event, so wird eine globale Transition gesucht und ausgeführt. Abbildung 1 stellt die Funktionsweise von Transitions schematisch dar. Hierbei fängt das Framework Events, die z. B. innerhalb eines Views oder Action States erzeugt wurden, ab und leitet durch die Transition auf den nächsten State weiter.

Action

Für die Implementierung von Action-Klassen, die u. a. dem Aufruf der Geschäftslogik dienen, stehen dem Entwickler bereits einige Klassen (z. B. FormAction, MultiAction usw.) zur Verfügung, von denen die eigentliche Action-Klasse abgeleitet werden kann. Alle diese Klassen implementieren das Interface org.springframework.webflow.execution.Action.

Die Methoden liefern innerhalb einer Action ein Objekt vom Typ Event zurück. Dieses wird vom Framework abgefangen und eine passende Transition wird für dieses Event gesucht. Anschließend wird durch die Transition der nächste State ausgewählt.

Nicht nur innerhalb eines Action States können Actions ausgeführt werden. Sie lassen sich auch als Entry oder End Action innerhalb eines beliebigen States oder z. B. als Render Action innerhalb eines View States einsetzen.

Scope

Wie bereits erwähnt, stellt Spring Web Flow einen eigenen Scope-Container zur Verfügung. Dieser erweitert die Web-Anwendung um die folgenden Scopes:

Es besteht die Möglichkeit, auch weiterhin die bekannten Scopes einer Web-Anwendung, wie beispielsweise Request und Session, zu verwenden. Der Request Scope sollte aber mit Bedacht eingesetzt werden, da der Lebenszyklus der zugehörigen Beans für eine fehlerfreie Unterstützung der Browser-Navigation zu kurz ist.

Der große Vorteil der neuen Scopes ist, dass diese nach Beendigung eines Flows automatisch gelöscht werden und nicht länger in der Session liegen.

Subflows

<subflow-state id="subflowState" flow="subflowID" >
    <attribute-mapper>
        <output-mapper>
            <output-attribute name="parameter1" scope="flow"/>
            <output-attribute name="parameter2" scope="flash"/>
        </output-mapper>
    </attribute-mapper>
    <transition on="success" to="stateX"/>
</subflow-state>
Abb. 2: Aufruf eines Subflows innerhalb eines anderen Flows.

Auch die Modularisierung von Flows in kleine Einheiten ist durch so genannte Subflows bzw. Inline-Flows ohne Weiteres möglich. Ein Subflow wird zunächst wie jede andere Flow-Definition erstellt. Der Unterschied zu einem normalen Flow liegt lediglich darin, dass der Subflow innerhalb eines Subflow States (siehe Abbildung 2) aufgerufen wird. Eine Flow-Definition kann beliebig viele Subflows enthalten, welche wiederum weitere Subflows aufrufen können.

Inline Flows werden im Gegensatz zu Subflows nicht in einer eigenen XML-Datei gekapselt, sondern direkt in dem Flow definiert, in dem sie aufgerufen werden. Dadurch ist dieser nur innerhalb des konkreten Flows sichtbar und wird nur dann eingesetzt, wenn der Flow zwingend zu dem Flow gehört, aus dem er aufgerufen wird, und nicht in einem anderen Flow verwendet werden darf.

Für den Datenaustausch zwischen Flow und Subflow gibt es Input- und Output-Mapper. Parameter können durch einen Input-Mapper an den Subflow übergeben und durch einen Output-Mapper auch wieder zurückgegeben werden.

Repository

Wie bereits erwähnt, setzt sich Spring Web Flow das Ziel, die Browser-Funktionalitäten zu unterstützen. Um diese Funktionalität zu gewährleisten, muss die Möglichkeit bestehen, den Zustand einer View zu speichern und wieder abzurufen. Hierfür steht ein so genanntes Repository zur Verfügung, welches die Zustände der einzelnen Views innerhalb eines Flows zwischenspeichert. Dadurch kann man den Zustand jeder View innerhalb eines Flows zu einem beliebigen Zeitpunkt reproduzieren. Nur durch so ein Repository ist es für Spring Web Flow möglich, die Browser-Navigation zu unterstützen. Spring Web Flow beinhaltet grundsätzlich die folgenden Repositories:

Die Repositories Continuation und Client enthalten die identische Funktionalität, nur mit dem Unterschied, dass bei einem Client Repository alle Informationen auf dem jeweiligen Client und nicht auf dem Server gehalten werden.

Die Repositories Simple und Single-Key benötigen zwar weit weniger Ressourcen als die vorherigen, besitzen aber dadurch keinen Support der Browser Buttons mehr.

Erst nach Erreichen des End States werden die zugehörigen Daten des Flows aus dem Repository wieder gelöscht. Demnach kann anschließend der Zustand einer View innerhalb des bereits beendeten Flows nicht mehr rekonstruiert werden.

Testen von Flows

public class MeineTestKlasse extends AbstractXmlFlowExecutionTests {
 protected FlowDefinitionResource getFlowDefinitionResource(){
 return createFlowDefinitionResource("/.../testflow-flow.xml");
 }

 public void testStartFlow(){
  ApplicationView view = applicationView(startFlow());
  assertActiveFlowEquals("testflow-flow");
  assertCurrentStateEquals("startingState");
  assertModelAttributeNotNull("attributeName", view);
  assertViewNameEquals("/test.xhtml", view);
 }

 public void testStateX(){
  testStartFlow();
  assertCurrentStateEquals("testStateX");
  MockParameterMap param = new MockParameterMap ();
  ApplicationView view=applicationView(signalEvent("submit", param));
  assertFlowExecutionEnded();
 }
}
Abb. 3: JUnit-Klasse zum Testen der Flow-Definition.

Auch das unter Entwicklern nicht sehr beliebte Testen der eigenen Anwendung wird innerhalb von Spring Web Flow durch die Integration von JUnit unterstützt. Als Beispiel wird ein JUnit-Test in Abbildung 3 genauer aufgezeigt.

Die JUnit-Tests werden dabei von der Klasse AbstractXmlFlowExecutionTests abgeleitet. Innerhalb der abgeleiteten Testklasse stehen nun diverse Methoden zum Testen des Flows zur Verfügung. Mit der Methode assertCurrentStateEquals kann z. B. kontrolliert werden, ob sich der Flow gerade in dem korrekten State befindet. Mit der Methode signalEvent wird ein User Event an den zu testenden Flow gesendet.

Bei der Erstellung eines JUnit-Tests muss die Methode getFlowDefinitionResource überschrieben werden. Innerhalb dieser Methode wird beispielsweise die konkrete Flow-Definition angegeben und es werden mögliche Subflows innerhalb des Tests registriert.

Exception Handling

Um Exceptions innerhalb eines Flows abzufangen, kann ein individueller Exception Handler entwickelt werden, der zwingend das Interface FlowExecutionExceptionHandler implementieren muss. Das Interface beinhaltet zwei Methoden, die vom Exception Handler implementiert werden müssen. Ob die übergebene Exception vom Handler verarbeitet werden kann, liefert die Methode handles zurück. Die Methode handle bearbeitet die Exception und liefert gegebenenfalls ein entsprechendes ViewSelection-Objekt zurück, welches den Namen der anzuzeigenden Fehlerseite enthält.

Dieser Exception Handler kann wahlweise direkt an ein View oder Action State gehängt oder global innerhalb eines Flows definiert werden. Er kann aber nur Exceptions behandeln, die innerhalb eines aktiven Flows aufgetreten sind. Alle anderen Exceptions werden durch Spring Web Flow an das darüber liegende MVC-Framework weitergeleitet.

Zusammenspiel mit JSF

Gerade bei der immer größer werdenden Beliebtheit von JSF ist der Einsatz von Spring Web Flow mit dieser Technologie interessant. Generell funktioniert die Zusammenarbeit wie bei den anderen MVC-Frameworks relativ einfach. Nur gibt es leider gerade in der Kombination mit JSF noch ein paar Schönheitsfehler.

Wie zuvor erwähnt werden alle Exceptions, die nicht durch einen Exception Handler bearbeitet werden können, an das übergeordnete Framework weitergeleitet. Bei JSF besteht das Problem, dass es keine Möglichkeit gibt, solche Exceptions abzufangen. Daher werden diese Exceptions direkt auf der Oberfläche ausgegeben. Das ist gerade bei der häufig auftretenden NoSuchConversationException sehr störend. Sie tritt auf, wenn ein Event innerhalb eines bereits beendeten Flows ausgelöst wird.

In diesem Fall könnte das Faces Servlet von JSF bei Bedarf erweitert werden. Innerhalb der Methode service könnte die Exception abgefangen und entsprechend behandelt werden.

Ein weiteres Problem ist, dass die Faces Messages durch die durchgeführten Redirects verloren gehen. Erst durch die Implementierung eines eigenen JSF-Phase-Listeners, der die Messages temporär in der Session zwischenspeichert, werden die Messages auf den jeweiligen Views wieder ausgegeben.

Ausblick

Mit Spring Web Flow 2.0, das im März 2008 veröffentlicht wird, werden einige neue Funktionalitäten den Weg ins Framework finden und einige bekannte „Macken“ ausgemerzt. Gerade die Integration in JSF soll durch das SpringFaces Projekt stark verbessert werden. Es soll unter anderem ein besseres Exception Handling besitzen. Auch das Vererben und Ableiten von Flow-Definitionen soll, genauso wie die Definition von Flows, in der neuen Version durch Annotations möglich sein.

Fazit

Der Einsatz von Spring Web Flow lohnt sich vornehmlich bei Web-Anwendungen mit logisch zusammenhängenden Dialogen, um eine Ablaufsteuerung der eigenen Anwendungsfälle zu erstellen. Für diesen Zweck stellt Spring dem Entwickler ein mächtiges Framework zur Seite, das sich sehr gut in die gängigsten Web-Frameworks integrieren lässt.

Nur in Kombination mit JSF sollte man ggf. auf das neue Release warten, da dieses höchstwahrscheinlich die Workarounds, die beim Exception Handling und den Faces Messages nötig sind, erspart.

Christian Wiesing (info@ordix.de).