Tag Handler sind in Java implementierte Funktionen, die aus einer JSP heraus in XML Syntax gerufen werden, Zugriff auf Parameter, den XML Body und (eingeschränkt) auf die weitere Abarbeitung der Seite haben. Zwei APIs stehen dafür zur Verfügung: Simple Tag Handler (seit JSP2.1) und klassische Tag Handler (schon immer). Das API für Simple Tag Handler ist einfacher und intuitiver und sollte dem für klassische Tag Handler bevorzugt werden.
Um einen Simple Tag Handler (STH) bereitzustellen:
: <taglib> <tlib-version>0.4</tlib-version> <uri>myCoolTL</uri> : <tag> <description>Beschreibung hier..</description> <name>mySimpleTag</name> <tag-class>a.b.MySimpleTag</tag-class> <body-content>empty</body-content> </tag> : </taglib>
<%@ taglib prefix="my" uri="myCoolTL" %> : <my:mySimpleTag />
Option | Erläuterung |
---|---|
body-content="empty" | Ein Body ist nicht erlaubt. Ausnahme: jsp:Attribute-Elemente. |
body-content="scriptless" | EL ist erlaubt, aber kein Direktiven, Expressions oder Skriptletts. Erzwingt skriptfreies Arbeiten. |
body-content="JSP" | gewöhnliches JSP ist erlaubt. |
body-content="tagdependent" | Body ist erlaubt, wird aber als plain Text behandelt. |
Class: a.b.MySimpleTag : public void doTag() throws JspException, IOException { // for (int i = 0, l = 10; i < l; i++) { getJspContext().setAttribute("counter", Integer.toString(i)); getJspBody().invoke(null); // Im Body kann nun der Paramter counter genutzt werden } }Das Argument "null" bewirkt das Schreiben auf den Response-Writer, es kann aber auch ein anderer java.io.Writer angegeben werden. Innerhalb von invoke wird dann Skripting wieder aktiv und der Body ist parametrisierbar:
<%@ taglib prefix="my" uri="myCoolTL" %> : <my:mySimpleTag farbe="rot"> // die Bean-Eigenschaft "farbe" muss im STH bereit stehen Nachricht: ${message} // wird erst innerhalb des STH in getJspBody().invoke(null) augewertet </my:mySimpleTag> // // oder mit jsp:attribute <my:mySimpleTag> <jsp:attribute name="farbe">${session.farbe}</jsp:attribute> </my:mySimpleTag>
STH Class: a.b.MySimpleTag : public void doTag() throws JspException, IOException { : getJspContext().setAttribute("message", "Hallo"); getJspBody().invoke(null); }Die Abarbeitung in einem STH kann mittels einer SkipPageException unterbrochen werden:
Class: a.b.MySimpleTag : public void doTag() throws JspException, IOException { : getJspContext().setAttribute("message", "Hallo"); getJspBody().invoke(null); if (notOK) { throw new SkipPageException(); } // Code ab hier (und der Rest der Seite, die diesen Tag augerufen hat) wird evtl. nicht erreicht : }
STH Handler Instanzen werden nicht wiederverwendet (kein Pooling). Das steht im Gegensatz zu dem Verhalten bei klassischen Handlern.
Nutzung und TLD sind (fast) gleich wie bei STH. Allerdings unterscheidet sich die API der CTH erheblich.
public class MyClassicTagHandler extends TagSupport { // public int doStartTag() throws JspException { // keine IOException // JspWriter out = pageContext.getOut(); // keine Methode getJspContext(), sondern eine Membervariable try { out.println(..); // alle Ausgaben sind so zu kapseln, weil keine IOException in Signatur } catch(IOException ioe) { throw new JspException(..); } : return SKIP_BODY; // Returnwert bestimmt, wie die Abarbeitung der Seite fortgeführt werden soll } }Verarbeitung des Tag-body bei CTH:
public class MyClassicTagHandler extends TagSupport { // public int doStartTag() throws JspException { // keine IOException : return EVAL_BODY_INCLUDE; // dieser Returnwert triggert die Verarbeitung des Tag-body an } }
die Instanz wird möglicherweise in einen Pool befördert ODER vom Container verworfen und release() wird gerufen
doStartTag() --- SKIP_BODY --------+ | | EVAL_BODY_INCLUDE | | | evaluate body <------------------+ | | | | doAfterBody() - EVAL_BODY_AGAIN -+ | | | SKIP_BODY | | <-----------------------+ doEndTag() --- SKIP_PAGE --> done | EVAL_PAGE | evaluate page | done
Nicht überschriebene Methoden haben die Standard-Rückgabewerte SKIP_BODY und EVAL_PAGE. CTH Handler Instanzen werden möglicherweise wiederverwendet (Pooling). Damit müssen bei Bedarf Instanzvariablen in doStartTag() initialisiert werden. Wenn der Container entscheidet die Instanz zu verwerfen wird release() aufgerufen. Ist im TLD body-content als empty deklariert, muss doStartTag() SKIP_BODY zurückliefern.
Durch Erweitern der Klasse BodyTagSupport gibt der Container noch mehr Kontrolle über den Tag-Body, denn es kommen zwei neue Callbacks zum Page-Lifecycle hinzu:
doStartTag() --- SKIP_BODY --------+ | - EVAL_BODY_INCLUDE + | EVAL_BODY_BUFFERED | | | | | setBodyContent() | | | | | doInitBody() | | | | | | | | evaluate body <------------------+ | | | | doAfterBody() - EVAL_BODY_AGAIN -+ | | | SKIP_BODY | | <-----------------------+ doEndTag() --- SKIP_PAGE --> done | EVAL_PAGE | evaluate page | doneGleichzeitig ist EVAL_BODY_BUFFERED der neue Standardwert für doStartTag().
Jsp Tag Handler Vererbungshierarchie
ab JSP 2.0 | vor JSP 2.0 JspTag <Interface> A A | | SimpleTag Tag <Interface> <Interface> A A | | SimpleTagSupport IterationTag <-- TagSupport <-- BodyTagSupport <Interface>
Ein Tag kann über getParent() seinen umschließenden Tag aufrufen. Ein wenig problematisch ist dabei, dass der Rückgabewert eines CTH Tag, der eines STH JspTag. Damit kann zwar ein STH einen CTH als Parent haben, aber nicht ohne weiteres umgekehrt (das geht zum Beispiel über die Adapterklasse TagAdapter). Ein Tag kann über die Methode findAncestorWithClass(this, NiceTagClass.class) in der Verschachtelungshierarchie nach einem umschließenden Tag der angegebenen Klasse suchen.
Tag Handler und der PageContext
Das PageContext-Objekt repräsentiert die Resourcen, die der Container dem TagHandler bereitstellt. Es wird erreicht über getJspContext() in STH und pageContext in CTH.
JspContext ---------- getAttribute(String) // finde Attribut im pageContext-Object getAttribute(String, int) // finde Attribut im Objekt des Scopes findAttribute(String) // finde Attribut in der Reihenfolge page, request, session, application getAttributeNamesInScope(int) getOut() A | PageContext ----------- APPLICATION_SCOPE PAGE_SCOPE SESSION_SCOPE REQUEST_SCOPE : getRequest() getServletConfig() getServletContext() getSession() :
foo.tld File irgendwo unter WEB-INF, der Server sucht alle *.tld und legt eine Map an (seit JSP2.0).
<?xml version="1.0" encoding="UTF-8" ?> <taglib 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 web-jsptaglibrary_2_0.xsd" version="2.0"> : <description>....</description> <display-name>...</display-name> <tlib-version>1.1</tlib-version> // nicht zwingend <short-name>foo</short-name> <uri>some_logical_name</uri> // referenziert mit <%@ taglib uri="some_logical_name" prefix="foo" %> : <tag> // hier kommt ein Taghandler <description>...</description> <name>bar</name> // aha! im Code dann <foo:bar .. /> <tag-class>com.mm.BarTag</tag-class> <body-content>JSP</body-content> <attribute> <description>...</description> <name>var</name> // aha! im Code dann <foo:bar var=".." /> <required>true</required> // muss sein <rtexprvalue>false</rtexprvalue> // wertet keine Expressions aus </attribute> </tag> : <function> // eine EL function .. </function> // kein DD hier für Tagfiles -> Deklaration in der attribute-Page directive des Files selbst : </taglib>Wenn mit einem alten Container gearbeitet wird, dann ist ein Eintrag im DD notwendig. Solche DD-Einträge werden auch weiterhin ausgewertet, sind aber eigentlich nicht mehr notwendig.
Achtung! STH werden nach Gebrauch verworfen und von der Garbage Collection weggeräumt. CTH werden möglicherweise wiederverwendet, ihr Zustand muss dann eventuell bei jedem Gebrauch zurückgesetzt werden. release() wird erst dann gerufen, wenn die Wiederverwendung endet und die CTH Instanz endgültig verworfen wird.