Servlets programmieren
Alle Servlets müssen von der Klasse javax.servlet.http.HttpServlet abgeleitet werden.
Im Prinzip können Servlets für spezielle Anforderungen auch javax.servlet.GenericServlet erweitern, allerdings sind diese Anforderungen so speziell, dass sie mir bisher noch nicht untergekommen sind. Generell müssen Sie von GenericServlet ableiten, wenn Sie ein anderes Protokoll als http, also beispielsweise ftp, nutzen.
Bitte öffnen Sie die Dokumentation der Klasse javax.servlet.http.HttpServlet aus Ihrer Dokumentation der ServletApi in ein neues Browserfenster. Wie Sie sehen, erweitert HttpServlet javax.servlet.GenericServlet und bringt daher auch alle Methoden mit, die GenericServlet anbietet.
Des weiteren handelt es sich bei HttpServlet um eine abstrakte Klasse (public abstract class HttpServlet). Das Schlüsselwort abstract bedeutet, dass keine Objekte aus dieser Klasse erzeugt werden können. Sie dürfen daher nicht schreiben:
HttpServlet myServlet=new HttpServlet(); // falsch!
Um eine abstrakte Klasse zu nutzen, müssen Sie eine Klasse erstellen, die sich von der abstrakten Klasse ableitet, also beispielsweise:
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
public class CountryNamesServlet extends HttpServlet {
Üblicher weise haben abstrakte Klassen eine oder mehrere abstrakte Methoden, die man in der abgeleiteten Klasse überschreibt, um seine speziellen Wünsche in einem vorgefertigtem Rahmen unterzubringen. Bei HttpServlet ist das nicht der Fall, hier finden wir keine abstrakte Methode. Alle Methoden sind implementiert, allerdings haben alle doXXX(..) Methoden keine Funktionalität.
Diese doXXX(..) Methoden werden vom Container aufgerufen, wenn ein Http-Request mit der entsprechenden Request-Methode erfolgt; doGet(..) wird beispielsweise aufgerufen, wenn ein Request mit "GET" erfolgt, doPost(..) bei "POST", doHead(..) bei HEAD und so weiter. Die anderen Request Methoden werden Sie selten verwenden und werden nicht immer vom Server unterstützt. Anschliessend eine knappe Übersicht zu den Request-Methoden, Einzelheiten entnehmen Sie bitte der genannten RFC 2616:
GET
Häufigste Anfrageform, keine weiteren Daten außer Header werden erwartet, es wird der Inhalt der angegeben URL zurückgegeben.
HEAD
Keine weiteren Daten außer Header erwartet, es wird der Header wie bei einer GET Anfrage zurückgegeben allerdings ohne den Inhalt der URL. Die Methode wird von Link-checkern verwendet und muss von Servern stets beantwortet werden. Bei Servlets wird bei einer HEAD-Anfrage automatisch das Ergebnis von doGet(..) ohne den Content gesendet, wenn doHead(..) nicht überschrieben wurde.
POST
Übermittlung von Daten, die von dynamischen Seiten bearbeitet werden können; wird häufig bei HTML-Formularen verwende. Es wird der Inhalt der URL zurückgegeben.
PUT
Übermittlung von Daten, die der Server unter der angegebenen URL bereitstellen soll, also ein upload. Der Server gibt einen Header zurück, ob die Ressource erzeugt werden konnte. (Die Methode wird üblicherweise weder von Servern noch von Browsern unterstützt.)
DELETE
Der Server wird angewiesen, die unter der URL zu findende Ressource zu löschen oder ihre Bereitstellung auszusetzen. Der Server gibt einen entsprechenden Header zurück, ob dies ausgeführt wurde. (Die Methode wird üblicherweise von Servern nicht unterstützt.)
TRACE
Proxyserver werden angewiesen, sich in den Via-Header einzutragen. Die Methode dient dazu, den Weg einer Anfrage über das Netz zu verfolgen.
OPTIONS
Der Server wird angewiesen, Header über seine Fähigkeiten auszugeben.
Wir werden daher in einem Servlet auf jeden Fall die doGet(..) Methode überschreiben. Wenn wir auch auf POST Anfragen reagieren wollen, überschreiben wir auch doPost(..). Um auf POST und GET in gleicher Weise zu reagieren, würden wir beispielsweise doPost(..) wie folgt schreiben:
Code: /code2html/jspkurs/bsp/kap3/Redirect_1.java.html
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
doGet(request,response);
}
Wir leiten also eine POST Anfrage einfach auf doGet(..) weiter, wo wir das entsprechende Verhalten implementieren.
Anders als ein klassisches CGI verbleibt ein einmal initialisiertes Servlet im Speicher und behält auch einmal initialisierte Felder (Variablen) bei. Es macht daher durchaus Sinn, aufwändige Prozesse wie das Laden von gleichbleibenden Daten oder das Kontakten von externen Services nur bei der ersten Ausführung des Servlets auszuführen und die Ergebnisse dann im Servlet zu speichern. Zu diesem Zweck gibt es die Methoden init() und destroy() aus javax.servlet.GenericServlet, die ebenfalls unser Servlet erbt. Init() wird beim ersten Laden des Servlets aufgerufen, destroy() vor dem Entladen des Servlets. Wir können daher auch init() oder destroy() überschreiben, um Initialisierung oder Finalisierungs-Code unterzubringen.
Wir wollen nun ein einfaches Servlet programmieren, das einen auszuwählenden Ländernamen in drei Sprachen (Englisch, Französisch und Italienisch) übersetzt. Das Beispiel gibt nur rudimentäres HTML aus und ist daher geeignet, als Servlet realisiert zu werden.
Länderspezifische Informationen werden in Java in der Klasse java.util.Locale gespeichert. Die Klasse ist im wesentlichen ein dünner Mantel um eine ganzen Haufen Daten und wird normalerweise eingesetzt, um Zeit, Datums oder Währungsangaben entsprechend den landestypischen Gegebenheiten zu formatieren. Daneben kapselt sie Ländernamen in vielen Sprachen, was wir uns hier zunutze machen wollen.
Schauen wir uns das Beispielservlet an:
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
package jspkurs.bsp.kap3;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
/**
* Ein einfaches Servlet, dass Ländernamen in
* Deutsch, Englisch, Französiche und Italienisch anzeigt.
*
* BEACHTEN SIE, DASS SIE ZUM KOMPILIEREN DIESES SERVLETS
* DIE DATEI servlet.jar ZUR FINDEN IN tomcat/lib IN DEN
* CLASSPATH AUFNEHMEN MÜSSEN!
*
* @author Hans Joachim Herbertz
* @created 18.02.2003
*
* Alle Servlets werden von
* javax.servlet.http.HttpServlet abgeleitet. Sie erhalten
* dadurch die grundlegende Funktionalität, um als Servlet
* verwendet werden zu können. Allerdings tun sie dann
* noch nichts.
*
* Funktionalität fügen wir hinzu, indem wir Methoden aus
* HttpServlet überschreiben.
*/
public class CountryNamesServlet extends HttpServlet {
/**
* Eine Hashtable, um bequem auf die Daten zugreifen zu können. */
private Map allLocales = null;
/**
* Die ini(..) Methode wird aufgerufen, wenn das Servlet vom Container
* geladen wird. */
public void init() throws ServletException {
// Locale.setDefault(Locale.GERMAN);
// Hashtable für Daten erzeugen
allLocales = new TreeMap();
// alle verfügbaren Lokalen holen
Locale[] locales = Locale.getAvailableLocales();
for (int i = 0; i < locales.length; i++) {
// Locale, die einen Ländernamen besitzen speichern
if (locales[i].getDisplayCountry().length() > 0) {
allLocales.put(locales[i].getDisplayCountry(), locales[i]);
}
}
}
/**
* Diese Methode wird vom Container aufgerufen, wenn ein Request mit
* der GET-Methode ankommt; das ist üblicherweise der Fall, ausser wenn
* Sie ein Formular explizit mit POST senden. Wir zeigen in diesem Fall
* eine Auswahlliste der verfügbaren Länder an. */
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Content type setzen
response.setContentType("text/html");
// besorgen uns einen Writer, mit dem wir in
// die Ausgabe schreiben können.
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("Wählen Sie ein Land aus!<br>");
out.println(
"<form action=\""
+ request.getRequestURL()
+ "\" method=\"post\" name=\"formb\">");
out.println("<select name=\"country\" onChange=\"formb.submit()\">");
// Aufzählung aller Ländernamen (keys)
Iterator it = allLocales.keySet().iterator();
while (it.hasNext()) {
String aCountry = (String) it.next();
out.println(
"<option value=\"" + aCountry + "\">" + aCountry + "</option>");
}
out.println("</select></form>");
out.println("</body></html>");
}
/**
* Schliesslich doPost(..) überschreiben, um nach Auswahl eines Landes
* die entsprechenden Ausdrücke anzuzeigen. */
public void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Ausgewähltes land holen
String selectedCountry = request.getParameter("country");
// wenn wir das ausgewählte Land nicht festellen können,
// noch einmal die Auswahlseite anzeigen.
if (selectedCountry == null
|| selectedCountry.length() < 1) {
doGet(request, response);
return;
}
// Locale zum Land
Locale selectedLocale = (Locale) allLocales.get(selectedCountry);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body><table>");
out.println(
"<tr><td>Deutsch</td><td>"
+ selectedLocale.getDisplayCountry(Locale.GERMAN)
+ "</td></tr>");
out.println(
"<tr><td>Englisch</td><td>"
+ selectedLocale.getDisplayCountry(Locale.US)
+ "</td></tr>");
out.println(
"<tr><td>Französisch</td><td>"
+ selectedLocale.getDisplayCountry(Locale.FRENCH)
+ "</td></tr>");
out.println(
"<tr><td>Italienisch</td><td>"
+ selectedLocale.getDisplayCountry(Locale.ITALIAN)
+ "</td></tr>");
out.println("</table><br>");
out.println(
"<a href=\"" + request.getRequestURI() + "\">zurück</a>");
out.println("</body></html>");
}
}
Wie Sie bemerken, handelt es sich bei unserem Servlet um eine ganz normale Java Klasse mit einer Package Angabe, diversen Imports und der nun schon bekannten Definition der Klasse als Erweiterung von HttpServlet.
Als erstes überschreiben wir die init(..) Methode, um unsere Daten aufzusetzen. Wir setzen zunächst die voreingestellte Sprache auf Deutsch (beachten Sie, dass viele Rechner auf einem englischsprachigem Betriebssystem laufen), denn wir wollen die deutschen Ländernamen zur Auswahl benutzen.
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
public void init() throws ServletException {
// Locale.setDefault(Locale.GERMAN);
// Hashtable für Daten erzeugen
allLocales = new TreeMap();
// alle verfügbaren Lokalen holen
Locale[] locales = Locale.getAvailableLocales();
for (int i = 0; i < locales.length; i++) {
// Locale, die einen Ländernamen besitzen speichern
if (locales[i].getDisplayCountry().length() > 0) {
allLocales.put(locales[i].getDisplayCountry(), locales[i]);
}
}
}
Unsere Daten halten wir in einer globalen java.util.TreeMap (Zeile 43). Das ist eine Hashtable (key->value), die automatisch nach den Keys sortiert wird. Die Klasse stellt auch sicher, dass jeder Key nur einmal vorkommt.
Dann holen wir und alle Localen, die das System kennt (46), und speichern sie unter dem Schlüssel ihres Ländernamens in der Hashtable(47-52). Hierbei ist Vorsicht geboten, denn einige Locale haben keinen anzeigbaren Namen. Sie bezeichnen dann beispielsweise nur einen Sprachraum oder sind unvollständig definiert. Um das auszufiltern, implementieren wir die if-Abfrage in Zeile 49.
Nun haben wir eine schöne Datenklasse aufgesetzt.
Das Servlet ist so gebaut, dass bei einem GET-Request eine Auswahlliste mit Ländernamen in einer HTML-Form angezeigt wird. Dieses Formular wird mit POST gesendet, worauf dann die Übersetzung des ausgewählten Landes angezeigt wird. Wir implementieren doGet(..) und doPost(..) daher unterschiedlich.
DoGet(..) besteht im Wesentlichen aus HTML-Ausgabe. Dazu besorgen wir uns in Zeile(68) einen java.io.PrintWriter aus dem javax.servlet.http.HttpServletResponse -Objekt. Der PrintWriter kapselt die Ausgabe an den Browser für Text. Er entspricht dem out-Objekt, das wir in der JSP benutzt haben. Wenn Sie binäre Daten schreiben wollen, benutzen Sie response.getOutputStream(). Benutzen Sie aber nicht beide in einem Servlet, da die Ausgabestreams wahrscheinlich durch den Server gepuffert werden und so vermutlich einiges durcheinander geraten wird.
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
// Aufzählung aller Ländernamen (keys)
Iterator it = allLocales.keySet().iterator();
while (it.hasNext()) {
String aCountry = (String) it.next();
out.println("<option value=\"" + aCountry + "\">" + aCountry + "</option>");
}
Aus unserer globalen Hashtable (allLocales) holen wir uns alle keys (Zeile 77) und schreiben für jeden Key einen option-Tag. Bitte beachten Sie, dass java.util.Iterator#next() ein java.lang.Object zurückgibt, dass entsprechend gecastet werden muss.
Wie Sie bereits hier bemerken, macht es nicht besonders viel Spaß, HTML mit print-Befehlen zu schreiben.
Vergessen Sie nicht, den Content-Type Header zu setzen, wenn Sie HTML schreiben. Die Voreinstellung in Servlets ist anders als in JSP "text/plain", sodass Browser, die die Standards implementieren, Ihnen ohne die folgende Zeile den Quelltext Ihrer HTML-Seite anzeigen werden:
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
// Content type setzen
response.setContentType("text/html");
Schauen wir uns doPost(..) an. DoPost wird vom Container aufgerufen, wenn das Formular aus doGet(..) mit POST abgeschickt wird dass im Request den Parameter "country", den wir uns als erstes holen.
Code: /code2html/jspkurs/bsp/kap3/CountryNamesServlet.java.html
// Ausgewähltes land holen
String selectedCountry = request.getParameter("country");
Dieses Locale - Objekt kann nun seinen Landesnamen in verschiedenen Sprachen schreiben, indem wir auf ihm die Methode getDisplayCountry(Locale gewünschte Sprache) aufrufen. Das ist unsere Ausgabe in den Zeilen 115-130.
Beispiel ausführen