Requests in Ketten
Mit der aktuellen Version 1.3 haben die Entwickler dem Java-Framework Struts eine modulare Struktur und neue Fähigkeiten gegeben. Unter anderem brachte das eine flexiblere Request-Verarbeitung.
- Michael Albrecht
- Manfred Wolff
Struts (deutsch etwa „Verstrebugen“) ist ein Java-Framework des Apache-Projekts. Es implementiert das MVC-Paradigma (Model, View, Controller) für Webanwendungen. Als Model fungieren Java-Beans, JSP-Seiten übernehmen die Rolle von Views, und als Controller kommen das ActionServlet sowie Action-Klassen zum Einsatz [1]. Seit Juni 2003 und Version 1.1 gehört Struts zum inoffiziellen Standard der Websoftwareentwicklung mit Java, wie die Einbettung in viele konventionelle Plattformen bezeugt. Mit der Version 1.3 vom Dezember letzten Jahres erfolgte ein größeres Redesign. Im Folgenden geht es um einige der wichtigsten Neuerungen.
Struts bildet jetzt kein starres Framework mehr, sondern besteht aus einzelnen Subprojekten. Die wichtigsten sind das Action-Framework (das traditionelle Struts), das Shale-Framework und einige Erweiterungen wie das Tiles-Framework oder die Tag-Bibliothek. Das Action-Framework ist Request-orientiert und damit eher auf Prozesse gerichtet; der Ablauf besteht aus aneinandergereihten Aktionen. Shale hingegen ist ein komponentenbasiertes Framework auf der Basis von JSF (Java Server Faces), bei dem die Verarbeitung durch Views, Listener und Rules erfolgt.
Module etablieren Namespaces
Auf den ersten Blick sieht es so aus, als sei die Umbenennung von Struts in Struts Action Framework die einzige Änderung des ursprünglichen Framework. Nach dem Einbinden des neuen Jar stellt sich jedoch heraus, dass so spezielle Actions wie DispatchAction durch das Refactoring in ein eigenes Subprojekt, nämlich struts-extras, verlagert wurden. Dasselbe ist mit den Struts-eigenen Tag-Bibliotheken geschehen, deren Subprojekt heißt struts-tiles.
Module, in Version 1.2 eingeführt, helfen nicht nur, die Länge der Struts-Konfigurationsdatei im Zaum zu halten, sondern sind auch ein probates Mittel, Komponenten abzugrenzen, um so komponentenbasiert Software zu entwickeln. Jedes Modul bekommt seine eigene Konfigurationsdatei, die alle Bestandteile wie Form-Beans, Mappings, Ressourcen und sogar Datenbankzugriffe separat parametriert. Dieses Modul kann man als Namespace betrachten, sodass gleiche Bezeichner in verschiedenen Modulen vorkommen dürfen. Das hilft bei der Entwicklung in großen Teams. Die Module werden im Servlet Deployment-Descriptor (web.xml, s. Listing 1) konfiguriert.
Listing 1: Konfiguration von Modulen
Auszug aus der Modulkonfiguration in web.xml; config definiert immer das Default-Modul, config/[name] konfiguriert weitere Module.
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
de.gepackt.struts.action.GepacktActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/modul</param-name>
<param-value>/WEB-INF/struts-modul-config.xml</param-value>
</init-param>
</servlet>
Lange behinderte es den Einsatz von Modulen, dass Anwendungen explizit zwischen ihnen wechseln müssen, denn dafür waren abenteuerliche Konstruktionen nötig. Seit Struts 1.2 kennen einige Tags den Parameter module, zum Beispiel html:link, und Module lassen sich einfach nutzen.
Eine typische Struts-Webanwendung befasst sich im Wesentlichen mit dem Anlegen, Editieren und Löschen von Daten. Diese CRUD-Ereignisse (create, read, update, delete) haben immer dieselbe Struktur:
- Eine Action behandelt einen Event;
- zur Action gibt es eine korrespondierende Form;
- für Fehler ist eine Fehlerseite zuständig und
- nach erfolgter Action wird auf eine JSP (Java Server Page) verzweigt.
Statt für jeden Geschäftsprozess vier Zuordnungen zwischen Event und Action zu vereinbaren, kann man Wildcards verwenden:
<action
path="/edit*"
type="org.apache.struts.webapp.example. Edit{1}Action"
name="{1}Form"
scope="request"
validate="false">
<forward name="failure" path="/mainMenu.jsp"/>
<forward name="success" path="/{1}.jsp"/>
</action>
Struts ersetzt den Parameter {i} durch das i-te Vorkommen der Wildcard (*). Im Beispiel würde es etwa den Pfad /editCustomer zu type=“org.apache.struts.webapp.example.EditCustomerAction“ und name=“CustomerForm“ auflösen. So ist bei geeigneter Benennung für jeden Event-Typ nur noch ein Mapping in der Struts-Konfiguration erforderlich.
Chains ersetzen den Request-Prozessor
Seit Version 1.1 war das HerzstĂĽck des Struts-Controllers der Request-Prozessor, der die Anfragen abarbeitete. DafĂĽr rief er eine Reihe von Methoden auf, die jeweils einen Aspekt erledigten. FĂĽr die anwendungsspezifische Verarbeitung stellte der Prozessor einen Hook zur VerfĂĽgung. Dieses Vorgehen war jedoch zu inflexibel, weil der Hook nur an einer Stelle der Verarbeitung aufgerufen wurde.
Mit Struts-Chain verwandelt sich der Request-Processor in einen je nach Anforderung variabel gestaltbaren Baukasten. Grundlage hierfür ist das Pattern „Chain-Of-Resposibility“ [2], das die Entwickler im Commons-Chain-Framework (jakarta.apache.org/commons/commons-chain) implementierten. Dieses Teilprojekt gehört zur Sammlung der Jakarta-Commons-Projekte, die das Ziel haben, häufig genutzte Funktionen und Module in eigene Bibliotheken auszulagern und allen Projekten zur Verfügung zu stellen.
Beim Commons-Chain-Framework beschreibt eine XML-Datei die verschiedenen Aspekte der Request-Verarbeitung deklarativ. Die frĂĽher vom Request-Prozessor erledigten und direkt in Java implementierten notwendigen Schritte fĂĽhrt jetzt die Standard-Chain durch (Listing 2).
Listing 2: Der Requestprozessor mit Struts Chain
<chain name="process-action">
<command
className="org.apache.struts.chain.commands.servlet.SelectLocale"/>
<command
className="org.apache.struts.chain.commands.servlet.SetOriginalURI"/>
<command
className="org.apache.struts.chain.commands.servlet.RequestNoCache"/>
...
<command
className="org.apache.struts.chain.commands.servlet.ExecuteAction"/>
</chain>
Um das Chaining überhaupt benutzen zu können, muss man es per ActionServlet (immer noch der Frontmann unter den Struts-Servlets) konfigurieren. Diese Konfiguration geschieht wiederum in der Datei web.xml (s. Listing 3).
Listing 3: Konfiguration der Struts Chain
Zur Konfiguration des Chaining dienen Einträge in web.xml.
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml
</param-value>
</init-param>
... eventuell weitere Module ...
<init-param>
<param-name>chainConfig</param-name>
<param-value>org/apache/struts/tiles/chain-config.xml
</param-value>
</init-param>
.... weitere Optionen ...
</servlet>
Wie die Pfadangabe org/apache/struts/tiles/chain-config.xml dort vermuten lässt, führt dieser Parameter zu einer Struts-Standardkonfiguration der Chain, die auf Kompatibilität mit älteren Versionen ausgelegt ist. Die hier angegebenen Werte sind die Voreinstellung, die Struts beim Fehlen der Konfigurationseinträge ohnehin wählt.
Eigene SchlĂĽssel in der Konfiguration
Seit Struts 1.3 ist es möglich, die Konfiguration um eigene Schlüssel-Werte-Paare zu erweitern, die den entsprechenden Elementen (etwa action, message-resource, form-bean) zur Verfügung stehen. Damit lässt sich einerseits das Framework flexibler konfigurieren, andererseits ist eine Wiederverwendung solcher Konstrukte oftmals nur bedingt möglich. Dennoch soll das folgende Beispiel einen solchen Einsatz zeigen.
Angenommen, eine Action soll vom Request unabhängige Daten aus der Session lesen. Dies könnte beispielsweise die User-Information aus der HttpSession sein. Dazu kann man mit einem zusätzlichen Element in der Konfiguration den Namen des Session-Attributs beispielsweise frei konfigurierbar übergeben:
<action-mapping ...>
<action ...>
<set-property key="sessionUser" value="userInSession" />
</action>
</action-mapping>
Diesen Konfigurationsparameter kann die Action auslesen:
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// ...
String sessionAttributeName =
mapping.getProperty("sessionUser");
// Benutzer auslesen
User user =
request.getSession().getAttribute(sessionAttributeName);
//...
}
Solche erweiterten Konfigurationsparameter stehen für form-bean, form-property, exception, forward, action, controller und message-resources zur Verfügung. Sie sind ähnlich auch mit Wildcards einsetzbar.
Bei der Benutzung von Tiles kam es häufig vor, dass eine Exception nach dem Abschicken der Response an den Browser auftrat. In diesem Fall soll sie jedoch vielleicht keine Ausgabe bewirken. Handler lassen sich jetzt so implementieren, dass sie solche Exceptions ignorieren:
<exception
key="GlobalExceptionHandler.default"
type="java.lang.Exception"
path="/ErrorPage.jsp">
<set-property key="SILENT_IF_COMMITTED"
value="true" />
</exception>
extends vererbt Konfigurationen
Kataloge und Zuständigkeitsketten (chain of responsibility) lösen die Starrheit des Request-Prozessors auf. Konsequente Weiterentwicklung dieses Prozesses ist, dass (theoretisch) jedes Action-Mapping mit einem eigenen Katalog beziehungsweise mit einer eigenen Zustandskette abgearbeitet werden kann. Catalog und command sind Attribute, die man Action-Mappings und Controller-Tags hinzufügen kann. Sie erlauben die Benutzung eines Kommandos in einem Katalog.
Normalerweise wird der Standardkatalog struts mit dem Kommando servlet-standard abgearbeitet. Das ruft wiederum unter anderem die Prozesskette mit dem Namen process-action auf (s. Listing 2). Die XML-Datei für den Standardkatalog heißt in der Struts-Distribution chain-config.xml. Eigene Kataloge können kommasepariert in web.xml stehen:
<init-param>
<param-name>chainConfig</param-name>
<param-value>org/apache/struts/tiles/chain-config.xml,
de/interwall/projekt/self/meine-chain-config.xml
</param-value>
</init-param>
In einem Action-Mapping gewähren die Attribute catalog und command Zugriff auf die Kommandos in diesem Katalog. Ob dies jemand in der Praxis braucht, ist fraglich, aber hier ist das Struts-Framework jetzt zukunftssicher auch für die Anbindung anderer Verfahren.
extends vererbt Konfigurationen
Mit dem Attribut extends kann man Struts-Konfigurationen erweitern, indem es auf eine bestehende Konfiguration Bezug nimmt. Dies entspricht dem bei Tiles bekannten Muster. Im Beispiel (Listing 4) wird mit dynamischen Formularen gearbeitet. Sie sind vollständig in der Struts-Konfigurationsdatei vereinbart, und es muss keine separate Klasse angelegt werden. Das Formular Kunde erbt von Person die Attribute vorname und nachname.
Listing 4: Vererbung von Struts Konfigurationen
extends erlaubt die Nutzung vorhandener Konfigurationen und damit eine Art Vererbung.
<form-beans>
<form-bean name="Person"
type="org.apache.struts.action.DynaValidatorForm">
<form-property name="vorname" type="java.lang.String" />
<form-property name="nachname" type="java.lang.String"/>
</form-bean>
<form-bean name="Kunde"
type="org.apache.struts.action.DynaValidatorForm"
extends="Person">
<form-property name="kundennummer"
type="java.lang.String" />
</form-bean>
</form-beans>
Für die Migration von Struts 1.2 zu Struts 1.3 ist mindestens struts.jar durch die Bibliotheken struts-action.jar, struts-extras.jar, struts-taglib.jar und struts-tiles.jar zu ersetzen - selbst wenn Tiles nicht zum Einsatz kommen. Zusätzlich benötigt man die Bibliothek commons-chain.jar. Da sich häufiger etwas an den Tag-Bibliotheken ändert und sich teilweise Attribute in der Version 1.3 auch geändert haben, sollten die aktuellen Definitionen verwendet werden. Hierzu bietet es sich an, sie direkt aus der jar-Datei zu lesen. Die JSP-Direktive
<%@ taglib
uri="http://struts.apache.org/tags-logic"
prefix="logic" %>
lädt beispielsweise für eine Java Server Page die Definitionen der logic-Tiles-Bibliothek.
Nach dieser Minimalmigration benutzt Struts den Request-Prozessor default. Das ist in der Version 1.3 bereits der ComposableRequestProcessor. Die Verwendung des Request-Prozessors aus Version 1.2 muss man in der controller-Sektion von struts-config.xml explizit konfigurieren. Noch ein Tipp zur Migration: Die irritierende Fehlermeldung
org.apache.jasper.JasperException: Module 'null' not found.
resultiert häufig aus dem Fehlen der commons-chain-Library oder anderer Commons-Pakete.
Fazit
Zwei Fragen stehen bei der Auswahl von Frameworks aus Managementsicht im Mittelpunkt: Wie sieht es mit der Investitionssicherheit aus, und ist das Framework nicht bereits veraltet? Die erste Frage beantwortet die Entwicklung des Struts-Framework zu einem De-facto-Standard eindrucksvoll. Die Release-Zyklen von circa einem Jahr sind solide; das Framework entwickelt sich kontinuierlich weiter. Struts-Shale integriert moderne Techniken wie Java Server Faces in das Framework, sodass Struts auch in den kommenden Jahren „state of the art“ bleiben dürfte.
Michael Albrecht
ist Chefarchitekt bei der Interwall GmbH in Bremen.
Manfred Wolff
ist Interwall-Partner und arbeitet seit fast 10 Jahren freiberuflich als IT-Dienstleister.
Literatur
[1] Karl Rehrl; Webprogrammierung; Auf StĂĽtze; Jakarta Struts: Hilfe fĂĽr komplexe Webanwendungen; iX 11/2002, S 130
[2] Erich Gamma, Richard Helm, Ralph E. Johnson; Entwurfsmuster, Elemente Wiederverwendbarer objektorientierter Software, Addison-Wesley; MĂĽnchen 2004, ISBN: 3-8273-2199-9
iX-TRACT
- In den letzten fĂĽnf Jahren hat sich das Struts-Framework zu einem De-facto-Standard fĂĽr Webentwickler gemausert.
- Version 1.3 modularisiert das Framework und schafft so Namespaces fĂĽr Anwendungen.
- Statt eines monolitischen Prozessors verarbeitet eine flexible Kette von Aktionen die HTTP-Requests.
(ck)