<jsptutorial />

Anhang II: Servlets - Die Grundlagen


Einleitung

Es gibt mehrere Gründe in diesem Tutorial auch auf Servlets einzugehen. Zunächst einmal werden alle JSPs in Servlets transformiert. Es lohnt sich daher von Zeit zu Zeit bei Problemen in den generierten Java-Code des Servlets zu sehen. Dies trifft v.a. dann zu, wenn JSPs jede Menge Skriptlets enthalten. Besonders lohnend ist allerdings die Beschäftigung mit Servlets, wenn man eine Architektur mit FrontController nutzt (s. dazu das Kapitel Anwendungsarchitektur). Bei diesen wird der Request zunächst von einem Controller-Servlet vorbereitet, dann von diesem Servlet der benötigte Logik-Programmanteil angestoßen und schlussendlich wird zur Generierung der Antwort dann an eine JSPs weitergeleitet. Warum man diesen, auf den ersten Blick kompliziert aussehenden Vorgang überhaupt wählen sollte, ist Gegenstand des Kapitels "Anwendungsarchitektur". Aber klar ist, dass zur Umsetzung dieses Weges selbst bei Verwendung von Frameworks wie bspw. Struts letztlich ein Grundverständnis von Servlets benötigt wird.

Zum SeitenanfangZum Seitenanfang

Servlet-Container

Servlets sind zunächst einmal nichts anderes als ganz normale Java-Klassen, die ein bestimmtes Interface (javax.servlet.Servlet) implementieren. Damit diese Klassen wirklich auf HTTP-Anfragen eines Browsers reagieren und die gewünschte Antwort (zumeist, aber nicht zwingend, eine HTML-Seite) liefern können, müssen die Servlets in einer bestimmten Umgebung laufen. Diese Umgebung wird vom sogenannten Servlet-Container bereit gestellt.
Der Container sorgt zunächst einmal für den korrekten Lebenszyklus der Servlets (s.u.), für das Pooling von Instanzen (bspw. von Datasources, aber v.a. auch der Servlets selber) und dafür dass Konfigurations-Parameter ausgelesen und bereitgestellt werden. Bei Anfragen entscheidet der Container anhand von Konfigurationsparameter darüber, an welche Servlets die Anfragen geroutet werden, und stellt den aufgerufenen Servlets mittels zweier Objekte vom Typ javax.servlet.ServletRequest bzw. javax.servlet.ServletResponse Informationen über die Client-Anfrage (s. Kapitel Client-Anfragen) bzw. die Response zur Verfügung.
Zumeist arbeitet der Container dabei im Zusammenspiel mit einem Webserver. Der Webserver bedient z.B. die Anfragen nach statischem HTML-Content, nach Bildern, multimedialen Inhalten oder Download-Angeboten. Kommt hingegen eine Anfrage nach einem Servlet oder einer JSP herein, so leitet der Webserver diese an den Servlet-Container weiter. Dieser ermittelt das zugehörige Servlet und ruft dieses mit den Umgebungsinformationen auf (s. dazu etwas weiter unten). Ist das Servlet mit seiner Abarbeitung des Requests fertig, wird das Ergebnis zurück an den Webserver geliefert, der dieses dem Browser wie gewöhnlich serviert. Die Nutzerin bekommt von den Unterschieden nichts mit und für den Browser stellen sich alle Seiten einheitlich als Anfragen nach einer benannten Ressource dar. Das folgende Schaubild von Jörg Buchberger verdeutlicht dieses Vorgehen noch einmal anschaulich:

ext_reqresp.png
Servlet-Container bieten üblicherweise auch die Möglichkeit, als gewöhnliche Webserver zu dienen. Dann entfällt das oben beschriebene Zusammenspiel, und der Servlet-Container wird vom internen Webserver angesprochen. Beim Tomcat heißen diese beiden Komponenten bspw. Catalina (Servlet-Engine) und Coyote (Webconnector). Für stark belastete Systeme und besonders solche mit vielen statischen Ressourcen (bspw. Bildern) reichen diese internen Webserver i.a.R. aber nicht aus.
Im Kapitel JSP-Container wird für verschiedene Servlet-Container gezeigt, wie man diese dem Apache Webserver bekannt macht.

Zum SeitenanfangZum Seitenanfang

Packages

Die Servlet- und JSP-Packages sind nicht Bestandteil der Java Standard Edition, sondern gehören zu den Erweiterungen im Rahmen der Enterprise Edition.
Jedes der Pakete, die Bestandteil der Servlet- und JSP-Spezifikationen sind, wird i.F. benannt und es wird kurz erläutert, was in dem jeweiligen Package zu finden ist.

javax.servlet

Dieses Package enthält die grundlegenden Interfaces und Klassen. Wesentliche Interfaces hier sind v.a. die Interfaces Servlet, ServletRequest und ServletResponse. Hierbei wird in der Praxis zumeist auf abgeleitete Klassen und Interfaces aus dem Package javax.servlet.http (s. nächster Abschnitt) zurückgegriffen. Die Interfaces zur Konfiguration eines einzelnen Servlets (ServletConfig) und zur Repräsentation der Umgebung (ServletContext) befinden sich ebenfalls im Paket javax.servlet. Konkrete Implementierungen all dieser Interfaces werden vom Servlet-Container bereitgestellt. Die Entwicklerin nutzt aber ausschließlich die Interfaces der hier genannten Packages, damit die Servlets und JSPs wirklich portabel sind. Wer mittels Mock-Objekte testen will, findet hier zudem ein paar nützliche Wrapper für die ServletRequest- und ServletResponse-Interfaces (zum Testen von JSPs s. das Kapitel "Test-Tools").

javax.servlet.http

Wie der Name des Packages schon andeutet, befinden sich hier all die Klassen und Interfaces, die für die Entwicklung von Servlets zur Behandlung von HTTP-Anfragen benötigt werden. HTTP ist das einzige Protokoll, für das ein solches Package bereit gestellt wird. Und es ist eigentlich das einzige Protokoll, für das sich Servlets durchgesetzt haben. Daher hat man in aller Regel auch mit den Interfaces dieses Packages zu tun. D.h. man nutzt bspw. ein Objekt vom Typ HttpServletRequest. Oder erweitert die Klasse HttpServlet, die im nächsten Unterkapitel vorgestellt wird.

javax.servlet.jsp

Dies ist kein eigentliches Servlet-Package mehr. Es kommt ausschließlich beim Einsatz von JSPs zum Tragen. Wichtig sind hier die Klasse PageContext und das Interface HttpJspPage, das wiederum JspPage erweitert. In diesem Kapitel gehen wir hierauf jedoch nicht näher ein.

javax.servlet.jsp.el

Eigentlich nur wichtig für die Hersteller von JSP- und Servlet-Containern. Dieses Package stellt Klassen und Interfaces für die JSP-Expression Language bereit. Zur Expression Language selber s. das Kapitel Expression Language. Dort wird aber nicht auf die Klassen und Interfaces dieses Packages eingegangen, da diese doch sehr speziell sind.

javax.servlet.jsp.tagext

Ein zunehmend unwichtiger werdendes Package - jedenfalls für die meisten Entwicklerinnen - obwohl es in der Vergangenheit sogar extrem wichtig war. Hier werden Klassen und Interfaces definiert, die für die Bereitstellung von Tag-Libraries (Taglibs) benötigt werden. Taglibs sind - auch heute noch - eine wichtige Möglichkeit, JSPs von Skriptlets frei zu halten. Viele häufig benötigte Tags sind allerdings inzwischen Bestandteil der Java Standard Tag Library (JSTL). Wir gehen auf Taglibs ausführlich im Kapitel "Tag-Libraries" ein. Die JSTL behandeln wir im Kapitel "Java Standard Tag Library (JSTL)".

Zum SeitenanfangZum Seitenanfang

Die wichtigsten Interfaces und Klassen

Es ist an dieser Stelle unmöglich, alle Klassen und Interfaces der Servlet- und JSP-Spezifikation vorzustellen. Wir empfehlen daher dringend, sich die API der Java Enterprise Edition herunterzuladen. Sie ist für die jeweils aktuelle Version der J2EE-Spezifikation hier zu finden: http://java.sun.com/j2ee/reference/api/. Im folgenden listen wir die Interfaces und Klassen auf, die einem bei der Arbeit mit JSPs bzw. Servlets immer wieder begegnen.

Servlet, GenericServlet und HttpServlet

javax.servlet.Servlet ist das Interface, das letztendlich entscheidet, ob eine Java-Klasse überhaupt ein Servlet ist. Jedes Servlet muss dieses Interface direkt oder indirekt implementieren. De facto wird man aber wohl in den meisten Fällen nicht das Interface selber implementieren, sondern auf javax.servlet.GenericServlet (falls man kein Servlet für HTTP-Anfragen schreibt), bzw. javax.servlet.http.HttpServlet zurückgreifen. HttpServlet implementiert alle wesentlichen Methoden. Man muss nur noch die Methoden doGet, doPost oder doXYZ überschreiben, je nachdem, welche HTTP-Methoden man unterstützen will. Wie man diese Klassen erweitert, sehen wir etwas weiter unten im Unterkapitel "Die Methode service() und die doXyz-Methoden" in einem Beispiel-Servlet.

ServletRequest und HttpServletRequest

Sowohl in den Verarbeitungsmethoden von Servlets (service(), doGet(), doPost()...) als auch in JSPs hat man stets Zugriff auf ein Objekt vom Typ (Http)ServletRequest. Arbeitet man mit HttpServlets bzw. JSPs handelt es sich um das Interface javax.servlet.http.HttpServletRequest, ansonsten um das Interface javax.servlet.ServletRequest. Diese Interfaces stellen wesentliche Informationen über die Anfrage des Clients zur Verfügung und werden detailliert im Kapitel "Client-Anfragen" behandelt.
Man implementiert selber nie diese Interfaces (wenn man nicht gerade einen Servlet-Container erstellt), vielmehr stellt der jeweilig benutzte Container konkrete Implementierungen dieser Interfaces für die Entwicklerin transparent zur Verfügung. Grundlage des eigenen Codes sollten immer die Interfaces selbst sein, niemals die konkrete Implementierung eines Container-Herstellers.

ServletResponse und HttpServletResponse

Letztlich geht es bei JSPs und Servlets immer darum, auf eine Client-Anfrage mit einer Antwort zu reagieren. Um diese zu generieren, benötigt man ein Objekt vom Typ (Http)ServletResponse. Während man aber auf das ServletRequest-Objekt sehr häufig zurückgreift, benötigt man das ServletResponse-Objekt vergleichsweise selten. Es dient bspw. im Falle eines Fehlers dazu, einen anderen HTTP-Statuscode als "200" zu setzen (zu den verschiedenen HTTP-Statuscodes s. den Anhang I: HTTP-Grundlagen). Desweiteren kann man HTTP-Header mit dem Response-Objekt setzen. Auch dies ist eher unüblich. Die wesentlichen Header, die auch im obigen Screenshot am Anfang des Kapitels zu sehen sind, werden alle vom Container gesetzt. Man selber greift hier eher selten ein. Ein paar Methoden sind aber doch wichtig und werden ausführlich in den passenden Kapiteln behandelt. Erwähnt seien hier die Methoden sendRedirect(String) zur Auslösung eines Redirects, setContentType(String) zur Setzung des Mime-Types der Antwort oder setCharacterEncoding(String) zur Setzung des richtigen Zeichensatz-Encodings (bspw. UTF-8 oder ISO-8859-1).
Schußendlich muss das Servlet seine Ausgabe auch irgendwohin schreiben. Dazu holt man sich in Servlets mit der Methode "getOutputStream()" ein Objekt vom Typ ServletOutputStream (für binäre Inhalte wie bspw. generierte PDF-Dateien oder Bilder) oder mit der Methode getWriter() ein Objekt vom Typ PrintWriter, wenn man als Antwort textuelle Daten generiert (bspw. HTML-Code oder XML-Daten). In JSPs hat man mit dem impliziten Objekt "out" immer ein Objekt vom Typ PrintWriter zur Verfügung. Dazu mehr im Unterkapitel "ServletOutputStream und PrintWriter"etwas weiter unten.
Wie schon beim ServletRequest geschrieben gilt auch bei der ServletResponse, dass konkrete Implementierungen vom Container-Hersteller kommen und man selber immer gegen das Interface entwickelt.

HttpSession

Im Anhang I: HTTP-Grundlagen wurde bereits darauf hingewiesen, dass das HTTP-Protokoll statuslos ist. Dies bedeutet, dass für den Server ohne (längst üblich gewordene) Tricks nicht erkennbar ist, ob ein Request von einer bekannten Nutzerin kommt oder nicht. Dies ist für nahezu alle Anwendungen allerdings nicht ausreichend. Nutzerinnen müssen sich bspw. mittels PIN und Kennwort legitimieren können, um Bestellungen zu tätigen, Foren-Beiträge verfassen zu können, administrative Aufgaben mittels eines Web-Interfaces vornehmen zu können etc. Mittels Cookies oder URL-Rewriting kann man daher einzelne Requests zu Sitzungen (Sessions) zusammenfassen. Genauer gehen wir darauf im Kapitel Session-Handling, Cookies und URL-Rewriting ein. Das HttpSession-Objekt, das in JSPs auch als das implizite Objekt "session" zur Verfügung steht, kapselt dabei wesentliche Informationen zur Session und ermöglicht v.a. den Zugriff auf Session-Attribute, die den Status einer Sitzung widerspiegeln.

ServletConfig

Servlets werden im Deployment-Deskriptor konfiguriert. Dort wird zumindest festgelegt, für welche URLs ein Servlet zuständig sein soll. Darüber hinaus können Servlets allerdings auch Initialisierungs-Parameter mitgegeben werden. Der Zugriff auf diese erfolgt mittels der Methoden getInitParameter(String), und getInitParameterNames() des Interfaces javax.servlet.ServletConfig. Die erste Methode liefert einen String zurück, der im Deployment-Deskriptor dem übergebenen Namen zugeordnet wurde. Die zweite Methode liefert eine Enumeration über die Namen aller Initialisierungsparameter. Ein Objekt vom Typ ServletConfig wird der init(ServletConfig)-Methode jedes Servlets mitgegeben (s. dazu auch den nächsten Abschnitt über den Lebenszyklus).
Vom Design her ungewöhnlich ist, dass die Klasse javax.servlet.http.HttpServlet das Interface ServletConfig implementiert. Somit ist aus allen Servlets, die diese Klasse erweitern, einfach mittels getInitParameter(String) der Zugriff auf die Parameter möglich. Dies mag zwar bequem sein, sauber designed ist dies allerdings nicht.

ServletContext

Das Interface javax.servlet.ServletContext definiert Methoden zum Zugriff auf Ressourcen im Dateisystem, zum Zugriff auf Informationen über die Servlet-Engine und liefert einem einen RequestDispatcher, um einen Request zu forwarden, d.h. zur Bearbeitung an ein anderes Servlet zu übergeben (ohne dass die Nutzerin dies mitbekommt), oder das Ergebnis anderer Servlets oder JSPs zu inkludieren. Alle wesentlichen Aspekte von ServletContext werden in diversen anderen Kapiteln vorgestellt. Ein Blick in die API lohnt sich freilich immer ;-)
Ein Objekt vom Typ ServletContext wird in den JSPs als implizites Objekt "application" (s. Kapitel Implizite Objekte) zur Nutzung bereit gestellt.

Filter

Die Servlet-Spezifikation sieht die Möglichkeit vor, vor und nach der Ausführung von Servlets eine Filterkette zu durchlaufen. Dabei müssen etwaige Filter das Interface javax.servlet.Filter implementieren. Der genaue Vorgang der Erstellung und Konfiguration von Filtern wird detailliert im Kapitel "Servlet-Filter" dargestellt.

Cookie

Cookies sind eine häufig genutzte Möglichkeit, um den Client eindeutig identifizieren zu können und zudem auf dem Client Informationen zu hinterlegen, die für diese oder weitere Sitzungen von Bedeutung sein können. Darauf gehen wir ausführlich im Kapitel "Session-Handling, Cookies und URL-Rewriting" ein. Die Klasse javax.servlet.http.Cookie bietet bei der Nutzung von Cookies eine Abstraktion dieser Informationen und erleichtert den Umgang mit Cookies.

ServletException

Wenn Exceptions innerhalb eines Servlets auftreten, stellt sich die Frage, wie man am besten damit umgeht,. Es ist nicht möglich, eine Checked Exception aus der Methode service(ServletRequest, ServletResponse) heraus zu werfen, da in der Methoden-Definition lediglich ServletException in der throws-Klausel erwähnt wird. Es gibt nun mehrere Möglichkeiten, mit dieser (von Sun absichtlich eingefügten) Beschränkung umzugehen. Zunächst einmal könnte man in jedem Servlet Exceptions fangen und behandeln. Zum zweiten könnte man auftretende Exceptions fangen, in eine ServletException wrappen und mittels deklarativer Fehler-Behandlung für URL-Mappings oder Exception-Typen Seiten bereit stellen, die der Nutzerin dann angezeigt werden. Wir gehen auf den Umgang mit Fehler- und Ausnahmesituationen im Kapitel "Fehler- und Ausnahmenbehandlung" ein.

Zum SeitenanfangZum Seitenanfang

Servlet-Lebenszyklus

Weiter oben wurde schon geschrieben, dass ein Servlet von einem Servlet-Container verwaltet wird. Erwähnt wurde ebenfalls, dass Client-Anfragen abhängig von der Konfiguration vom Container an das passende Servlet geleitet werden. Aber das ist nicht alles. Und zudem bedarf auch das noch einer Erklärung.
Ein Servlet wird über den sogenannten Deployment-Deskriptor dem Container bekannt gemacht. Der Deployment-Deskriptor ist einfach eine XML-Datei, die stets unter dem in der Servlet-Spezifikation festgelegten Namen "web.xml" im ebenfalls dort festgelegten Verzeichnis "WEB-INF" abgelegt werden muss (s. dazu die Kapitel "Verzeichnisstruktur von Java-Webanwendungen" und "Die zentrale Konfigurationsdatei "web.xml"").
Nehmen wir an, wir hätten ein Servlet "SimpleServlet" im Package "org.jsptutorial.examples.servlets". Dieses Servlet wollen wir unter der URL "www.jsptutorial.org/examples/SimpleServlet" aufrufen. Dann würden wir dazu folgenden Deployment-Deskriptor schreiben:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!--
   This is the central configuration file for
   any servlet container. It must be located in
   the WEB-INF-subdirectory of your webapp.
-->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
    "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">


   <!-- ... -->


   <!-- servlet definition -->
   <servlet>
      <servlet-name>SimpleServlet</servlet-name>
      <servlet-class>org.jsptutorial.examples.servlets.SimpleServlet</servlet-class>
   </servlet>

   <!-- servlet mapping -->
   <servlet-mapping>
      <servlet-name>SimpleServlet</servlet-name>
      <url-pattern>/examples/SimpleServlet</url-pattern>
   </servlet-mapping>

   
   <!-- ... -->

</web-app>

Dies würde dem Servlet-Container mitteilen, das jeder Aufruf an die URL

contentRoot/examples/SimpleServlet

an ein Objekt vom Typ org.jsptutorial.examples.SimpleServlet weitergeleitet werden müsste.
Der Container reicht aber nicht nur einfach Aufrufe weiter. Er instanziiert zudem das Servlet, ruft die Lebenszyklus-Methoden auf und sorgt für die korrekte Umgebung. Jedes Servlet durchläuft dabei in der Spezifikation genau definierte Phasen:

  1. Laden der Servlet-Klasse
    Zunächst muss der Classloader des Containers die Servlet-Klasse laden. Wann der Container dies macht, bleibt ihm überlassen, es sei denn, man definiert den Servlet-Eintrag Deployment-Deskriptor so wie oben geschehen. Mit dem optionalen Eintrag "load-on-startup" wird definiert, dass der Servlet-Container die Klasse bereits beim Start des Containers lädt. Die Zahlen geben dabei die Reihenfolge vor (Servlets mit niedrigeren "load-on-startup"-Werten, werden früher geladen).

  2. Instanziieren
    Unmittelbar nach dem Laden wird das Servlet instanziiert, d.h. der leere Konstruktor aufgerufen. Am besten verwendet man gar keinen eigenen expliziten Konstruktor, da Initialisierungsschritte ohnehin, wie im nächsten Teilschritt erwähnt, in der init()-Methode vorgenommen werden.

  3. init(ServletConfig)
    Bevor der erste Request an das Servlet weiter gereicht wird, wird dieses initialisiert. Dazu wird die Methode init(ServletConfig) des Servlets aufgerufen. Diese Methode wird genau einmal im Lebenszyklus eines Servlets und nicht etwa vor jedem Request aufgerufen und dient dazu, grundlegende Konfigurationen vorzunehmen. Dem Servlet wird dabei ein Objekt vom Typ javax.servlet.ServletConfig (s.o.) mitgegeben. Nach Abarbeiten der init()-Methode ist das Servlet bereit, Anfragen entgegen zu nehmen und zu beantworten.

  4. service(ServletRequest, ServletResponse)
    Für jede Anfrage eines Clients an ein entsprechend definiertes Servlet, wird die Methode service(ServletRequest, ServletResponse) aufgerufen. Servlet-Container halten zumeist nur genau eine Instanz pro Servlet. Somit wir jeder Request in einem eigenen Thread aufgerufen. Die daraus entstehenden Threading-Issues werden im Kapitel "MultiThreading im Servlet-/JSP-Kontext" behandelt.
    Alle wesentliche Informationen, die die Client-Anfrage betreffen, befinden sich im ServletRequest-Objekt. Und zur Erzeugung der Antwort nutzt man Methoden des ServletResponse-Objekts.
    In eigenen Servlets für HTTP-Anfragen (und das ist sicherlich der Regelfall für selbstgeschriebene Servlets) sollte man allerdings nie die service()-Methode selber überschreiben. Vielmehr bietet es sich an, die eigenen Klasse von HttpServlet abzuleiten und dessen Methoden doGet(), doPost() etc. zu überschreiben. Dadurch wird das korrekte Handling der diversen HTTP-Methoden (s. dazu Anhang I: HTTP-Grundlagen) automatisch vom Container übernommen.

  5. destroy()
    Der Container kann jederzeit eine Servlet-Instanz als überfällig ansehen und diese aus dem Request/Response-Zyklus entfernen. Dazu ruft er am Ende des Lebenszyklus der Servlet-Instanz deren destroy()-Methode auf und gibt damit dem Servlet die Möglichkeit, etwaig gehaltene Ressourcen (bspw. Datenbank-Connections) freizugeben. Der Container muss zuvor dem Servlet allerdings noch die Gelegenheit geben, seine in der Bearbeitung befindlichen Requests abzuarbeiten, bzw. zumindest einen definierten Timeout abwarten, bevor er die Abarbeitung unterbricht.
    Ist einmal die Methode destroy() aufgerufen, steht diese Servlet-Instanz nicht mehr für weitere Anfragen zur Verfügung. Was aber nicht heißt, dass der Container nicht durchaus weitere Instanzen halten könnte - ja der Container muss sogar, solange er für Anfragen bereit steht, auch passende Servlet-Instanzen bereit halten. Da Servlet-Container im Regelfall genau eine Instanz eines jeden im Deployment-Deskriptor definierten Servlets halten, wird die init()-Methode zumeist vor dem ersten Aufruf der service()-Methode und die destroy()-Methode beim Beenden des Servlet-Containers aufgerufen.

Zum SeitenanfangZum Seitenanfang

Die Methode service() und die doXyz()-Methoden

Das in diesem Abschnitt Beschriebene gilt ausschließlich für Servlets, die HttpServlet erweitern, also HTTP-Anfragen bedienen und die Interfaces und Klassen des Packages javax.servlet.http nutzen. Im Rahmen der Servlet-Entwicklung und erst recht in Kombination mit der Nutzung von JSPs ist dies allerdings der Normalfall.
Wie weiter oben schon geschrieben, ruft der Container immer die service(HttpServletRequest, HttpServletResponse)-Methode eines Servlets auf. Im Falle von HttpServlet analysiert diese Methode zunächst, welche HTTP-Methode (GET, POST, HEAD... - s. dazu Anhang I: HTTP-Grundlagen) vom Client genutzt wurde. Zumeist dürften das GET-Requests sein, bei Formularen mitunter auch POST-Requests. Die anderen HTTP-Methoden sind eher für die Servlet-Engine und/oder den vorgeschalteten Webserver interessant. Anhand der verwendeten HTTP-Methode leitet die service()-Methode nun den Request an entsprechende Handler-Methoden weiter. Diese heißen analog zur HTTP-Methode doGet(), doPost(), doHead(), usw. und haben allesamt die gleichen Parameter, wie die service()-Methode.
Wir zeigen im Folgenden an einem simplen Beispiel, wie man idealerweise vorgehen sollte. Dabei wird unterstellt, dass Anfragen sowohl mittels GET- als auch mittels POST-Anfragen gestellt werden können und die Antwort in beiden Fällen gleich sein soll.

package org.jsptutorial.examples.servlets;

import javax.servlet.http.*;
import javax.servlet.*;

import java.io.*;

/**
 * A simple Servlet for demonstration-purposes only.
 */
public class SimpleServlet extends HttpServlet {

   public void doGet(HttpServletRequest request, HttpServletResponse response)
           throws IOException, ServletException{

      // 1. process request-parameters and -headers...
      String userName = request.getParameter("userName");
      if (userName == null) {
         userName = "Stranger";
      }
      String client = request.getHeader("User-Agent");
      // 2. prepare the response...
      response.setContentType("text/html");
      // 3. get access to a PrintWriter or to an OutputStream...
      PrintWriter out = response.getWriter();
      // 4. write to PrintWriter or OutputStream...
      out.println("<html>");
      out.println("<head>");
      out.println("<title>JSP Tutorial - SimpleServlet</title>");
      out.println("</head>");
      out.println("<body>");
      out.println("Hello " + userName + "<br>You are using: " + client);
      out.println("</body></html>");
   }
   
   
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
      // let doGet() do all the work...
      doGet(request, response);
   }
   
}

Code im Extrafenster ausführen

Auf den ersten Blick sieht es so aus, als ob mit dem Überschreiben von doGet() und doPost() nichts gewonnen wäre, bzw. u.U sogar mehr Arbeit entsanden wäre. Aber das hängt damit zusammen, dass man sich normalerweise nur um die GET- und POST-Requests kümmert. Aber der Container muss auch korrekt auf HEAD-, OPTIONS-, PUT- und TRACE-Anfragen antworten. Und dafür existieren mit den entsprechenden doHead()- doOptions()-, doPut()- und doTrace()-Methoden geeignete Default-Methoden. Würde man nun service() direkt überschreiben, so würden diese Anfragen inkorrekt beantwortet - alle Anfragen würden gleich beantwortet. Insbesondere bei HEAD-Anfragen, die für das Caching von Bedeutung sind, bedeutet dies unnötigen Netzwerktraffic. Es empfiehlt sich daher dringend, wirklich niemals die service()-Methode zu überschreiben, sondern sich auf doGet() und doPost() zu beschränken.
Es lohnt sich auch unter diesem Gesichtspunkt, sich den Source von HttpServlet anzusehen. Dazu kann man auf docjar.com zurückgreifen, einem Service, mit dem zahlreiche Java-OpenSource-APIs angesehen werden können. In unserem Fall ist der Code von javax.servlet.http.HttpServlet Bestandteil der Referenz-Implementation (Tomcat) und kann hier gefunden werden: http://www.docjar.com/html/api/javax/servlet/http/HttpServlet.java.html.

Zum SeitenanfangZum Seitenanfang

ServletOutputStream und PrintWriter

Jede Anfrage eines Clients erwartet logischerweise auch eine Antwort. Dazu stehen uns über das javax.servlet.ServletResponse-Interface der Zugriff entweder auf einen PrintWriter oder auf einen ServletOutputStream zur Verfügung. Die entsprechenden Methoden heißen getOutputStream() für den ServletOutputStream und getWriter() für den PrintWriter.
Es ist unbedingt zu beachten, dass sich die beiden Methoden gegenseitig ausschließen: Wird auf beide Methoden während der Bearbeitung eines Requests zugegriffen, so wirft die als zweite aufgerufene Methode eine IllegalStateException. Dies ist allerdings praktisch kaum relevant, da beide Objekte für jeweils andere Antwort-Typen geeignet sind. So wird man für Anfragen nach binären Objekten, wie bspw. Bildern oder PDFs, den ServletOutputStream nutzen. Hingegen ist der PrintWriter eindeutig besser geeignet, um rein textuellen Output (wie XML- oder HTML-Code ) zu erzeugen. In JSPs wird standardmäßig ein PrintWriter mittels des impliziten Objekts "out" zugänglich gemacht (s. dazu das Kapitel Implizite Objekte).
Beide Objekte puffern die Inhalte, bevor sie zum Client übertragen werden. Die Größe des Puffers kann dabei über die Methode setBufferSize(int) des HttpServletResponse-Objekts geändert werden, zudem kann man dort den Puffer mittels resetBuffer() zurücksetzen. Das Konzept des Puffers ist extrem wichtig. Denn als erstes enthält jede Antwort immer alle HTTP-Header (s. dazu den Anhang I: HTTP-Grundlagen). Würde also direkt die Antwort schon beim ersten Nutzen des ServletOutputStreams oder des PrintWriters an den Client durchgereicht, so müssten vorher die Header gesendet werden. Damit aber könnte auf Fehler bspw. bei HTTP nicht mehr mit dem Senden eines entsprechenden StatusCodes (mittels sendError(int) oder setStatusCode(int) des HttpServletResponse-Objekts) reagiert werden. Auch eine Weiterleitung der Anfrage an ein anderes Servlet ist nur möglich, solange noch kein Content an den Client gesendet wurde.
Anders formuliert, sollte man mit dem Schreiben der Antwort immer so lange warten, wie nur eben möglich (also bspw. sollten alle Datenbank- und sonstige Backend-Zugriffe vorher erfolgt sein), um auf Fehler benutzter Services und Ressourcen noch reagieren zu können.
Am Ende, wenn der Content vollständig geschrieben an das PrintWriter- oder ServletOutputStream-Objekt übergeben wurde, wird die Response durch Aufruf der Methode "flush()" auf dem entsprechenden Writer oder OutputStream-Objekt committet. In der ServletSpezifikation wird auf dieses Committen 60 mal Bezug genommen - ein weiterer Hinweis, wie wichtig es ist, sich mit der Bedeutung des Puffers und des Committens der Response zu beschäftigen.

Zum SeitenanfangZum Seitenanfang

Umwandlung einer JSP in ein Servlet

Abschließend wollen wir kurz zwei SourceCodes zeigen. Es handelt sich hier um eine einfache JSP und um den Servlet-Code, der daraus entsteht. Dieser Servlet-Code ist nur ein Beispiel. Der konkrete Code ist abhängig vom Container. Dies ist insofern unproblematisch, als die Entwicklerin immer ausschließlich mit dem JSP-Code arbeitet. Und die konkrete Realisierung der Umsetzung einer JSP in ein Servlet kann ein Unterscheidungsmerkmal zwischen verschiendenen Container-Herstellern sein (wobei in der Praxis dafür wohl eher andere Punkte wie bspw. Administrations-Tools und die Integration in die Server-Landschaft des Unternehmens eine wichtige Rolle spielen).
Das Beispiel hier ist das Ergebnis einer JSP-Transformation durch den Tomcat JSP-Compiler Jasper in der Tomcat-Version 5.0.28. Als Beispiel dient uns dabei die schon im Kapitel Die Syntax von JSPs teilweise vorgestellte JSP, die die Syntax der Deklarationen verdeutlicht. Die JSP als solche macht kaum Sinn, zeigt dafür aber hervorragend, wie die verschiedenen syntaktischen Elemente übersetzt werden.

<%--
     Make the JSTL-core and the JSTL-fmt taglibs available
     within this page. JSTL is the Java Standard Tag Library,
     which defines a set of commonly used elements
     The prefix to be used for core-tags is "c"
     The prefix to be used for fmt-tags is "fmt"
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%! // JSP-declaration-tag 

/** This constant defines the maximal allowed number. */
private static int MAX_VALUE = 65535;

/**
 * This method checks whether an value
 * is inside the positive numbers 
 * up to MAX_VALUE.
 */
private boolean isInRange(int var) {
   if (var < 0 || var > MAX_VALUE) {
      return false;
   }
   return true;
}
%>

<html>
<head><title>JSP Tutorial - small declaration example</title></head>
<body>
   <%-- The following is just a local variable --%>
   <% int random = (int)(Math.pow(2, 17) * Math.random()); %>
   <%-- The local variable is passed to the global method --%>
   The random number <%= random %> is within the given range: <%= isInRange(random) %>
   
   <br><br>
   
   <%-- The global constant is of course accessible as well --%>
   The constant MAX_VALUE is <%= MAX_VALUE %>.
   
   <br><br>
   
   All Request-Parameters as a Query-String using JSTL:
   <c:out value="${pageContext.request.queryString}" />
</body>
</html>

Code im Extrafenster ausführen

Und nun das daraus generierte Servlet:
/*
    slightly changed only to fit onto a page
*/


package org.apache.jsp.examples;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class code_005ffull_005fdeclaration_005fexample_jsp
    extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

 // JSP-declaration-tag

/** This constant defines the maximal allowed number. */
private static int MAX_VALUE = 65535;

/**
 * This method checks whether an value
 * is inside the positive numbers
 * up to MAX_VALUE.
 */
private boolean isInRange(int var) {
   if (var < 0 || var > MAX_VALUE) {
      return false;
   }
   return true;
}

  private static java.util.Vector _jspx_dependants;

  private
      org.apache.jasper.runtime.TagHandlerPool _jspx_tagPool_c_out_value_nobody;

  public java.util.List getDependants() {
    return _jspx_dependants;
  }

  public void _jspInit() {
    _jspx_tagPool_c_out_value_nobody =
      org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());
  }

  public void _jspDestroy() {
    _jspx_tagPool_c_out_value_nobody.release();
  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

    JspFactory _jspxFactory = null;
    PageContext pageContext = null;
    HttpSession session = null;
    ServletContext application = null;
    ServletConfig config = null;
    JspWriter out = null;
    Object page = this;
    JspWriter _jspx_out = null;
    PageContext _jspx_page_context = null;


    try {
      _jspxFactory = JspFactory.getDefaultFactory();
      response.setContentType("text/html");
      pageContext = _jspxFactory.getPageContext(this, request, response,
               null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write('\n');
      out.write('\n');
      out.write('\n');
      out.write("\n");
      out.write("\n");
      out.write("<html>\n");
      out.write
         ("<head><title>JSP Tutorial - small declaration example</title></head>\n");
      out.write("<body>\n");
      out.write("   ");
      out.write("\n");
      out.write("   ");
 int random = (int)(Math.pow(2, 17) * Math.random());
      out.write("\n");
      out.write("   ");
      out.write("\n");
      out.write("   The random number ");
      out.print( random );
      out.write(" is within the given range: ");
      out.print( isInRange(random) );
      out.write("\n");
      out.write("   \n");
      out.write("   <br><br>\n");
      out.write("   \n");
      out.write("   ");
      out.write("\n");
      out.write("   The constant MAX_VALUE is ");
      out.print( MAX_VALUE );
      out.write(".\n");
      out.write("   \n");
      out.write("   <br><br>\n");
      out.write("   \n");
      out.write("   All Request-Parameters as a Query-String using JSTL:\n");
      out.write("   ");
      if (_jspx_meth_c_out_0(_jspx_page_context))
        return;
      out.write("\n");
      out.write("</body>\n");
      out.write("</html>");
    } catch (Throwable t) {
      if (!(t instanceof SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          out.clearBuffer();
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
      }
    } finally {
      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }

  private boolean _jspx_meth_c_out_0(PageContext _jspx_page_context)
          throws Throwable {
    PageContext pageContext = _jspx_page_context;
    JspWriter out = _jspx_page_context.getOut();
    //  c:out
    org.apache.taglibs.standard.tag.rt.core.OutTag
         _jspx_th_c_out_0 = (org.apache.taglibs.standard.tag.rt.core.OutTag)
            _jspx_tagPool_c_out_value_nobody.
            get(org.apache.taglibs.standard.tag.rt.core.OutTag.class);
    _jspx_th_c_out_0.setPageContext(_jspx_page_context);
    _jspx_th_c_out_0.setParent(null);
    _jspx_th_c_out_0.setValue((java.lang.Object)
         org.apache.jasper.runtime.PageContextImpl.
         proprietaryEvaluate("${pageContext.request.queryString}",
         java.lang.Object.class, (PageContext)_jspx_page_context, null, false));
    int _jspx_eval_c_out_0 = _jspx_th_c_out_0.doStartTag();
    if (_jspx_th_c_out_0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
      return true;
    _jspx_tagPool_c_out_value_nobody.reuse(_jspx_th_c_out_0);
    return false;
  }
}

Erkennbar ist, dass am Anfang der _service-Methode zunächst die impliziten Objekte zur Verfügung gestellt werden. Dann folgt die eigentliche Ausgabe, die in der Form mehrerer out.println()-Zeilen erkennbar ist. In der JSP eingebetteter Skriptlet-Code wird einfach eins zu eins übertragen. Ebenfalls deutlich sichtbar sind die innerhalb des Deklarations-Tags definierte Methode und die globale Variable. Zudem können wir sehen, was Tomcats JSP-Compiler Jasper aus Custom-Tags (hier ein JSTL-Tag) macht. Kommentare innerhalb von Skriptlets oder Deklarationen werden eins zu eins übernommen, JSP-Kommentare (<%-- Kommentar --%>) hingegen nicht.
Glücklicherweise kann man meist ignorieren, was für ein Code aus der JSP generiert wurde. Allerdings kommt es mitunter - besonders bei intensivem Gebrauch von Skriptlets - zu Situationen, in denen man Fehlerfälle besser anhand des generierten Codes versteht. In dem Fall lohnt es sich, in der Server-Dokumentation nachzusehen, an welcher Stelle solcher Code abgelegt wird. Im Falle von Tomcat findet man den Java-Code übersetzter JSPs unterhalb des tomcat_root/work/Catalina-Verzeichnises. Der genaue Pfad ist zudem noch abhängig vom Server-Namen und dem Kontext-Pfad. Für localhost und dem Root-Kontext befinden sich die Sourcen bspw. hier:

tomcat_root/work/Catalina/localhost/_/org/apache/jsp/

Hier steht "/_" für den Root-Kontext. Bei einem anderen Kontext-Pfad ersetzt man einfach das "/_" durch den entsprechenden Pfad.

Zum SeitenanfangZum Seitenanfang


www.jsptutorial.org
© 2005, 2006, 2007