Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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.4 Java Server Pages in Tomcat und Eclipse
    gp 17.4.1 Download und Installation
    gp 17.4.2 Ablageort für eigene JSP-Seiten
    gp 17.4.3 Web-Applikationen
    gp 17.4.4 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.5.5 Entsprechende XML-Tags
  gp 17.6 Implizite Objekte
  gp 17.7 Was der Browser mit auf den Weg gibt – HttpServletRequest
    gp 17.7.1 Verarbeiten der Header
    gp 17.7.2 Hilfsfunktion im Umgang mit Headern
    gp 17.7.3 Übersicht der Browser-Header
  gp 17.8 Formulardaten
  gp 17.9 Das HttpServletResponse-Objekt
    gp 17.9.1 Automatisches Neuladen
    gp 17.9.2 Seiten umlenken
  gp 17.10 JSP-Direktiven
    gp 17.10.1 page-Direktiven im Überblick
    gp 17.10.2 include-Direktive
    gp 17.10.3 Mit JSPs Bilder generieren
  gp 17.11 Aktionen
    gp 17.11.1 Aktion include
    gp 17.11.2 Aktion forward
    gp 17.11.3 Aktion plugin
  gp 17.12 Beans
    gp 17.12.1 Beans in JSP-Seiten anlegen, Attribute setzen und erfragen
    gp 17.12.2 Der schnelle Zugriff auf Parameter
  gp 17.13 Kleine Kekse: die Klasse Cookies
    gp 17.13.1 Cookies erzeugen und setzen
    gp 17.13.2 Cookies vom Servlet einlesen
    gp 17.13.3 Kleine Helfer für Cookies
    gp 17.13.4 Cookie-Status ändern
    gp 17.13.5 Langlebige Cookies
    gp 17.13.6 Ein Warenkorbsystem
  gp 17.14 Sitzungsverfolgung (Session Tracking)
    gp 17.14.1 Das mit einer Sitzung verbundene Objekt HttpSession
    gp 17.14.2 Werte mit einer Sitzung assoziieren und auslesen
    gp 17.14.3 URL-Rewriting
    gp 17.14.4 Zusätzliche Informationen
  gp 17.15 Tag-Libraries
    gp 17.15.1 Standard Tag Library (JSTL)
    gp 17.15.2 Jakarta Taglibs Project
  gp 17.16 Servlets
    gp 17.16.1 Servlets compilieren
    gp 17.16.2 Wohin mit den Servlets: das classes-Verzeichnis
    gp 17.16.3 Servlets mit dem Sysdeo-Plugin unter Eclipse
    gp 17.16.4 Servlet-Mapping
  gp 17.17 Der Lebenszyklus eines Servlets
    gp 17.17.1 Initialisierung in init()
    gp 17.17.2 Abfragen bei service()
    gp 17.17.3 Mehrere Anfragen beim Servlet und die Thread-Sicherheit
    gp 17.17.4 Das Ende eines Servlets
  gp 17.18 Das HttpServletResponse-Objekt
    gp 17.18.1 Wir generieren eine Web-Seite
    gp 17.18.2 Binärdaten senden
    gp 17.18.3 Komprimierte Daten mit Content-Encoding
    gp 17.18.4 Noch mehr über Header, die der Server setzt
  gp 17.19 Objekte und Dateien per POST verschicken
    gp 17.19.1 Datei-Upload
  gp 17.20 Servlets und Sessions
  gp 17.21 Weiterleiten und Einbinden von Servlet-Inhalten
  gp 17.22 Inter-Servlet-Kommunikation
    gp 17.22.1 Daten zwischen Servlets teilen
  gp 17.23 Internationalisierung
    gp 17.23.1 Die Länderkennung des Anfragers auslesen
    gp 17.23.2 Länderkennung für die Ausgabe setzen
    gp 17.23.3 Westeuropäische Texte senden
  gp 17.24 Tomcat: Spezielles
    gp 17.24.1 Tomcat als Service unter Windows NT ausführen
    gp 17.24.2 Interessante Links zum Thema Servlets/JSP


Galileo Computing

17.17 Der Lebenszyklus eines Servletdowntop

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 leitet 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.17.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 aktuellen 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(ServletConfig) 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.17.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.17.3 Mehrere Anfragen beim Servlet und die Thread-Sicherheit  downtop

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.17.4 Das Ende eines Servlets  toptop

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 © 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