Beispiel: JSP-Grundlagen
Inhalt:
Allgemeiner Code
Beispiel 1: JSP-Grundlagen
Beispiel 2: Controller-Servlet
Beispiel 3: Tag Libraries
Beispiel 4: Custom Tag Libraries
Beispiel 5: Tag Files
Diverse JSP-Beispiele für WildFly 24 und JakartaEE 8.
Eine aktuelle Version für JakartaEE10 und WildFly 33 findet sich
hier.
Alle diese Beispiele arbeiten völlig ohne EJB-Zugriff.
Sie führe die gleiche Logik aus wie das Beispiel der Stateful Session Bean: Sie errechnen Oberfläche und Volumen eines Quaders.
Beim Anlegen des Projekts wird als Projekttyp hier immer ein "Dynamic Web Project" verwendet.
Die Checkbox "Add project to an EAR" darf dabei nicht gesetzt sein (das ist der Default).
In Schritt 2 können wir alle Einstellungen bei den Default-Werten belassen.
Der Export und Re-Import des Projekts erfolgt diesmal nicht als "EAR file" sondern als
"WAR file", ansonsten ist das Vorgehen identisch. Beim Import ist zu beachten dass
das Projekt wiederum nicht zu einer Enterprise Application zugefügt wird.
In Beispiel 3 verwenden wir TagLibraries, deren JAR-Dateien stecken im Unterverzeichnis "WebContent/WEB-INF/lib".
Beim Re-Import müssen wir beachten dass die JARs nicht als Utility-Projekte importiert werden sondern
als JARs in Verzeichnis "WEB-INF/lib" gepackt werden (d.h. in Schritt 2 des Importers keine Haken setzen).
Allgemeiner Code
Alle folgenden Beispiele verwenden zwei Hilfsklassen (die jeweils in einem an das Beispiel angepaßten Package stecken).
Die Klasse Seitenlaengen
enthält die drei Seitenlängen A, B und C:
package de.fhw.komponentenarchitekturen.knauf.jsp1;
public class Seitenlaengen
{
private double dblA;
private double dblB;
private double dblC;
public double getA()
{
return dblA;
}
public void setA(double dblA)
{
this.dblA = dblA;
}
public double getB()
{
return dblB;
}
public void setB(double dblB)
{
this.dblB = dblB;
}
public double getC()
{
return dblC;
}
public void setC(double dblC)
{
this.dblC = dblC;
}
}
Die Klasse Historie
enthält eine Liste von Seitenlaengen
:
package de.fhw.komponentenarchitekturen.knauf.jsp1;
import java.util.Iterator;
import java.util.Vector;
public class Historie
{
private Vector<Seitenlaengen> vectorBerechnungen = new Vector<Seitenlaengen>();
public void addSeitenlaenge (Seitenlaengen seitenlaengeAktuell)
{
this.vectorBerechnungen.add( seitenlaengeAktuell);
}
public Iterator<Seitenlaengen> iterator()
{
Iterator<Seitenlaengen> iterator = this.vectorBerechnungen.iterator();
return iterator;
}
}
Beispiel 1: JSP-Grundlagen
Dieses Beispiel enthält drei JSP-seiten ("index.jsp", "input.jsp" und "errorpage.jsp") und die Klassen
de.fhw.komponentenarchitekturen.knauf.jsp1.Historie
und de.fhw.komponentenarchitekturen.knauf.jsp1.Seitenlaengen
.
Hier gibt es das Projekt als WAR-Export-Datei: JSP1.war.
Das Beispiel soll folgende Grundelemente einer JSP zeigen:
- Das
@page
-Attribut sieht so aus (im folgenden um zwei Attribute erweitert, die es nicht im Beispielcode gibt):
<%@page language="java"
import="de.fhw.komponentenarchitekturen.knauf.jsp1.Seitenlaengen,java.util.Iterator"
contentType="text/html; charset=windows-1252"
pageEncoding="windows-1252"
errorPage="/errorpage.jsp"
session="true"%>
Das "language"-Attribut gibt an, in welcher Sprache eventuelle Codeschnipsel umgesetzt sind (mir ist nur "java" bekannt). Man kann dieses Attribut weglassen,
so wie in meinem Beispiel geschehen.
Das "import"-Attribut ist selbsterklärend und funktioniert wie das Statement in Java. Die einzelnen Imports sind kommasepariert.
Der "contentType" gibt an, welchen ContentType und welches Encoding der Server für die Header der HTTP-Response setzen soll. Im Beispiel kommt also HTML im Zeichensatz "windows-1252" zurück.
Das "pageEncoding" ist ebenfalls für den Server bestimmt und gibt an, in welchem Zeichensatz die JSP-Datei vorliegt (bzw. erstellt wurde). Dieser Wert
muss nicht unbedingt identisch mit dem "contentType" sein, sondern kann abweichen.
"session=true" definiert, dass diese Seite Teil einer (automatisch vom Server verwalteten) HTTP-Session sein soll.
"errorPage" definiert eine JSP-Seite, die im Falle einer Exception auf der aktuellen Seite aufgerufen wird (siehe weiter unten)
- Verwenden des Session-Contexts zum Speichern der bisherigen Berechnungen mithilfe eines "jsp:useBean"-Tags.
Dies sieht so aus:
<jsp:useBean id="historie"
class="de.fhw.komponentenarchitekturen.knauf.jsp1.Historie" scope="session">
</jsp:useBean>
Beim ersten Aufruf der Seite wird im Session Context eine neue Instanz von Historie
als Variable namens "historie" auf der JSP-Seite zur Verfügung gestellt (deshalb benötigt die Klasse Historie
einen parameterlosen Konstruktor),
und außerdem unter dem Namen "historie" als Attribut in den Session Context gelegt .
Bei allen weiteren Aufrufen der Seite wird die Variable "historie" mit dem Wert aus dem Session Context befüllt.
- "jsp:useBean"-Tag für das Erzeugen einer Java-Klasse im Page Context und automatisches Befüllen aus Request-Parametern.
<jsp:useBean id="seitenlaenge"
class="de.fhw.komponentenarchitekturen.knauf.jsp1.Seitenlaengen" scope="page">
<jsp:setProperty property="a" name="seitenlaenge" param="a"></jsp:setProperty>
<jsp:setProperty property="b" name="seitenlaenge" param="b"></jsp:setProperty>
<jsp:setProperty property="c" name="seitenlaenge" param="c"></jsp:setProperty>
</jsp:useBean>
Da der Scope hier auf "page" steht, wird diese Bean bei jedem Aufruf der Seite neu erzeugt und nur innerhalb der Seite als Variable "seitenlaenge"
zur Verfügung gestellt. Die Properties der Klasse Seitenlaengen
("getA" / "setA", "getB" / "setB", "getC" / "setC") werden über das Tag jsp:setProperty
an Request-Parameter "a", "b" und "c"
gebunden.
- Einbinden einer externen Seite mit jsp:include:
<jsp:include page="input.jsp"/>
Beim Rendern der Seite wird an dieser Stelle die HTML-Ausgabe der Seite "input.jsp" eingebunden (im Prinzip wie ein Funktionsaufruf).
Eine alternative Methode der Einbindung wäre:
<%@include file="input.jsp" %>
Hierbei wird beim Umwandeln der JSP-Seite in eine Java-Datei der Inhalt der includierten Seite in die generierte Javaklasse eingebunden. In dieser Variante
darf die includierte Datei kein @page
-Tag enthalten!
- Bei Fehlern an den Eingaben (Parse-Fehler in "index.jsp") wird automatisch die Errorpage aufgerufen.
Die Errorpage wird zu einer solchen durch folgende @page
-Direktive:
<%@page contentType="text/html; charset=windows-1252" isErrorPage="true"%>
Ihr Aufruf erfolgt durch den Server, wenn die Errorpage auf einer JSP-Seite über das Attribut "errorPage" deklariert ist.
Auf der Errorpage gibt es eine weitere Variable "exception" vom Typ java.lang.Throwable
, in der die Exception steht, die zum Aufruf der Fehlerseite
führte.
Die Anwendung findet sich unter dieser URL: http://localhost:8080/JSP1/index.jsp.
Beispiel 2: Controller-Servlet
In diesem Beispiel wird ein großer Teil der Anwendungslogik in ein Servlet ausgelagert.
Beim Absenden des Formulars ist nicht die JSP-Seite das Ziel, sondern ein Servlet. Dieses
wertet die Request-Parameter aus, erzeugt die Bean mit den aktuellen Request-Werten, berechnet
Volumen und Oberfläche.
Anschließend wird der Request an "index.jsp" weitergeleitet, diese Seite gibt die errechneten
Werte aus.
Hier gibt es das Projekt als WAR-Export-Datei: JSP2.war.
Das Beispiel zeigt dieses:
- Trennung von Anwendungslogik und Anzeigelogik
- Verwaltung der Session über Servlet.
- Weiterleiten des Requests vom Servlet an die JSP.
- Speichern von Objekten im Request, so dass weitere Serverkomponenten sie nutzen können.
Hierzu wurde ein Servlet gebaut, das als Action des Eingabeformulars ausgerufen wird.
package de.fhw.komponentenarchitekturen.knauf.jsp2;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GeometricModelServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
try
{
Seitenlaengen seitenlaengenAktuell = new Seitenlaengen();
double dblA, dblB, dblC;
Im folgenden werden die Eingaben des Formulars aus dem Request geholt:
dblA = Double.parseDouble(request.getParameter ("a"));
dblB = Double.parseDouble(request.getParameter ("b"));
dblC = Double.parseDouble(request.getParameter ("c"));
seitenlaengenAktuell.setA ( dblA );
seitenlaengenAktuell.setB ( dblB );
seitenlaengenAktuell.setC ( dblC );
double dblVolumen = dblA * dblB * dblC;
double dblOberflaeche = 2 * (dblA * dblB) + 2 * (dblA * dblC) + 2 * (dblB * dblC);
Die Ergebnisse der Berechnung werden als Attribute in den Request geschrieben und stehen dadurch auf der JSP zur Verfügung:
request.setAttribute("Volumen", dblVolumen);
request.setAttribute("Oberflaeche", dblOberflaeche);
Beim ersten Aufruf des Servlets gibt es noch kein Attribut "historie" in der Session. Deshalb wird es erzeugt und unter diesem Namen in den
Sessioncontext gehängt. Bei weiteren Aufrufen kann es dort heraus geholt werden.
request.getSession()
erzeugt eventuell eine Session, falls dies noch nicht geschehen ist (z.B. beim ersten Aufruf des Servlets)
Historie historie = null;
if (request.getSession().getAttribute("historie") != null)
{
historie = (Historie) request.getSession().getAttribute("historie");
}
else
{
historie = new Historie();
request.getSession().setAttribute("historie", historie);
}
historie.addSeitenlaenge(seitenlaengenAktuell);
Nach der Vorbereitung wird die Verarbeitung an "index.jsp" weitergeleitet. Achtung: wir befinden uns hier in der
URL "/servlet/GeometricModelServlet", d.h. eine Ebene höher wechseln!
Außerdem sollen Fehler gefangen werden, siehe Details weiter unten.
request.getRequestDispatcher("../index.jsp").forward(request, response);
}
catch (Exception ex)
{
request.setAttribute("javax.servlet.error.exception", ex);
response.sendError(500);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
this.doGet(request, response);
}
}
Auf der JSP-Seite wird hier nur noch das Formular aufgebaut, die Ergebnisse der aktuellen Rechnung (falls Aufruf der Seite nach Formulareingabe)
und die Historie der letzten Berechnungen ausgegeben.
- Fehlerbehandlung (z.B. bei Eingabe von Buchstaben durch den User): im Servlet ausgelöste Exception werden an die Errorpage weitergegeben.
Dazu wird die Exception als Attribut "javax.servlet.error.exception" in den Request geschrieben.
Unter diesem Namen sucht die Errorpage sie später (siehe JSP-Spezifikation oder Doku zu Tomcat5-Klasse "org.apache.jasper.runtime.JspRuntimeLibrary").
Das Setzen dieses Attributs reicht damit JBoss einen Fehler erkennt und auf die Errorpage weiterleitet.
Außerdem wird ein Response-Fehlercode "500" gesetzt. Bei JBoss 5 war dies noch nicht nötig, aber in WildFly-Servern erfolgt ohne diesen Aufruf keine Weiterleitungz zu "errorpage.jsp.
Auf welche Seite bei der Fehlerbehandlung weitergeleitet wird ist in web.xml
im Element "<error-page>" deklariert:
<error-page>
<error-code>500</error-code>
<location>/errorpage.jsp</location>
</error-page>
Die Anwendung findet sich unter dieser URL: http://localhost:8080/JSP2/index.jsp.
Beispiel 3: Tag Libraries
In diesem Beispiel wird Beispiel 2 weitergeführt: auf der JSP-Seite "index.jsp" soll
kein Stück Javacode mehr zu sehen sein. Stattdessen soll alle dynamische Formatierung von
TagLibs erledigt werden.
Das Beispiel verwendet die Apache-Implementation des Java-Standard-Taglib-Standards (JSTL) Version 1.2:
https://tomcat.apache.org/taglibs/standard/
Eine offizielle Doku der TagLib habe ich nicht gefunden, das ist wohl zu alt. Auf obiger Download-Seite gibt es nur eine Readme, die das Einrichten erklärt.
Aber zum Beispiel gibt es diese Seite: https://www.w3schools.in/jsp/standard-tag-library/.
Hier gibt es das Projekt als WAR-Export-Datei: JSP3.war.
Nach dem Anlegen des Dynamic Web Projects muss die zu verwendende Tag-Library zugefügt werden.
Dazu muss die Datei "taglibs-standard-spec-1.2.5.jar" und "taglibs-standard-impl-1.2.5.jar" aus der Apache-Referenzimplementation (siehe oben)
in das Verzeichnis "WebContent\WEB-INF\lib" des Projekts eingefügt werden. Anschließend "Refresh" im Projekt wählen. Die beiden Libraries
sollten jetzt im Project Explorer unter "Web App Libraries" auftauchen.
WildFly enthält diese Dateien ebenfalls, und sogar in etwas neueren Versionen: sie liegen im WildFly-Verzeichnis unter "modules\system\layers\base\javax\servlet\jstl\api\main\taglibs-standard-impl-1.2.6-RC1.jar")
Da eine mittels der JBoss Tools erzeugte Server Runtime diese Jar-Dateien einbindet, ist oben beschriebenes Platzieren der Dateien in "WEB-INF\lib" nicht nötig:
Deklaration der Taglib in web.xml:
Sofern die "taglib uri", die in der JSP deklariert ist, dem Default entspricht, der in der .tld-Datei deklariert ist, kann man sich die im Folgenden beschriebenen Einträge
in web.xml sparen, WildFly und Eclipse finden die Taglib auch so, sofern sie (auch in JAR-Dateien versteckt) im WEB-INF-Verzeichnis liegen.
Hier ist aber trotzdem der Vollständigkeit halber beschrieben, wie man die TagLibrary explizit deklarieren könnte:
Die Deklarationen der Taglibraries (Dateien mit der Endung ".tld") werden ins Projekt
gelegt. Dazu aus "jstl.jar" die Datei "c.tld". Diese in das Verzeichnis "WebContent\WEB-INF\tags" (das Ziel ist frei wählbar,
die Datei könnte auch direkt in WEB-INF gelegt werden) packen.
Jetzt wird die Tag Library in "web.xml" eintragen:
<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/core</taglib-uri>
<taglib-location>/WEB-INF/tags/c.tld</taglib-location>
</taglib>
</jsp-config>
Hier werden symbolische Namen für die Taglibs in "WEB-INF\lib" deklariert, unter diesen URI (Unified Resource
Identifier) werden sie später auf den JSP-Seiten angesprochen.
In "index.jsp" wird die Taglib so eingebunden:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
Jetzt können die Tags aus der Tag Library unter dem Alias-Namen "c" verwendet werden.
Der Rest von "index.jsp" sieht so aus:
<c:if test="${requestScope.Volumen != null}">
Volumen = <c:out value="${requestScope.Volumen}" />, Oberfläche = <c:out value="${requestScope.Oberflaeche}"/>
<br/> <br/> <br/>
</c:if>
<jsp:include page="input.jsp" />
<h2>Ihre letzten Berechnungen waren:</h2> <br/>
<c:forEach var="item" items="${sessionScope.historie.iterator}">
a=<c:out value="${item.a}"/>, b=<c:out value="${item.b}"/>, c=<c:out value="${item.c}"/> <br/>
</c:forEach>
Das Tag "c:if" prüft, ob ein bestimmtes Attribut im Request ("requestScope") existiert. Im Beispiel prüfe ich ob "Volumen" ungleich null ist und erkenne daran, ob das
Servlet zur Berechnung aufgerufen wurde, oder ob dies der erste Aufruf der JSP ist.
Das Tag "c:out" gibt ein bestimmes Request-Attribut aus.
Das Tag "c:forEach" der JSTL läuft über eine Liste und schreibt das jeweils aktuelle Item als Attribut in den PageContext. Die zu durchlaufende
Liste wird hier als Expression Language (EL) - Ausdruck deklariert. ${sessionScope.historie.iterator}
bedeutet:
"Im SessionContext gibt es ein Attribut namens 'historie', und dieses hat eine Property 'iterator' (also eine Methode "getIterator"), über die Rückgabe der Property laufen.".
Das aktuelle Item wird in ein PageContext-Attribut namens "item" gepackt (der Name dieses Attributs wird durch das Attribut "var" deklariert).
Anmerkung:
Bei der EL ist zu beachten, dass ihre Elemente sich auf Attribute in Session-, Request- und PageContext sowie auf Properties von darin gefundenen Objekten
beziehen. Dies alles sucht sich der Server zur Laufzeit zusammen, es gibt keinerlei Möglichkeit, sie durch Compilierung oder Validierungen der Entwicklungsumgebung zu prüfen.
Für weitere Informationen sei auf folgendes verwiesen:
Das Tag "c:out" gibt den Wert einer Variablen als String aus. Im Beispiel wird ein EL-Ausdruck verwendet: ${item.a}
sucht ein Attribut "item" im PageContext
(ich hätte es auch sauberer als ${pageScope.item.a}
schreiben können), und gibt von diesem eine Property "a" (also die Rückgabe der Methode "getA") aus.
Wie im vorherigen Beispiel muss außerdem die Errorpage als Handler für den Error Code 500 eingetragen werden.
Die Anwendung findet sich unter dieser URL: http://localhost:8080/JSP3/index.jsp.
Beispiel 4: Custom Tag Libraries
In diesem Beispiel werden die TagLibraries aus Beispiel 3 ersetzt durch handgeschriebene Taglibraries.
Hier gibt es das Projekt als WAR-Export-Datei: JSP4.war.
Die TagLibraries landen im Package de.fhw.komponentenarchitekturen.knauf.jsp4.tags
.
Die Definition der Tags erfolgt in der Datei "WebContent\WEB-INF\tags\jsp4.tld".
Der Rahmen dieser TagLibrary-Deklaration sieht so aus (gemäß JSP-2.1-Spezifikation, die TLDs aus dem letzten
Beispiel wurden erstellt gemäß JSP-Spezifikation 1.1, dort wurde eine DTD verwendet und einige Tags hießen leicht anders):
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1>
<tlib-version>1.0</tlib-version>
<short-name>jsp4</short-name>
<uri>http://www.informatik.fh-wiesbaden.de/~knauf/jsp4</uri>
...
</taglib>
Hier vergebe ich einen Kurznamen der TagLib und eine URI, wobei beide Werte frei vergebbar sind und nur als Vorschläge
dienen, auf der JSP-Seite können sie beliebig überschrieben werden.
Folgende Tags werden verwendet:
IfExistsRequestAttributeTag
: Prüft ob im Request ein Attribut mit einem bestimmten Namen steckt.
Der Name des zu prüfenden Attributs wird auf der JSP-Seite per Attribut "requestAttribute" gesetzt.
Ist dieses Attribut im Request vorhanden wird der JSP-Inhalt ausgeführt.
In "jsp4.tld" sieht das so aus:
<tag>
<name>ifExistsRequestAttribute</name>
<tag-class>de.fhw.komponentenarchitekturen.knauf.jsp4.tags.IfExistsRequestAttributeTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>requestAttribute</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
Wichtig ist hier die Angabe dass das Tag JSP-Inhalt haben kann (Tag body-content
).
Die Implementierung sieht so aus:
package de.fhw.komponentenarchitekturen.knauf.jsp4.tags;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.JspException;
public class IfExistsRequestAttributeTag extends BodyTagSupport
{
private String strRequestAttribute = "";
@Override
public int doStartTag() throws JspException
{
HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
if (request.getAttribute(this.strRequestAttribute) != null)
{
return TagSupport.EVAL_BODY_INCLUDE;
}
else
{
return TagSupport.SKIP_BODY;
}
}
public void setRequestAttribute(String str_RequestAttribute)
{
this.strRequestAttribute = str_RequestAttribute;
}
}
Die Tag-Klasse ist von javax.servlet.jsp.tagext.BodyTagSupport
abgeleitet da das Tag JSP-Inhalt hat.
In setRequestAttribute
wird das Attribut "requestAttribute" aus der
JSP-Seite heraus gesetzt. Die überladene Methode doStartTag
wird zu Beginn der Tagauswertung aufgerufen und steuert,
ob der Inhalt des Tags ausgeführt wird.
Gibt sie TagSupport.EVAL_BODY_INCLUDE
zurück, wird der JSP-Inhalt des Tags ausgewertet. Eine Rückgabe von TagSupport.SKIP_BODY
bedeutet, dass der Inhalt des Tags übersprungen wird.
Die Verwendung auf "index.jsp" sieht so aus:
<jsp4:ifExistsRequestAttribute requestAttribute="Volumen">
Volumen = <jsp4:printRequestAttribute requestAttribute="Volumen" />, Oberfläche = <jsp4:printRequestAttribute requestAttribute="Oberflaeche" />
<br/> <br/> <br/>
</jsp4:ifExistsRequestAttribute>
PrintRequestAttributeTag
: Gibt ein Request-Attribut aus. Der Name des auszugebenden Attributs wird auf der JSP-Seite per Attribut
"requestAttribute" gesetzt. Ist dieses Attribut im Request vorhanden wird sein Wert ausgegeben.
In "jsp4.tld" sieht das so aus:
<tag>
<name>printRequestAttribute</name>
<tag-class>de.fhw.komponentenarchitekturen.knauf.jsp4.tags.PrintRequestAttributeTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>requestAttribute</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
Wichtig ist hier die Angabe dass das Tag keinen Inhalt hat.
Die Implementierung sieht so aus (wobei die Implementierung von doTag
im vollen Beispiel mehr Fehlerprüfungen enthält):
package de.fhw.komponentenarchitekturen.knauf.jsp4.tags;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class PrintRequestAttributeTag extends SimpleTagSupport
{
private String strRequestAttribute = "";
@Override
public void doTag() throws JspException
{
Object objValue = this.getJspContext().getAttribute(this.strRequestAttribute, PageContext.REQUEST_SCOPE);
if (objValue != null)
{
try
{
this.getJspContext().getOut().write(objValue.toString());
}
catch (IOException ioex)
{
throw new JspException ("IOException: " + ioex.getMessage(), ioex);
}
}
}
public void setRequestAttribute(String str_RequestAttribute)
{
this.strRequestAttribute = str_RequestAttribute;
}
}
Die Klasse ist von javax.servlet.jsp.tagext.SimpleTagSupport
abgeleitet, da das Tag keinen Inhalt hat.
In der Methode setRequestAttribute
hier wird das Attribut "requestAttribute" aus der
JSP-Seite heraus gesetzt. Die überladene Methode doTag
führt die gesamte Logik des Tags aus.
LoopOverHistorieTag
: Holt aus dem Session-Context ein Attribut mit einem bestimmten Namen vom Typ
de.fhw.komponentenarchitekturen.knauf.jsp4.Historie
und läuft per java.util.Iterator
über die
Elemente der Collection. Das aktuelle Element wird unter einem konfigurierbaren Namen in den PageContext gesteckt.
Der Name des Session-Context-Attributs in dem die Historie steckt wird auf der JSP-Seite per Attribut gesetzt.
Ist dieses Attribut im Request vorhanden wird der JSP-Inhalt für jedes Item ausgeführt.
In "jsp4.tld" sieht das so aus:
<tag>
<name>loopOverHistorie</name>
<tag-class>de.fhw.komponentenarchitekturen.knauf.jsp4.tags.LoopOverHistorieTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>sessionContextAttribute</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>item</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
Wichtig ist hier die Angabe dass das Tag JSP-Inhalt haben kann.
Die Implementierung sieht so aus (auch hier: im vollen Beispiel gibt es mehr Fehlerprüfungen):
package de.fhw.komponentenarchitekturen.knauf.jsp4.tags;
import java.util.Iterator;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.JspException;
import de.fhw.komponentenarchitekturen.knauf.jsp4.Historie;
import de.fhw.komponentenarchitekturen.knauf.jsp4.Seitenlaengen;
public class LoopOverHistorieTag extends BodyTagSupport
{
private String strSessionContextAttribute = "";
private String strCurrentItem = "";
/**Der Iterator über die Historie. Wird im "doStartTag" initialisiert. */
private Iterator<Seitenlaengen> iteratorHistorie = null;
@Override
public int doStartTag() throws JspException
{
Object objSessionContextAttribute = this.pageContext.getSession().getAttribute(this.strSessionContextAttribute);
if (objSessionContextAttribute != null)
{
Historie historie = (Historie) objSessionContextAttribute;
this.iteratorHistorie = historie.getIterator();
if (this.iteratorHistorie.hasNext() == true)
{
Seitenlaengen seitenlaengeAktuell = this.iteratorHistorie.next();
this.pageContext.setAttribute(this.strCurrentItem, seitenlaengeAktuell);
return TagSupport.EVAL_BODY_INCLUDE;
}
else
{
return TagSupport.SKIP_BODY;
}
}
else
{
return TagSupport.SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException
{
if (this.iteratorHistorie.hasNext() == true)
{
Seitenlaengen seitenlaengeAktuell = this.iteratorHistorie.next();
this.pageContext.setAttribute(this.strCurrentItem, seitenlaengeAktuell);
return TagSupport.EVAL_BODY_AGAIN;
}
else
{
return TagSupport.SKIP_BODY;
}
}
@Override
public int doEndTag() throws JspException
{
if (this.pageContext.getAttribute(this.strCurrentItem) != null)
{
this.pageContext.removeAttribute(this.strCurrentItem);
}
return super.doEndTag();
}
public void setSessionContextAttribute(String str_SessionContextAttribute)
{
this.strSessionContextAttribute = str_SessionContextAttribute;
}
public void setItem(String str_Item)
{
this.strCurrentItem = str_Item;
}
}
Die Tag-Klasse ist von javax.servlet.jsp.tagext.BodyTagSupport
abgeleitet da das Tag JSP-Inhalt haben kann.
Mittels setSessionContextAttribute
wird das Attribut "sessionContextAttribute" aus der
JSP-Seite heraus gesetzt (über die Historie-Liste hinter diesem SessionContext-Attribut läuft das Tag).
Über "setItem" wird der Name eines PageContext-Attributs gesetzt, in das der aktuelle Schleifenwert gepackt wird.
Interessant ist hier die Erzeugung der Schleife: in doStartTag
wird der Iterator initialisiert und erstmalig der Inhalt des Tags ausgeführt
(falls mindestens ein Element in der Liste steckt, kommt TagSupport.EVAL_BODY_INCLUDE
zurück, sonst TagSupport.SKIP_BODY
).
Das erste Element der Liste wird in ein Attribut des PageContext gelegt, und der Iterator in einer Membervariablen des Tags gespeichert.
Ob ein zweiter oder weitere Schleifendurchläufe nötig sind, wird nach Ausführung des Taginhalts durch Aufruf von
doAfterBody
geprüft. Hier wird der Iterator um ein Element weitergeschoben, falls noch etwas vorhanden (Rückgabe: TagSupport.EVAL_BODY_AGAIN
).
In diesem Fall wird das aktuelle Element wiederum in ein PageContext-Attribut geklebt.
Ist der Iterator am Ende, kommt TagSupport.SKIP_BODY
.
Nachdem alle Elemente abgearbeitet wurden, wird doEndTag
aufgerufen. Hier wird das Attribut für
das aktuelle Item aus dem PageContext entfernt.
Die Verwendung auf "index.jsp" sieht so aus:
<jsp4:loopOverHistorie item="item" sessionContextAttribute="historie">
a=<jsp4:printPageContextSeitenlaenge pageContextAttribute="item" seitenlaengeValue="a"/>, b=<jsp4:printPageContextSeitenlaenge pageContextAttribute="item" seitenlaengeValue="b"/>, c=<jsp4:printPageContextSeitenlaenge pageContextAttribute="item" seitenlaengeValue="c"/> <br/>
</jsp4:loopOverHistorie>
PrintPageContextSeitenlaengeTag
: Ausgabe einer der Properties "a", "b" oder "c" einer Instanz
von de.fhw.komponentenarchitekturen.knauf.jsp4.Seitenlaengen
.
Das Attribut "pageContextAttribute" gibt an, in welchem Attribut des PageContext die Historie gesucht werden soll,
seitenlaengeValue
gibt an, ob welche der Properties von Seitenlaengen
ausgegeben werden soll.
In "jsp4.tld" sieht das so aus:
<tag>
<name>printPageContextSeitenlaenge</name>
<tag-class>de.fhw.komponentenarchitekturen.knauf.jsp4.tags.PrintPageContextSeitenlaengeTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>pageContextAttribute</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>seitenlaengeValue</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
Wichtig ist hier die Angabe dass das Tag keinen Inhalt hat.
Eine Implementierung wird hier nicht angegeben, da keine Unterschiede zum PrintRequestAttributeTag
.
In "web.xml" muss dieses zugefügt werden:
<jsp-config>
<taglib>
<taglib-uri>http://www.informatik.fh-wiesbaden.de/~knauf/jsp4</taglib-uri>
<taglib-location>/WEB-INF/tags/jsp4.tld</taglib-location>
</taglib>
</jsp-config>
Die Taglib-URI unter der wir die TagLib später in den JSPs ansprechen ist hier identisch mit der in der
TLD-Datei vorgegebenen, dies ist aber nicht verpflichtend.
In "index.jsp" wird die TagLib so eingebunden:
<%@ taglib uri="http://www.informatik.fh-wiesbaden.de/~knauf/jsp4" prefix="jsp4" %>
Jetzt zeigt uns Eclipse auch brav die verfügbaren Tags an:
Die Anwendung findet sich unter dieser URL: http://localhost:8080/JSP4/index.jsp.
Beispiel 5: Tag Files
Wir nutzen hier ein im JSP-Standard 2.0 eingeführtes neues Feature: Tag-Libraries können auch in sogenannten Tag Files implementiert werden.
In diesem Beispiel werden die in Java codierten Tags aus Beispiel 4 ersetzt durch JSP-Fragmente.
Weitere Informationen finden sich in Kapitel 8.5 der JSP-2.1-Spezifikation.
Die Links, die es 2009 beim Erstellen dieser Seite noch gab, funktionieren leider alle nicht mehr.
Hier gibt es das Projekt als WAR-Export-Datei: JSP5.war.
Erstellen der Tags:
Das Erstellen eines Tags ist einfach: wir legen ein Unterverzeichnis "tags" in "WEB-INF" an (das Verzeichnis darf auch anders heißen). Alle Dateien in diesem Verzeichnis,
die die Endung ".tag" haben werden als Tag erkannt. Der Dateiname ist dabei gleichzeit der Tagname.
Das Erstellen einer TLD-Datei entfällt. Falls ein Tag Attribute hat, dann werden die so in der .tag-Datei deklariert:
<%@ attribute name="..."%>
Die Entsprechung zur "page"-Direktive auf einer JSP-Seite ist die "tag"-Direktive, die z.B. zum Importieren von Java-Packages verwendet werden kann:
<%@ tag import="de.fhw.komponentenarchitekturen.knauf.jsp4.*"%>
Verwendung der Tags:
In web.xml muss nichts eingetragen werden.
In der JSP wird die Taglibrary so eingebunden:
<%@ taglib prefix="jsp5" tagdir="/WEB-INF/tags" %>
Der relative Pfad zum Verzeichnis der Tags wird angegeben und die Taglibrary erhält einen Alias.
In der JSP selbst erfolgt die Verwendung genauso wie im letzten Beispiel.
"ifExistsRequestAttribute"-Tag:
Die Tags in diesem Beispiel verwenden alle massiv Javacode. Dies ist nicht wirklich schön, aber da die Tags allesamt auch durch Tags der JSTL
abbildbar sind würde es nicht viel Sinn ergeben hier die JSTL zu verwenden.
<%@ attribute name="requestAttribute" required="true"%>
<%
//Prüfen ob das Attribut existiert !
if (request.getAttribute(requestAttribute) != null)
{
//Body ausführen !
%>
<jsp:doBody />
<%
}
%>
Das Tag hat ein Attribut "requestAttribute", dies ist ein String der ein Attribut angibt auf dessen Existenz im Request geprüft wird.
Falls das Attribut vorhanden ist wird der Inhalt des Tags auf der JSP ausgeführt. Dies geschieht durch das JSP-Element "<jsp:doBody />".
"loopOverHistorie"-Tag:
Hier ergab sich das Problem dass ich das aktuelle Item nicht in den JspContext
legen konnte, denn es ging in den Childelementen verloren.
Deshalb verwende ich hier den Request
, um das aktuelle Item abzulegen.
<%@ tag import="java.util.Iterator"%>
<%@ tag import="de.fhw.komponentenarchitekturen.knauf.jsp5.*"%>
<%@ attribute name="item" required="true"%>
<%@ attribute name="sessionContextAttribute" required="true"%>
<%
if (session.getAttribute(sessionContextAttribute) != null)
{
Object objSessionContextAttribute = session.getAttribute(sessionContextAttribute);
if (!(objSessionContextAttribute instanceof Historie))
throw new JspException("Attribut " + sessionContextAttribute + " ist nicht vom Typ 'Historie' sondern '" + objSessionContextAttribute.getClass().toString());
Historie historie = (Historie) objSessionContextAttribute;
//Iterator initialisieren:
Iterator<Seitenlaengen> iteratorHistorie = historie.getIterator();
//Ist überhaupt ein Item vorhanden ?
while (iteratorHistorie.hasNext() == true)
{
Seitenlaengen itemAktuell = iteratorHistorie.next();
//Aktuelles Item in den Request packen (im jspContext kommt das leider nicht im Subtag an):
request.setAttribute(item, itemAktuell);
//Tag-Body ausführen.
%>
<jsp:doBody />
<%
}
}
%>
Die Anwendung findet sich unter dieser URL: http://localhost:8080/JSP5/index.jsp.
Stand 02.03.2022
Historie:
02.03.2022: Erstellt aus Beispiel von 2009. Aktualisiert auf WildFly 24, JakartaEE 8.
Beispiel "JSP3": "taglibs-request.jar" entfernt, da schon lange deprecated, Quellen für JSTL aktualisiert, Einbindung der TagLib überarbeitet.