Ofentürchen

Wicket – ein neuer Stern am Webframework-Himmel?

René Preißel und Markus Wittwer

Wieder einmal ein neuer Stern am Himmel der Web-Frameworks! Lohnt es sich jedoch, dieses eine unter den vielen Web-Frameworks zu nutzen? In 2006 wurde ja auch Pluto der Status als Planet aberkannt. Warum wir glauben, dass Wicket nicht Plutos Schicksal teilen wird, erläutert dieser Artikel.

Am 20. Juni wurde das Wicket-Projekt zu einem Top-Level-Projekt der Apache Software Foundation erhoben. Wicket wird als Open-Source-Projekt seit 2004 entwickelt und ist unter der Apache Licence 2.0 verfügbar. Viele Web-Frameworks erfordern, dass Entwickler mehrere Sprachen und Konzepte beherrschen. Für gängige Frameworks, wie z.B. Struts2 [2], JSF und Spring MVC, sind Kenntnisse in HTML, XML, JSP, die Erstellung von Custom Tags und natürlich die Beherrschung von Java selbst erforderlich. Wicket unterscheidet sich hier durch Reduktion: Für eine Wicket-Anwendung ist nur HTML- und Java-Wissen erforderlich.

Wicket ermöglicht die oft versprochene Trennung der Verantwortlichkeiten zwischen HTML-Designern und Entwicklern. Die Verbindung zwischen den Wicket-Komponenten in Java und den entsprechenden HTML Tags wird über einen eigenen XHTML-Namensraum für Wicket bereitgestellt. HTML-Seiten sind daher mit allen gängigen Editoren bearbeitbar und präsentierbar. Bei den nicht vermeidbaren Abhängigkeiten zwischen dem UI-Design einer Seite und ihrem Inhalt ist die Richtung der Abhängigkeiten klar: von Java zu HTML.

Um beispielsweise eine wechselnde Einfärbung von Tabellenzeilen zu realisieren, werden in Wicket css-Styles in Java-Klassen referenziert. Serverseitig werden in Wicket Webseiten und UI-Interaktionselemente als Java-Klassen implementiert. Die UI-Ereignisse werden über Callback-Methoden in der Art von Swing behandelt. Wer Swing kennt, wird sich mit Wicket schnell zurechtfinden. Eine weitere Motivation für die Entwicklung von Wicket war der Wunsch nach einer automatischen Verwaltung des Serverzustands. Jede Wicket-Komponente besitzt ihr eigenes Modell – ein POJO. Die Verwaltung der POJO-Zustände wird von Wicket übernommen, ein explizites Abspeichern von Beans in einem Session-Kontext ist nicht mehr erforderlich. Auf diese Weise kann Wicket auch für das bekannte „Back-Button“-Problem Unterstützung bieten. Im Folgenden stellen wir die Architektur von Wicket vor, zeigen die Merkmale anhand eines Beispielprojekts mit zwei Iterationen und schließen den Artikel mit einem Fazit. Wir nutzen dazu Wicket 1.3.0beta.

Architektur

Jede Wicket WebPagebesteht aus hierarchisch angeordneten Komponenten (gebaut nach dem Kompositum-Muster [Erich Gamma et al.: Design Patterns. Addison-Wesley Professional, 1994]). Einfache Komponenten sind direkt mit HTML Tags verbunden, Wicket WebPagesund komplexen Komponenten (z.B. Panels) sind eigene HTML-Dateien zugeordnet (Abb. 1).

Abb. 1: Wicket-Architektur

Ruft der Nutzer einen von Wicket verwalteten URL auf, landet der Request bei dem WicketServletFilter. Dieser übernimmt die folgenden Aufgaben:

Wicket löst den URL auf und bestimmt die gesuchte Ressource. Falls diese eine Wicket WebPage ist, folgen die nächsten Schritte. Wicket kann automatisch Request-Parameter extrahieren und diese an das zugehörige Modell übergeben. Dabei übernimmt es Typ-Konvertierungen und ruft einen Callback für Validierungen auf. Dann werden die Event Listener der Komponenten aufgerufen. Die Event Handler werden üblicherweise durch anonyme innere Klassen definiert – auch hier werden Java-Standard-Sprachmittel geschickt genutzt, anstatt auf andere Sprachen auszuweichen. Die Event Handler führen die notwendige Business-Logik aus und entscheiden, welche Seite als nächstes angezeigt wird. Das zugehörige WebPage-Objekt der Seite erzeugt für die enthaltenen Komponenten die Ausgabe. Dabei werden die zugehörigen HTML-Dateien genutzt und alle HTML Tags mit einer Wicket-Id durch den generierten Markup der entsprechenden Wicket-Komponente ersetzt.

Das Projekt „Umfrageanwendung“

Unsere Beispielanwendung soll es ermöglichen, verschiedenste Umfragen durchzuführen. Dabei wird dem Anwender eine beliebige Anzahl von Fragen präsentiert. Die Fragen können unterschiedlichen Typs sein, z.B. Ja-Nein-Fragen, Wertebereichs-Fragen und Fragen, die eine Texteingabe erfordern (Abb. 2).

Abb. 2: Frage-Maske

In der Zukunft sind weitere Fragetypen denkbar, und unsere Architektur soll diese Erweiterungen möglichst einfach unterstützen. Am Ende bekommt der Anwender seine Antworten erneut präsentiert und kann diese bestätigen oder zur Frageseite zurückkehren (Abb. 3).

Abb. 3: Ergebnis-Maske
Erste Iteration – Wicket zeigt sich

In der ersten Iteration wollen wir uns mit den Grundkonzepten von Wicket vertraut machen: Formulareingaben und Seitennavigation. Aufgabenstellung ist, auf der Einstiegsseite (Umfrageseite) eine Texteingabe entgegenzunehmen und auf der zweiten Webseite (Ergebnisseite) die Eingabe anzuzeigen. Weiterhin soll der Rücksprung zur Eingabe möglich sein.

Webseiten – Trennung von Verantwortlichkeiten

Jede Wicket-Seite besteht aus einer Java-Klasse und einer gleichnamigen HTML-Datei. Normale HTML Tags werden durch ein spezielles Attribut (wicket:id) als durch Wicket verwaltete Komponente gekennzeichnet (Listing 1).

Listing 1
----------------------------------------------------------------

   Fragen

Fragen

Bezeichner
Komponenten und Modelle

In der zugehörigen Java-Klasse werden die HTML Tags über die Wicket-Ids mit den passenden Wicket-Java-Komponenten verbunden. Die notwendigen Daten für das HTML Markup bekommen die Wicket-Komponenten aus einem zugeordneten Modellobjekt (MVC-Muster). Modelle abstrahieren den Lese- und Schreibzugriff auf die benötigten Daten. Die einfachste Modell-Implementierung (Model) ist ein einfacher Container für die benötigten Daten und reicht diese nur an die Wicket-Komponente weiter. Ein PropertyModel ermöglicht den direkten Zugriff auf ein Attribut eines beliebigen Objekts. Die zugehörige Get- und Set-Methode des Attributs wird durch das PropertyModel aufgerufen, wenn die Wicket-Komponente Daten lesen oder schreiben muss. Für unsere Umfrageseite benötigen wir eine Formular-Komponente (Form), eine Text-Komponente (Label) und ein Eingabefeld (TextField). Die Komponenten bilden eine Baumstruktur, die der HTML-Struktur entsprechen muss. Als Wurzel der Hierarchie dient die Umfrageseite, unter dieser liegt das Formular und darunter das Text- und das Eingabefeld. Das Eingabefeld wird durch ein PropertyModel mit einem Attribut (wert) der Seitenklasse verbunden. Um auf die Eingabe der Formulardaten zu reagieren, muss in der Formular-Komponente die onSubmit()-Methode überschrieben werden (anonyme Klasse, siehe Listing 2).

Seitennavigation

Wie erfolgt nun die Navigation zur Ergebnisseite? Webseiten sind in Wicket normale Objekte, d.h. zwischen Seiten wird einfach durch Verweisen auf ein anderes Seitenobjekt navigiert. Dazu dient die Methode setResponsePage(). Als Parameter erwartet diese entweder das Class-Objekt einer Wicket-Seite oder eine konkrete Seiteninstanz. Für unsere Anwendung erzeugen wir eine neue Instanz der Ergebnisseite und übergeben dieser eine Referenz auf die aktuelle Umfrageseite. Über diesen Verweis kann sich die Ergebnisseite den eingegebenen Wert geben lassen (getWert()), und wir haben auch gleich eine Möglichkeit, den Rücksprung zur Umfrageseite durchzuführen (Listing 2).

Listing 2
--------------------------------------------------------------------
import org.apache.wicket.markup.html.WebPage;
.

public class UmfragePage extends WebPage{
   private String wert;	
   public String getWert() {
  return wert;
   }
   public void setWert(String wert) {
  this.wert = wert;
   }
   public UmfragePage() {
  Label bezeichnerLabel=new Label("bezeichner",
          new Model("Alter"));
  TextField wertField=new TextField("wert",
          new PropertyModel(this,"wert"));
  Form textEingabeForm=new Form("textEingabe") {
     protected void onSubmit() {
    setResponsePage(new ZusammenfassungPage(UmfragePage.this));
     }
  };
  textEingabeForm.add(bezeichnerLabel);
  textEingabeForm.add(wertField);		
  add(textEingabeForm);
   }
}
UmfragePage.java
Seiten mit Zustand

Die Ergebnisseite besteht genau wie die Umfrageseite aus einer HTML-Datei und einer Java-Klasse. Für den Rücksprung benötigen wir einen Link, der im HTML ganz normal mit <a href=““/> angegeben wird. Jedoch brauchen wir keinen URL anzugeben, da dieser durch die verbundene Java-Link-Komponente eingefügt wird (Listing 3).

Listing 3
--------------------------------------------------------------

   
  Ergebnis

Antworten

Eingegebener Wert: Wert
Ändern

Die zugehörige Java-Klasse erzeugt eine Link-Komponente, die durch Überschreiben der Ereignismethode (onClick()) auf das Selektieren des Links reagieren kann. In dieser navigieren wir wieder zur ursprünglichen Umfrageseite. Da Wicket-Seiten normale Objekte sind, haben diese auch einen Zustand. Das heißt in unseren Fall, dass die Umfrageseite automatisch den vormals eingegebenen Wert des Anwenders enthält (Listing 4).

Listing 4
----------------------------------------------------------------
public class ErgebnisPage extends WebPage {

   public ErgebnisPage(UmfragePage umfragePage) {
  Label wertLabel=new Label("wert",
          new PropertyModel(umfragePage,"wert"));
  add(wertLabel);
  Link aendernLink=new Link("aendern") {
    @Override
    public void onClick() {
      setResponsePage(umfragePage);				
  }
  };
  add(aendernLink);
   }
}
ErgebnisPage.java
Wicket-Konfiguration

Jetzt müssen wir noch unsere Wicket-Anwendung konfigurieren. Der Einstiegspunkt für jede Wicket-Anwendung ist die Application-Klasse. Sie dient unter anderen dazu, die Einstiegsseite (Homepage) unserer Anwendung festzulegen. Die Application-Klasse wird als Konfigurationsparameter dem Wicket-Filter (ehemals Servlet) im Standard-Web-Deskriptor (web.xml) mitgegeben (Listing 5). Damit beenden wir die erste Iteration unserer Anwendung.

Listing 5
------------------------------------------------------------------
public class UmfrageApplication extends WebApplication{
  
  public Class getHomePage() {
    return UmfragePage.class;
  }
}
UmfrageApplication.java
Zweite Iteration – Wicket zeigt seine Stärken

In der zweiten Iteration wollen wir unsere bisherige Lösung an die eigentliche Aufgabenstellung anpassen. Dazu führen wir ein einfaches Domänenmodell für Fragen und Antworten ein und zeigen, wie einfach es ist, mit Wicket eigene Komponenten zu entwickeln.

Fragen und Antworten

Als Domänenobjekte (Abb. 4) für unsere Anwendung benötigen wir Fragen und Antworten. Um auf zukünftige Erweiterungen vorbereitet zu sein, definieren wir für Fragen und Antworten jeweils eine Schnittstelle. Die konkreten Implementierungen enthalten den Fragetext und die notwendigen Informationen für die Antworten (z.B. den Wert-Bezeichner und den Wertebereich).

Abb. 4: Domänenmodell

Die Verwaltung der Fragen- und Antworten-Objekte wird in diesem Beispiel bewusst einfach gehalten; auf Persistenz wird vollständig verzichtet. Der zentrale Anker für globale Anwendungsdaten ist die Application-Klasse. Auf diese können alle Webseiten zugreifen. In unserer Application-Klasse definieren wir eine Liste von Fragen, die dem Anwender zufällig angeboten werden soll (Listing 6).

Listing 6
-----------------------------------------------------------------
public class UmfrageApplication extends WebApplication{
  
   private List fragen=new ArrayList();
   protected void init() {
    initFragen();
   }
   private void initFragen() {
fragen.add(new JaNeinFrage("Finden Sie den Artikel gut?",true));
fragen.add(new VonBisFrage("Wie bewerten Sie den Artikel?",0,5,3));
fragen.add(new WerteingabeFrage("Ihr Lieblings-Framework?","Name","Wicket"));
	...
   }
   public List getSpannendeFragen(int anzahl) {
      ...
   }
   ...
}
UmfrageApplication.java
Das Projekt „Umfrageanwendung“

Wie wir bereits in der ersten Iteration gesehen haben, ist Wicket komponentenorientiert. Bisher haben wir nur sehr einfache Komponenten betrachtet, wie Textfelder und Links, die sich direkt auf HTML-Komponenten abbilden lassen. Komplexere Komponenten, die wiederum aus Subkomponenten bestehen, können mit Wicket-Panels erstellt werden. Panels bestehen wie Webseiten aus einer Java-Klasse und einer gleichnamigen HTML-Datei. In der HTML-Datei wird das HTML-Fragment definiert, welches in die übergeordnete Webseite eingebettet werden soll. Dabei wird nur der Teil übernommen, der durch den <wicket:panel> Tag eingeschlossen ist (Listing 7).

Listing 7
-------------------------------------------------------------------

Panel
Das ist die Frage
Label

Die zugehörige Java-Klasse erinnert stark an eine Seiten-Klasse. Da ein Panel allerdings eine Komponente ist, wird ihm eine Id und ein Modellobjekt übergeben. In unserem Fall gehen wir davon aus, dass im übergebenen Modell ein Objekt vom Typ WerteingabeAntwort verpackt ist. Die Panels für die anderen Fragetypen sehen äquivalent aus (Listing 8).

Listing 8
--------------------------------------------------------------------
public class WerteingabeAntwortPanel extends Panel {	
   public WerteingabeAntwortPanel(String id, IModel model) {
  super(id, model);
  Label frageLabel=new Label("frage", getAntwortModel().getFrage().getText());
  add(frageLabel);
		
  Label bezeichnerLabel=new Label("bezeichner", 
  getAntwortModel().getFrage().getBezeichner());
  add(bezeichnerLabel);
		
  TextField eingabeField=new TextField("eingabe",
    new PropertyModel(getAntwortModel(),"antwort"));
  add(eingabeField);
   }
	
   public WerteingabeAntwort getAntwortModel() {
	return (WerteingabeAntwort) getModel().getObject(this);
   }
}
WerteingabeAntwortPanel.java
Das Projekt „Umfrageanwendung“

Jetzt müssen wir noch auf der Umfrageseite die verschiedenen Antwort-Panels benutzen. Wir wollen, basierend auf einer zufälligen Frageliste, das jeweils richtige Panel erzeugen. Für die dynamische Anzeige von HTML-Listen existiert in Wicket eine ListView-Komponente. Diese lässt sich mit einer HTML-Tabelle (<tr>) verknüpfen. Einem ListView wird eine Liste von Objekten übergeben, und es erzeugt dann für jedes Listenelement eine neue Tabellenzeile. Der Inhalt der Zeile kann wiederum aus Wicket-Komponenten bestehen, die dann für jedes Listenelement dynamisch gefüllt werden können. In unserem Beispiel befindet sich in der Tabellenzeile nur eine Wicket-Komponente, das jeweils passende Antwort-Panel (Listing 9).

Listing 9
----------------------------------------------------------------------

  Fragen

Fragen


Panel für die Frage/Antwort

UmfragePage.html

In der zugehörigen Java-Klasse wird der ListView mit einer Liste von Antwortobjekten erzeugt. Dann wird Wicket-typisch in einer anonymen Klasse die Methode populateItem() überschrieben. In dieser Methode wird der Inhalt der einzelnen Zeilen dynamisch angepasst, d.h. für jede Frage ein entsprechendes Antwort-Panel erzeugt (Listing 10).

Listing 10
----------------------------------------------------------------------
public UmfragePage() {
   List fragen=getQuestionApplication().getSpannendeFragen(5);
		
   final List antworten=new ArrayList();
   for (Frage frage : fragen) {
      antworten.add(frage.createAntwort());
   }
   
   ListView list=new ListView("antwortenListe",antworten) {
   protected void populateItem(ListItem item) {
     Antwort antwort=(Antwort) item.getModelObject();
     Panel panel= createAntwortPanel("antwortPanel",antwort);
     item.add(panel);
  }
   };
   list.setReuseItems(true);
		
   Form form = new Form("antwortenForm") {
      protected void onSubmit() {
         setResponsePage(new ErgebnisPage(antworten, UmfragePage.this));
  }
   };
   form.add(list);
   add(form);
}
UmfragePage.java

Beim Absenden des Formulars (onSubmit()) wird zur Ergebnisseite navigiert und dabei die Liste der Antwortobjekte übergeben. Durch das durchgängige Modellkonzept von Wicket sind die Antwortobjekte „automatisch“ mit den Eingaben der jeweiligen Panels gefüllt. Auf der Ergebnisseite benutzen wir auch die gezeigten ListView-Komponenten, um unsere Antworten anzuzeigen. Damit haben wir unser kleines Projekt abgeschlossen. Den vollständigen Quelltext finden Sie unter www.oose.de/wicket.

Das Projekt „Umfrageanwendung“

Für Wicket als komponenten- und ereignisorientiertes Framework gilt:

  • Webseiten lassen sich leicht aus einzelnen Teilen dynamisch zusammensetzen, die Entwicklung eigener Komponenten ist einfach möglich.
  • Ereignisse lassen sich direkt an HTML- bzw. Java-Komponenten koppeln und dezentral behandeln (im Vergleich zum direkten Arbeiten mit dem Http-Request-Response-Zyklus).
  • AJAX (siehe Kasten) lässt sich auf Komponentenebene leicht integrieren.
Wicket und AJAX
Wicket liefert eine eigene
AJAX-Integration mit, arbeitet aber auch mit anderen AJAX‑Bibliotheken, wie beispielsweise Prototype, zusammen.
Die eingebaute AJAX-Bibliothek ermöglicht es dem Entwickler, Browser-Events abzufangen und serverseitige Ereignismethoden aufzurufen. In diesen Methoden werden die geänderten Komponenten angegeben, die dann im Browser aktualisiert werden. Die AJAX-Integration verlangt von dem Entwickler keine einzige Zeile JavaScript (Listing 11).
Listing 2
--------------------------------------------------------------------
import org.apache.wicket.markup.html.WebPage;
.

public class UmfragePage extends WebPage{
   private String wert;	
   public String getWert() {
  return wert;
   }
   public void setWert(String wert) {
  this.wert = wert;
   }
   public UmfragePage() {
  Label bezeichnerLabel=new Label("bezeichner",
          new Model("Alter"));
  TextField wertField=new TextField("wert",
          new PropertyModel(this,"wert"));
  Form textEingabeForm=new Form("textEingabe") {
     protected void onSubmit() {
    setResponsePage(new ZusammenfassungPage(UmfragePage.this));
     }
  };
  textEingabeForm.add(bezeichnerLabel);
  textEingabeForm.add(wertField);		
  add(textEingabeForm);
   }
}
UmfragePage.java

Damit bietet sich Wicket für moderne Anwendungen an, die eine komplexe UI-Logik haben und sich wie Desktop-Anwendungen dynamisch aus wiederverwendbaren Komponenten zusammensetzen. Die dezentrale Verarbeitung von Ereignissen kann den Nachteil haben, dass der Dialogfluss im Java-Code versteckt ist – im Gegensatz zu einer expliziten externen Spezifikation der Zustandsübergänge wie z.B. bei Spring Webflow. Des Weiteren fallen die „hässlichen“ URLs bei zustandsbehafteten Seiten auf – diese enthalten technische Informationen und sind nicht fachlich motiviert. Die automatische Zustandsverwaltung von Wicket erhöht die Entwicklungsgeschwindigkeit deutlich. Dabei benötigt es allerdings mehr Speicher auf dem Server und erhöht den Aufwand für die Replikation des Zustands im Cluster-Betrieb. Für viele Anwendungen ist dies jedoch vernachlässigbar.

Im Vergleich zu JSF trennt Wicket strikt zwischen HTML Markup und Code. Damit wird das Webdesign von der Java-Entwicklung entkoppelt. Trotzdem lassen sich HTML-Seiten wie Java-Artefakte behandeln. Mechanismen wie Internationalisierung, Paketierung und Vererbung werden dadurch auf HTML-Seiten und -Fragmente übertragen. Wicket kommt ohne XML-Konfiguration aus und nutzt ausschließlich die Sprache Java. Daher sind auch Refactorings kein Problem. Allerdings ist die Syntax – insbesondere die anonymen inneren Klassen – im Vergleich zu Blöcken bzw. Closures in Script-Sprachen wie Groovy oder JRuby schlecht lesbar und geschwätzig.

Wicket ist sicherlich kein einfach zu erlernendes Framework – insbesondere MVC-geprägte Webentwickler müssen sich umgewöhnen. Als Belohnung winkt jedoch eine hohe Produktivität in der Entwicklung durch die elegante Integration von HTML und Java-Code. Uns erscheint Wicket als die Abstraktion von Webtechnologien, die man sich immer gewünscht hat.

Wicket und Spring
Wicket ist im Gegensatz zu Spring ein „unmanaged“ Framework, d.h. die meisten Objekte können an beliebiger Stelle mit new erzeugt werden. Das einzige verwaltete Objekt von Wicket ist die Application-Klasse. Durch das zusätzliche Modul wicket-spring ist es möglich, die Application-Klasse durch Spring verwalten zu lasen. Damit wird es auch möglich, Abhängigkeiten zu anderen Spring Beans in die Application-Klasse zu injizieren. Da alle Wicket-Seiten auf die Application-Klasse zugreifen können, sind somit alle Abhängigkeiten bekannt.

Durch das Modul wicket-spring-annot wird es möglich, Abhängigkeiten direkt in Wicket-Komponenten (Seiten, Panels etc.) zu injizieren. Dazu wird in der Application-Klasse ein spezieller ComponentInstantiationListener registriert. Dieser sucht in allen neu erzeugten Wicket-Komponenten nach Attributen mit einer @SpringBean-Annotation und injiziert die jeweiligen Spring Beans (Listing 12).

Listing 2
--------------------------------------------------------------------
import org.apache.wicket.markup.html.WebPage;
.

public class UmfragePage extends WebPage{
   private String wert;	
   public String getWert() {
  return wert;
   }
   public void setWert(String wert) {
  this.wert = wert;
   }
   public UmfragePage() {
  Label bezeichnerLabel=new Label("bezeichner",
          new Model("Alter"));
  TextField wertField=new TextField("wert",
          new PropertyModel(this,"wert"));
  Form textEingabeForm=new Form("textEingabe") {
     protected void onSubmit() {
    setResponsePage(new ZusammenfassungPage(UmfragePage.this));
     }
  };
  textEingabeForm.add(bezeichnerLabel);
  textEingabeForm.add(wertField);		
  add(textEingabeForm);
   }
}
UmfragePage.java
René Preißel und Markus Wittwer arbeiten als Berater und Trainer bei der oose Innovative Informatik GmbH in Hamburg. Mit ihrer langjährigen Erfahrung unterstützen sie Menschen dabei, IT-Projekte erfolgreich abzuwickeln, vor allem durch Schulungen und Coachings. Ihr Wissen und ihre Fähigkeiten im Bereich Java, J2EE und Softwarearchitekturen sind dabei ein wichtiger Baustein.
Geschrieben von
René Preißel und Markus Wittwer
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.