Start - Publikationen - Wissen - TOGAF - Impressum -

Tag Handler


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.

Simple Tag Handler


Um einen Simple Tag Handler (STH) bereitzustellen:

  1. Klasse schreiben, die SimpleTagSupport erweitert und doTag() überschreibt.
  2. Eine TLD für diesen STH bereitstellen:
  3. :
    <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>
    
  4. Nutzen des STH:
  5. <%@ taglib prefix="my" uri="myCoolTL" %>
    :
    <my:mySimpleTag />
    
Will man im STH einen Body nutzen, muss das im TLD berücksichtig werden. Hier alle Optionen:
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.
Mit getJspBody().invoke wird dann die Abarbeitung des Body gestartet. Das kann beliebig oft und mit sich ändernden Parametern erfolgen. Ein typischer Ablauf sieht so aus:
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 API und Lebenszyklus


  1. STH Klasse wird geladen
  2. no-arg Konstruktor wird gerufen
  3. setJspContext(JspContext) wird gerufen
  4. wenn Parent-Tag nicht null, wird setParent(JspTag) gerufen
  5. die Attribut-Setter des Tags werden gerufen
  6. wenn body-content nicht "empty" deklariert und ein body ist vorhande, wird setJspBody(JspFragment) gerufen
  7. doTag() wird gerufen

STH Handler Instanzen werden nicht wiederverwendet (kein Pooling). Das steht im Gegensatz zu dem Verhalten bei klassischen Handlern.

Classic Tag Handler (CTH)


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

CTH API und Lebenszyklus


  1. CTH Klasse wird geladen, der no-arg Konstruktor und setJspContext(JspContext) wird gerufen ODER eine Instanz wird aus dem Pool reaktiviert
  2. wenn Parent-Tag nicht null, wird setParent(JspTag) gerufen
  3. die Attribut-Setter des Tags werden gerufen
  4. doStartTag() wird gerufen
  5. wenn body-content nicht "empty" deklariert und ein body ist vorhanden und doStartTag() gibt ein EVAL_BODY_INCLUDE wird der Body verarbeitet und doAfterBody() gerufen. Das wird wiederholt, solange doAfterBody() mit EVAL_BODY_AGAIN endet.
  6. doEndTag() wird gerufen

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.

CTH mit BodyTagSupport


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              
    |                      
   done
Gleichzeitig 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>

Verschachtelte Tag Handler


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()
:

Tag Library Descriptors (TLD)


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.

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved