Spielereien

Einführung in die Verwendung des Google Web Toolkits mit Eclipse

Christian Schmidt

Ajax-Anwendungen werden in letzter Zeit immer beliebter, da bei Ajax nicht immer eine komplette Webseite neu geladen werden muss, sondern gezielt bestimmte Teile aktualisiert werden können. Dies macht Webseiten komfortabler und spart Bandbreite. Vielen Website-Entwicklern fehlt jedoch fundiertes JavaScript-Wissen, um stabile Ajax-Applikationen entwickeln zu können. Um dieses Problem zu umgehen sowie den Entwicklungsaufwand niedrig zu halten, bietet sich die Verwendung von Frameworks an. Eines der bekanntesten Frameworks für Ajax-Applikationen ist das Google Web Toolkit (GWT). Dieser Artikel gibt einen Überblick über die Verwendung von GWT mit Eclipse.

Der erste Anlaufpunkt für Entwickler, die das GWT verwenden wollen, ist die offizielle GWT-Seite von Google: code.google.com/webtoolkit/. Hier kann es heruntergeladen werden, außerdem gibt es verschiedene Beispielprojekte, Einführungen sowie eine Klassenreferenz. Um einen ersten Eindruck von den Möglichkeiten des GWT zu bekommen, bietet es sich an, eines der Beispielprojekte, z.B. „Desktop App Clone“ oder „Kitchen Sink“ auszuwählen und sich die jeweilige Demo anzuschauen. Ist man jetzt von den Möglichkeiten des GWT überzeugt, wird das Archiv heruntergeladen.

Die Installation

Nach dem Download wird das Archiv entpackt, und die grundlegende Installation ist beendet. Weitere Schritte sind zunächst nicht notwendig. Das Verzeichnis, in das das GWT entpackt wurde, wird im Folgenden mit gwt-1.4 bezeichnet. Vor dem Fortfahren empfiehlt es sich, eines der Beispiele auszuführen, um zu prüfen, ob auch alles korrekt funktioniert. Dazu wechselt man in das Verzeichnis gwt-1.4/samples/Hello. Dort wird die Datei Hello-shell.cmd ausgeführt. Dadurch sollte die GWT Shell geöffnet werden sowie ein Browser-Fenster, in dem eine einfache GWT-Applikation ausgeführt wird.

Abb. 1: GWT-Beispielapplikation
Aller Anfang ist leicht

Als Nächstes wird ein eigenes Projekt erstellt. Im Ordner gwt-1.4 wird dazu ein Verzeichnis mit dem Namen TestProjekt erzeugt und dorthin gewechselt. Das GWT bietet über das projectCreator-Kommando Unterstützung, um Projekte anzulegen. Um mit diesem Kommando die von Eclipse benötigten Projektdateien anzulegen, wird das Kommando mit den Parametern eclipse sowie dem Projektnamen aufgerufen. Der vollständige Aufruf für das Testprojekt ist also der folgende: projectCreator-eclipse TestProjekt. Dabei muss natürlich auf die vollständige Pfadangabe zum projectCreator-Kommando (das sich in gwt-1.4 befindet) bzw. auf das korrekte Setzen eines Pfades geachtet werden.

Das Kommando hat jetzt im Ordner TestProjekt zwei Verzeichnisse sowie die beiden von Eclipse benötigten Dateien classpath und project erzeugt. Die Verzeichnisse haben aber noch keinen Inhalt.

Das Generieren der Inhalte für eine einfache GWT-Applikation ist sehr leicht, da das GWT auch hierfür wieder ein Kommando mit dem Namen applicationCreator zur Verfügung stellt. Das Kommando benötigt als Parameter den Namen des Projekts, eclipse, sowie die gewünschte Struktur des GWT-Projekts. Bei der Angabe des Projektnamens ist es sinnvoll, sich an die Vorschläge von Google zu halten, nach denen die Inhalte nach den Kategorien „Client“ (Quellcode für den Client), „Server“ (Quellcode für den Server) und „public“ (öffentlich zugängliche Ressourcen) aufgeteilt werden. Um die grundlegenden Dateien für ausführbaren Client-Code anzulegen, wird als dritter Parameter daher com.testprojekt.client.HelloWorld übergeben. HelloWorld ist der Name der Einstiegsklasse für das GWT-Projekt. Der vollständige Aufruf sieht folgendermaßen aus: applicationCreator -eclipse TestProjekt com.testprojekt.client.HelloWorld.

Als nächstes wird das Projekt in Eclipse importiert. Dazu geht man auf File | Import und wählt im Import-Dialog Existing Projects into Workspace. Im nächsten Fenster wird dann das Wurzelverzeichnis angegeben, im aktuellem Fall also gwt-1.4/TestProjekt. Durch einen Klick auf Finish wird das Projekt in Eclipse importiert.

Kleiner Überblick

Es wurden bisher verschiedene Dateien generiert (Abb. 2), deren Funktion im Folgenden genauer betrachtet wird.

Abb. 2: Überblick über die Projektstruktur

Im Package source.com.testprojekt.client befindet sich die Datei HelloWorld.java. Diese Datei enthält den Java-Quellcode, der die Programmlogik der Ajax-Applikation enthält.

GWT-Applikationen sind in so genannte Module unterteilt. Diese werden über XML-Dateien konfiguriert, in unserem Fall ist dies die Datei HelloWorld.gwt.xml. In diesen XML-Dateien werden unter anderem so genannte Einstiegspunkte definiert. Da wir in diesem kleinen Beispiel nur eine Klasse haben, muss diese zwangsläufig ein Einstiegspunkt sein und daher das Interface EntryPoint implementieren. Die Klasse enthält die Methode onModuleLoad, die zu Beginn ausgeführt wird.

Außer der eben beschriebenen Klasse gibt es noch eine weitere wichtige Datei, dies ist die Datei HelloWorld.html im Ordner public. In dieser HTML-Datei befindet sich der HTML-Teil der GWT-Applikation. Der grundlegende Aufbau entspricht einer gewöhnlichen HTML-Datei. Im Header befinden sich CSS-Anweisungen, mittels derer z.B. das Aussehen der GWT Widgets konfiguriert werden kann. Bei größeren Projekten empfiehlt es sich, diese Angaben in eine Datei auszulagern und nur diese Datei einzubinden. Ein weiterer wichtiger Eintrag im Header ist folgender:

 . 

Durch diese Zeile wird das kompilierte GWT-Modul eingebunden. Im Body-Bereich ist die folgende Zeile interessant:

Diese Zeile ist nicht zwingend nötig. Ein häufig beanstandetes Problem bei Ajax-Applikationen ist aber, dass man nicht mittels des Zurück-Knopfs des Browsers in der Browser-Historie zurückgehen kann. Dieses Problem wird durch das GWT gelöst. Um die History-Funktion zur Verfügung zu stellen, muss dieses IFrame eingebunden sein.

Als Nächstes folgen einige Zeilen normaler HTML-Code. Zu beachten sind hier die beiden IDs, die für die Tabellenzellen vergeben wurden. Über solche IDs kann aus dem Java-Quellcode auf bestimmte Bereiche einer HTML-Seite zugegriffen werden.

Die Ausführung

Bisher wurden die verschiedenen Teile dieser einfachen GWT-Applikation betrachtet, gestartet wurde sie aber noch nicht. Um die Anwendung unter Eclipse auszuführen, genügt es, den grünen Run-Knopf anzuwählen. Die Anwendung läuft nun im so genannten Hosted Mode. Das GWT kann eine Anwendung entweder in diesem Hosted Mode, oder im Web Mode ausführen. Der Hosted Mode hat den Vorteil, dass er direkt aus Eclipse gestartet werden kann. Der Quellcode wird dabei innerhalb der Java Virtual Machine ausgeführt, und man hat vollen Zugriff auf den Debugger, was während der Entwicklung von großem Vorteil ist. Der Hosted Mode ist daher der Modus, in dem eine Applikation normalerweise entwickelt wird, auch wenn er langsamer als der Web Mode ist.

Für den Web Mode wird das Projekt komplett in HTML und JavaScript übersetzt. In diesem Zustand kann die GWT-Applikation dann auf einen Webserver überspielt werden. Um dies zu demonstrieren, wird das Kommando HelloWorld-compile.cmd in gwt-1.4/TestProjekt aufgerufen. Dieses Kommando erzeugt einen Ordner www, der die kompilierte GWT-Anwendung enthält. Durch das im selben Ordner liegende Kommando HelloWorld-shell.cmd wird, genau wie durch Run in Eclipse, die Applikation im Hosted Mode gestartet.

Spielereien

Als Nächstes wird ein kleines Spiel erstellt, um die grundlegende GWT-Programmierung zu veranschaulichen. Bei dem Spiel handelt es sich um das bekannte Zahlenraten, bei dem eine Zufallszahl durch die Angaben „zu groß“ und „zu klein“ erraten werden muss.

Als Vorbereitung wird zuerst, wie oben beschrieben, in einem Verzeichnis games mithilfe der Kommandos projectCreator und applicationCreator ein Projekt game angelegt (als dritter Parameter für den ApplicationCreator wird com.game.client.NumberGame angegeben). Danach muss die Datei NumberGame.html bearbeitet werden. Die Überschrift wird in „Zahlenraten“ geändert, der GWT-Text entfernt und der Tabelle werden zwei neue Zellen hinzugefügt, eine mit dem Text „Bitte Zahl eingeben“ und eine mit der ID „slot3“. Der Body-Bereich sieht nun aus wie in Listing 1.

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

  
  
  

Zahlenraten

Bitte Zahl eingeben:

Als Nächstes wird in der Datei NumberGame.java der Inhalt der Methode onModuleLoad gelöscht, der im Folgenden neu aufbaut wird. Für das Spiel werden zwei Label für „zu groß“ und „zu klein“ benötigt, ein Textfeld zur Eingabe und ein Knopf zum Bestätigen. Der Code, um diese Elemente zu erzeugen, sieht folgendermaßen aus:

Label tooBig = new Label("zu groß");
Label tooSmall = new Label("zu klein");
Button button = new Button("OK");
TextBox textBox = new TextBox();

Diese Elemente müssen nun in die HTML-Seite eingebunden werden. Dafür können die IDs verwendet werden, die in den Tabellenzellen angegeben sind. Durch den Befehl RootPanel.get(id) kann auf bestimmte Bereiche der HTML-Seite zugegriffen werden. Durch RootPanel.get(„slot1“) würde man also z.B. auf die Tabellenzelle mit der ID „slot1“ zugreifen. Dann können die Elemente mittels der Methode add einfügt werden. Um also z.B. das Textfeld in die Tabellenzelle mit der ID „slot1“ einzufügen, schreibt man RootPanel.get(„slot1“).add(textBox). Die Platzierung der Elemente kann dann über HTML gesteuert werden, in diesem Fall über die Attribute der Tabelle.

Um die von einem Benutzer eingegebene Zahl zu überprüfen, wird ein ClickListener benötigt. Dieser holt sich aus dem Textfeld die eingegebene Zahl und vergleicht sie mit einer Zufallszahl. Der vollständige Quellcode für das Spiel ist in Listing 2 angegeben. Abbildung 3 zeigt das fertige Spiel in Aktion.

Listing 2
---------------------------------------------------------------
Label tooBig = new Label("zu groß");
Label tooSmall = new Label("zu klein");
Button button = new Button("OK");
TextBox textBox = new TextBox();
int randomNumber = 0;

public void onModuleLoad() {
  randomNumber = Random.nextInt(100);
      
  ClickListener inputListener = new ClickListener() {
    public void onClick(Widget sender) {
      RootPanel.get("slot3").clear();

      if(Integer.parseInt(textBox.getText()) > randomNumber)
        RootPanel.get("slot3").add(tooBig);
      else if(Integer.parseInt(textBox.getText()) 

Abb. 3: Spiel

Das GWT stellt verschiedenste Widgets zur Verfügung, mit denen auch komplexe Oberflächen relativ schnell aufgebaut werden können. Der Entwickler spart dabei sehr viel Routinearbeit, die sonst in die Oberfläche investiert werden müsste.

Alles eine Frage der Kommunikation

Der eben entwickelten Anwendung fehlt noch die Client-Server-Kommunikation. Die Möglichkeit, diese asynchron auszuführen und danach bestimmte Teile einer Webseite gezielt zu aktualisieren, ist aber der zentrale Bestandteil einer Ajax-Applikation. Das Spiel wird daher jetzt so abgeändert, dass der Test auf Übereinstimmung der beiden Zahlen am Server durchgeführt wird. Diese Methode zum Server auszulagern, ist zwar in der Praxis nicht besonders sinnvoll, bietet in diesem Fall aber einen einfachen Überblick über das Funktionsprinzip.

Für die Client-Server-Kommunikation bietet das GWT so genannte Remote Procedure Calls (RPC) an. Um einen RPC verwenden zu können, muss zunächst ein Dienst dafür implementiert werden.

Um den Dienst zu spezifizieren, beginnt man auf der Clientseite damit, zwei Interfaces zu definieren. Das erste Interface bekommt den Namen NumberService und muss vom Interface RemoteService (ein MarkerInterface für RPC Interfaces) abgeleitet sein. Es enthält nur die Methode compareNumber(int number, int targetNumber), die die eingegebene Zahl mit der Zufallszahl (targetNumber) vergleicht. Dieses Interface spezifiziert den synchronen Aufruf.

Zusätzlich wird aber noch der asynchrone Aufruf benötigt. Hierfür wird ein zweites Interface generiert. Der Name des zweiten Interfaces muss identisch mit dem des ersten sein, abgesehen von dem Suffix Async am Ende. In diesem Fall hat das zweite Interface also den Namen NumberServiceAsync. Es enthält wieder nur die Methode compareNumber, diesmal jedoch mit einem zusätzlichen Parameter vom Typ AsyncCallback.

Nach der Spezifikation des Service geht es an die Implementierung des selbigen. Man erstellt dazu einen Ordner mit dem Namen server, um Client- und Server-Code getrennt zu halten. In diesem Ordner wird dann eine Klasse mit dem Namen NumberServiceImpl generiert. Die Klasse muss vom Interface RemoteServiceServlet abgeleitet sein und den vorher definierten Service (NumberService) implementieren. RemoteServiceServlet ist von HTTPServlet abgeleitet und kümmert sich z.B. um die automatische Serialisierung. Die Klasse enthält den Code, den der Service ausführen soll, er vergleicht also die beiden Zahlen. Ist die eingegebene Zahl größer als die Zufallszahl, so soll 1 zurückliefert werden, ist sie kleiner -1, und bei Übereinstimmung 0. Die Implementierung des Service ist in Listing 3 dargestellt.

Listing 3
------------------------------------------------------------------
public class NumberServiceImpl extends RemoteServiceServlet implements NumberService {
  public int compareNumber(int number, int targetNumber) {
    if(number > targetNumber)
      return 1;
    else if (number 

Der Service, der die Zahlen vergleicht, ist nun fertig implementiert. Nun muss er in die Konfigurationsdatei NumberGame.gwt.xml aufgenommen werden. Dies geschieht durch den Tag:

.

„/number“ gibt dabei den Pfad zum Service an, unter dem dieser später verfügbar sein wird. Das GWT besitzt im Hosted Mode eine eingebettete Version von Tomcat, sodass es nicht nötig ist, sich während der Entwicklung um das Einrichten oder die Konfiguration eines Servers zu kümmern.
Vor dem Aufruf des RPC müssen der Hauptklasse noch die folgenden Zeilen hinzugefügt werden.

NumberServiceAsync myService = (NumberServiceAsync) GWT.create(NumberService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) myService;
String url = GWT.getModuleBaseURL() + "number";
endpoint.setServiceEntryPoint(url);

Zeile 1 instanziiert den Dienst. In Zeile 2 wird der Dienst dann in einen RemoteService gecastet, dem in Zeile 4 der URL zugewiesen wird, unter dem die Implementierung des Service verfügbar ist. Dieser URL wird in Zeile 3 generiert, er besteht aus einem per Kommando ermittelten Basis-URL und der Endung, die vorher in der XML-Konfigurationsdatei angegeben wurde. Nun kann in dem ClickListener aus Listing 2 statt dem direkten Vergleich der Zahlen der RPC aufrufen werden. Dies geschieht durch

numberService.compareNumber(Integer.parseInt(textBox.getText()), randomNumber, callback);

An dieser Stelle ist das Objekt mit dem Namen callback interessant. Der Aufruf von compareNumber als RPC liefert nicht direkt ein Ergebnis zurück (synchron), sondern ist ein asynchroner Aufruf. Sobald die Berechnung am Server (in diesem Fall der Zahlenvergleich) beendet ist, wird die Methode onSuccess des in Listing 4 definierten AsyncCallback-Objektes aufgerufen. Im Fehlerfall wird die Methode onFailure ausgeführt. Listing 5 zeigt dasselbe Spiel aus Listing 2, diesmal aber mit RPC-Aufruf.

Listing 4
---------------------------------------------------------------
AsyncCallback callback = new AsyncCallback() {
  public void onSuccess(Object result) {
    RootPanel.get("slot3").clear();
    int number = Integer.parseInt(result.toString());
            
    if(number == 1)
      RootPanel.get("slot3").add(tooBig);
    else if(number == -1)
      RootPanel.get("slot3").add(tooSmall);
    else 
      Window.alert("Treffer!");
  }

  public void onFailure(Throwable caught) {
    Window.alert("Fehler!");
    caught.printStackTrace();
  }
};

Das GWT bietet sehr viele Möglichkeiten, die natürlich eine gewisse Einarbeitungszeit erfordern. Investiert man diese Zeit aber, so verfügt man über ein Framework, mit dem auch komplexe Ajax-Applikationen mit großer Zeitersparnis entwickelt werden können.

Christian Schmidt studierte Bioinformatik in Tübingen und arbeitet heute als Junior Software Entwickler bei der CoWare AG in München.
Geschrieben von
Christian Schmidt
Kommentare

Schreibe einen Kommentar

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