Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 17 Servlets und Java Server Pages
gp 17.1 Dynamische Web-Seiten und Servlets
gp 17.1.1 Was sind Servlets?
gp 17.1.2 Was sind Java Server Pages?
gp 17.1.3 Vorteil von JSP/Servlets gegenüber CGI-Programmen
gp 17.2 Vom Client zum Server und wieder zurück
gp 17.2.1 Der bittende Client
gp 17.2.2 Was erzeugt ein Web-Server für eine Antwort?
gp 17.2.3 Wer oder was ist MIME?
gp 17.3 Servlets und Java Server Pages entwickeln und testen
gp 17.3.1 Servlet-Container
gp 17.3.2 Web-Server mit Servlet-Funktionalität
gp 17.3.3 Tomcat
gp 17.4 Java Server Pages in Tomcat und Eclipse
gp 17.4.1 Erster Ablageort für eigene JSP-Seiten
gp 17.4.2 Das Sysdeo-Plugin
gp 17.5 Skript-Elemente
gp 17.5.1 Scriptlets
gp 17.5.2 Ausdrücke
gp 17.5.3 Deklarationen
gp 17.5.4 Kommentare und Quoting
gp 17.6 Web-Applikationen
gp 17.7 Implizite Objekte
gp 17.8 Entsprechende XML-Tags
gp 17.9 Was der Browser mit auf den Weg gibt - HttpServletRequest
gp 17.9.1 Verarbeiten der Header
gp 17.9.2 Hilfsfunktion im Umgang mit Headern
gp 17.9.3 Übersicht der Browser-Header
gp 17.10 Formulardaten
gp 17.11 Das HttpServletResponse-Objekt
gp 17.11.1 Automatisches Neuladen
gp 17.11.2 Seiten umlenken
gp 17.12 JSP-Direktiven
gp 17.12.1 page-Direktiven im Überblick
gp 17.12.2 include-Direktive
gp 17.13 Aktionen
gp 17.13.1 Aktion include
gp 17.13.2 Aktion forward
gp 17.13.3 Aktion plugin
gp 17.14 Beans
gp 17.14.1 Beans in JSP-Seiten anlegen, Attribute setzen und erfragen
gp 17.14.2 Der schnelle Zugriff auf Parameter
gp 17.15 Kleine Kekse: die Klasse Cookies
gp 17.15.1 Cookies erzeugen und setzen
gp 17.15.2 Cookies vom Servlet einlesen
gp 17.15.3 Kleine Helfer für Cookies
gp 17.15.4 Cookie-Status ändern
gp 17.15.5 Langlebige Cookies
gp 17.15.6 Ein Warenkorbsystem
gp 17.16 Sitzungsverfolgung (Session Tracking)
gp 17.16.1 Das mit einer Sitzung verbundene Objekt HttpSession
gp 17.16.2 Werte mit einer Sitzung assoziieren und auslesen
gp 17.16.3 URL-Rewriting
gp 17.16.4 Zusätzliche Informationen
gp 17.17 Tag-Libraries
gp 17.17.1 Standard Tag Library (JSTL)
gp 17.18 Servlets
gp 17.18.1 Servlets compilieren
gp 17.18.2 Wohin mit den Servlets: das classes-Verzeichnis
gp 17.18.3 Servlets mit dem Sysdeo-Plugin unter Eclipse
gp 17.18.4 Servlet-Mapping
gp 17.19 Der Lebenszyklus eines Servlets
gp 17.19.1 Initialisierung in init()
gp 17.19.2 Abfragen bei service()
gp 17.19.3 Mehrere Anfragen beim Servlet und die Thread-Sicherheit
gp 17.19.4 Das Ende eines Servlets
gp 17.20 Das HttpServletResponse-Objekt
gp 17.20.1 Wir generieren eine Web-Seite
gp 17.20.2 Binärdaten senden
gp 17.20.3 Komprimierte Daten mit Content-Encoding
gp 17.20.4 Noch mehr über Header, die der Server setzt
gp 17.21 Objekte und Dateien per POST verschicken
gp 17.21.1 Datei-Upload
gp 17.22 Servlets und Sessions
gp 17.23 Weiterleiten und Einbinden von Servlet-Inhalten
gp 17.24 Inter-Servlet-Kommunikation
gp 17.24.1 Daten zwischen Servlets teilen
gp 17.25 Internationalisierung
gp 17.25.1 Die Länderkennung des Anfragers auslesen
gp 17.25.2 Länderkennung für die Ausgabe setzen
gp 17.25.3 Westeuropäische Texte senden
gp 17.26 Tomcat: Spezielles
gp 17.26.1 Tomcat als Service unter Windows NT ausführen
gp 17.26.2 MIME-Types mit Tomcat verbinden
gp 17.26.3 Servlets beim Start laden
gp 17.27 Ein Servlet generiert WAP-Seiten für das Handy
gp 17.27.1 Ein WAP-Handy simulieren
gp 17.27.2 Übersicht der wichtigsten Tags
gp 17.27.3 Der Gateway
gp 17.27.4 WML-Seiten aufbauen
gp 17.27.5 Interessante Links zum Thema Servlets/JSP


Galileo Computing

17.19 Der Lebenszyklus eines Servletsdowntop

Der Container für Servlets registriert eine Anfrage durch den Client und lädt das Servlet in den Speicher. Da Servlets normale Klassen sind, übernimmt ein spezieller Klassenlader diese Aufgabe. Die Abarbeitung findet anschließend in einem Thread statt, der die Methoden des Servlet-Objekts aufruft.

Wir wollen nun verfolgen, wie der Container die Arbeit an das Servlet delegiert. Über die Schnittstelle Servlet werden drei elementare Methoden für Initialisierung, Abarbeitung der Anfragen und Beendigung vorgeschrieben. Der Ablauf dieser Methoden nennt sich Lebenszyklus eines Servlets.

Die folgende Aufzählung zeigt alle Methoden, die die Schnittstelle Servlet für alle Java-Servlets vorschreibt.


interface javax.servlet.Servlet

gp void init( ServletConfig config )
Wird zu Anfang eines Dienstes aufgerufen.
gp void service( ServletRequest req, ServletResponse res )
Der Container leitetet die Anfrage an das Servlet an diese Stelle.
gp void destroy()
Wird am Ende eines Servlets vom Container genau einmal aufgerufen.
gp ServletConfig getServletConfig()
Liefert ein ServletConfig-Objekt, welches Initialisierungs- und Startparameter kapselt.
gp String getServletInfo()
Liefert Informationen über das Servlet wie Autor, Version und Copyright.

Beispiel Ein Servlet implementiert getServletInfo(), um Informationen an den Servlet-Container zu geben.
public class FirstServlet extends GenericServlet
{
  public void service( ServletRequest request, ServletResponse response )
  {
  }
  public String getServletInfo()
  {
    return "Ich bin der erste Erguss seiner Servlet-Fähigkeiten";
  }
}


Galileo Computing

17.19.1 Initialisierung in init()downtop

Nachdem der Klassenlader genau ein Exemplar der Servlet-Klasse geladen hat, ruft der Container die init()-Methode des Servlets auf. Die Servlet-Spezifikation macht allerdings keine Aussage darüber, wann init() ausgeführt wird. Dies kann der Fall sein, wenn das Servlet geladen wird, aber noch keine Anfrage ansteht, wenn die erste Anfrage kommt oder aufgrund einer globalen Initialisierung. In init() kann das Servlet sich initialisieren und beispielsweise Netzwerk- oder Datenbankverbindungen aufbauen.

Neben init() existiert init(ServletConfig) mit einem Konfigurationsobjekt vom Typ der Schnittstelle ServletConfig. Die wichtigste Methode ist getServletContext(), die ein ServletContext-Objekt liefert, welches sich zum Beispiel zur Pfadumsetzung eignet. Wenn wir diese parametrisierte Methode überschreiben, dann dürfen wir es nicht versäumen, super() mit dem Parameter ServletConfig aufzurufen, und es somit weiterzuleiten:

public void init( ServletConfig config ) throws ServletException
{
  super.init( config );
  ...
}

Die init()-Methode in der Oberklasse über super() anzusprechen, ist die einzige Möglichkeit, die uns Zugang zum Konfigurationsobjekt verschafft. Der Container rückt es nur an dieser Stelle heraus. Daher bleibt uns nichts anders übrig, als entweder init(Servlet-Config) mit super() oder init() ohne Parameter zu verwenden, um das Servlet-Config-Objekt zu bekommen.

Falls wir das ServletConfig-Objekt in init(ServletConfig) nicht benötigen, ist es einfacher, nur init() zu überschreiben. Das sieht dann in der Klasse GenericServlet (davon erbt HttpServlet) so aus:

public void init( ServletConfig config ) throws ServletException {
  this.config = config;
  log( "init" );
  this.init();
}

Der Container ruft eigentlich nur init(ServletConfig) auf, und wie wir sehen, ruft die Methode dann selbstständig init() auf. Eine Referenz auf das Konfigurationsobjekt wird in GenericServlet gespeichert, auf die über getServletConfig() zugegriffen werden kann. Die Referenzvariable ist private, so wie es sich gehört. Somit sollte nicht init(ServletConig) überschrieben werden, sondern nur init().

Thread-Sicherheit

Während externe CGI-Programme Datenbankverbindungen für jede Anfrage neu aufbauen müssen, muss ein Servlet dies nicht. (Dadurch ergibt sich natürlich ein satter Geschwindigkeitsvorteil.) Dies funktioniert aber nur deswegen, da für jede Servlet-Instanz in ihrem Lebenszyklus nur einmal die init()-Methode aufgerufen wird. Alle Anfragen werden in einem eigenen Thread behandelt, und dieser ruft die Methode service() auf. init() muss nicht Thread-sicher sein. service() wird von mehreren Threads parallel aufgerufen und muss Thread-sicher sein.


Hinweis Obwohl in der Regel der Servlet-Server mehrere Threads initialisiert, die auf die init()-Methode zugreifen, kann die init()-Methode mehrfach aufgerufen werden, um mehrere Exemplare einer Servlet-Klasse zu erzeugen. Dann ist es ungünstig, Ressourcen wie Datenbankverbindungen immer neu anzufordern, obwohl vielleicht schon eine andere init()-Methode dies gemacht hat. Da es mehrere Exemplare der Servlet-Klasse im Servlet-Container geben kann, sollten statische Initialisierungen nicht in der init()-Methode durchgeführt werden.

Besser gelöst ist die Implementierung mit einer Fabrik-Methode seitens der Datenbank. Dann kümmert sie sich darum, dass es nur ein Exemplar gibt. Die Referenz wird dann ebenfalls von der Datenbank und nicht vom Servlet gespeichert.


Beispiel Wenn ein Servlet erwacht, wollen wir eine fiktive Datenbankverbindung für alle Servlets dieser Klasse aufbauen.
public class DBServlet extends GenericServlet
{
  static DBConnection con;
  public void init()
  {
    if ( con == null )
      con = DB.getConnection();
  }
 public void service( ServletRequest request, ServletResponse response )
       throws ServletException, IOException
  {
    ...
  }
}


Galileo Computing

17.19.2 Abfragen bei service()downtop

In unserem ersten Beispiel haben wir uns der Klasse GenericServlet bedient, um eine service()-Methode zu überschreiben, die auf beliebige Anfragen des Clients antwortet. In der Regel wollen wir aber bei unterschiedlichen Anfragen auch unterschiedlich reagieren. Eine Anfrage ist zum Beispiel GET. Diese Form wird dann benutzt, wenn im Browser vom Benutzer eine URL eingetragen oder ein Verweis verfolgt wird. Neben GET-Anfragen gibt es noch POST-Anfragen, die für Formulare Verwendung finden.

Klasse HttpServlet

Damit wir auf GET-Anfragen anders reagieren können als auf POST-Anfragen, sind zwei Möglichkeiten denkbar: Entweder können wir die service()-Methode überschreiben, den Typ herausfinden und dann die Anfrage behandeln, oder wir verwenden eine andere Klasse, nämlich HttpServlet. Sie implementiert service() so, dass Anfragen an Methoden wie doGet(), doPost() und entsprechende Methoden weitergeleitet werden. Bei eigenen Anfragen wollen wir im Folgenden immer HttpServlet erweitern und service() nicht mehr direkt verwenden, sondern die entsprechenden doXXX()-Methoden.

Implementierung von service() in HttpServlet

Die service()-Methode der Klasse HttpServlet erfragt die verwendete Methode (GET oder POST) mit der Dienstmethode getMethod() vom aktuellen Request. Kurz skizziert hat sie folgendes Format:

protected void service( HttpServletRequest req, HttpServletResponse resp )
  throws ServletException, IOException
{
  String method = req.getMethod ();
  if (method.equals(METHOD_GET)) {
    ...
  } else if (method.equals (METHOD_HEAD)) {
    ...
  } else if (method.equals (METHOD_POST)) {
    doPost (req, resp);
  } else {
    ...
    resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
                   errMsg);
  }
}

Auf diese Weise testet service() zusätzlich auf METHOD_PUT, METHOD_DELETE, METHOD_OPTIONS und METHOD_TRACE.


Hinweis Die service()-Methode überschreiben bedeutet auch, dass wir uns um die Sicherheit Gedanken machen müssen. Wenn wir nur doGet() und doPost() gleich behandeln wollen, so implementieren wir zum Beispiel doGet() und leiten die Anfrage an doPost() weiter. Wenn wir alles in service() implementieren, dann kann Programmcode bei allen Anfragetypen ablaufen.


Galileo Computing

17.19.3 Mehrere Anfragen beim Servlet und die Thread-Sicherheitdowntop

In der Regel wird der Container pro Anfrage einen Thread erzeugen, der dann die service()-Methode des Servlet-Objekts betritt und die Anfrage bearbeitet. Es gibt demnach für mehrere Aufträge keine unterschiedlichen Exemplare des Servlets, sondern lediglich unterschiedliche Threads bei einem Servlet-Exemplar. Es ist aus diesem Grund zu bedenken, dass die Dienste seiteneffektfrei sein müssen. Eine einfache Möglichkeit, dies zu erzwingen, bietet eine synchronized-Methode. Die Entwickler haben noch eine zweite Möglichkeit vorgesehen, so dass sich der Entwickler nicht um die Nebenläufigkeit kümmern muss. Dazu implementiert das Servlet die Markierungsschnittstelle SingleThreadModel. Sie garantiert, dass der Dienst von maximal einem Thread zu jedem Zeitpunkt ausgeführt wird. Ein Nebeneffekt wäre, wenn ein Thread in einem Servlet Daten ändert, während ein anderer Thread im gleichen Servlet die gleichen Daten liest.

Im Server läuft dann etwa Folgendes ab:

Servlet servlet = getServlet();
if ( servlet instanceof SingleThreadModel )
{
  synchronized ( servlet )
  {
    servlet.service();
  }
}
else servlet.service();

Die Synchronisation wirkt sich natürlich auf die Ausführungsgeschwindigkeit nachteilig aus.


Galileo Computing

17.19.4 Das Ende eines Servletstoptop

Wenn eine Servlet-Klasse nicht mehr benötigt wird und aus dem Speicher entfernt werden soll, ruft der Container zum Abschluss die Methode destroy() auf. Hier findet sich der Programmcode, der für die Freigabe sorgt und die aus init() bereitgestellten Ressourcen wieder freigibt. Häufig findet sich in init() und destroy() ein Mechanismus, der serialisierte Daten liest und schreibt. Doch was ist, wenn der Container vor destroy() den Geist aufgibt? Dies führt zu Problemen, wenn in destroy() Daten für die Persistenz serialisiert werden. Sie würden bei einem Absturz verloren gehen.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de