Java Web Aufbau – Tag 6 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting

📋 Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| 1 | Filter im Webcontainer | ✅ Abgeschlossen |
| 2 | Listener im Webcontainer | ✅ Abgeschlossen |
| 3 | Authentifizierung über Datenbank | ✅ Abgeschlossen |
| 4 | Container-Managed Security & Jakarta Security API | ✅ Abgeschlossen |
| 5 | Custom Tags & Tag Handler (SimpleTag) | ✅ Abgeschlossen |
| → 6 | Custom Tag Handler mit BodyTagSupport | 👉 DU BIST HIER! |
| 7 | JPA vs JDBC – Konfiguration & Provider | 🔒 Noch nicht freigeschaltet |
| 8 | JPA Relationen (1): @OneToOne & @ManyToOne | 🔒 Noch nicht freigeschaltet |
| 9 | JPA Relationen (2): @OneToMany & @ManyToMany | 🔒 Noch nicht freigeschaltet |
| 10 | JSF Überblick – Component-Based UI | 🔒 Noch nicht freigeschaltet |
Modul: Java Web Aufbau
Gesamt-Dauer: 10 Arbeitstage (je 8 Stunden)
Dein Ziel: Classic Tag Handler mit BodyTagSupport meistern und komplexe Iteration verstehen
📋 Voraussetzungen für diesen Tag
Du brauchst:
- ✅ JDK 21 LTS installiert
- ✅ Apache NetBeans 22 (oder neuer)
- ✅ Payara Server 6.x konfiguriert
- ✅ Tag 1-5 abgeschlossen
- ✅ SimpleTag verstanden (Tag 5!)
- ✅ TLD-Dateien erstellen können
Tag 5 verpasst?
Spring zurück zu Tag 5, um SimpleTag zu verstehen. BodyTagSupport baut darauf auf!
Setup-Probleme?
Schreib uns: support@java-developer.online
⚡ Das Wichtigste in 30 Sekunden
Heute lernst du:
- ✅ Was BodyTagSupport ist und warum es existiert
- ✅ Der Unterschied zwischen SimpleTag und Classic Tag Handler
- ✅ doStartTag(), doEndTag(), doAfterBody() Lifecycle
- ✅ Body Content mehrfach verarbeiten (Iteration!)
- ✅ Return-Codes verstehen (EVAL_BODY_BUFFERED, SKIP_BODY, etc.)
- ✅ Komplexe Tags wie
<c:forEach>nachbauen
Am Ende des Tages kannst du:
- Classic Tag Handler mit BodyTagSupport erstellen
- Über Collections iterieren in Custom Tags
- Body Content mehrfach durchlaufen
- Den kompletten Tag Lifecycle verstehen
- Entscheiden wann SimpleTag vs. BodyTagSupport
Zeit-Investment: ~8 Stunden
Schwierigkeitsgrad: Fortgeschritten (aber du packst das!)
👋 Willkommen zu Tag 6!
Hi! 👋
Elyndra hier. Heute wird’s richtig interessant!
Kurzwiederholung: Challenge von Tag 5
Gestern solltest du eine Tag Library mit 4 Tags erstellen:
<fleet:greeting>– Personalisierte Grüße<fleet:badge>– Bootstrap-Badges<fleet:truncate>– Text kürzen<fleet:repeat>– Content wiederholen
Falls du das noch nicht gemacht hast – kein Problem! Du kannst die Lösung im GitHub-Projekt finden.
Was du heute lernst:
Gestern hast du SimpleTagSupport kennengelernt – die moderne, einfache Art Custom Tags zu erstellen. Heute tauchen wir tiefer ein: BodyTagSupport – die Classic Tag Handler API.
„Moment“, denkst du vielleicht, „warum soll ich veraltete Technologie lernen?“
Gute Frage! Drei Gründe:
1. Legacy-Code verstehen
Du wirst in der Praxis auf Custom Tags mit BodyTagSupport treffen – besonders in älteren Enterprise-Projekten. Du musst sie verstehen können, um sie zu warten.
2. Mehr Kontrolle
BodyTagSupport gibt dir feinere Kontrolle über den Tag-Lifecycle. Manche komplexe Use-Cases sind damit einfacher zu lösen als mit SimpleTag.
3. Tiefes Verständnis
Wenn du beide APIs kennst, verstehst du WARUM SimpleTag besser ist – und wann du doch BodyTagSupport brauchst.
Keine Sorge:
BodyTagSupport wirkt kompliziert mit seinen vielen Methoden und Return-Codes. Aber ich zeige dir Schritt für Schritt, wie alles zusammenpasst. Nach heute verstehst du den kompletten Tag-Lifecycle!
Los geht’s! 🚀
🟢 GRUNDLAGEN: Was ist BodyTagSupport?
Definition
BodyTagSupport ist eine Basis-Klasse für Classic Tag Handler – die ursprüngliche API für Custom Tags in JSP (vor JSP 2.0).
Wichtig zu verstehen:
SimpleTag (Tag 5) wurde in JSP 2.0 eingeführt, um die API zu vereinfachen.
BodyTagSupport ist die ältere, komplexere, aber mächtigere Variante.
SimpleTag vs. BodyTagSupport – Der direkte Vergleich
| Feature | SimpleTag | BodyTagSupport |
|---|---|---|
| Eingeführt | JSP 2.0 (2003) | JSP 1.2 (2001) |
| Basis-Klasse | SimpleTagSupport | BodyTagSupport |
| Lifecycle | 1 Methode (doTag()) | 3+ Methoden |
| Body verarbeiten | invoke() | doAfterBody() Loop |
| Iteration | Manuell im Code | Mit Return-Codes |
| Komplexität | ⭐ Einfach | ⭐⭐⭐ Komplex |
| Use Cases | 90% aller Tags | Legacy + Spezialfälle |
Wann welches nutzen?
Nutze SimpleTag wenn:
- ✅ Einfache Tags
- ✅ Body einmal verarbeiten
- ✅ Moderne Codebasis
- ✅ Du die Wahl hast
Nutze BodyTagSupport wenn:
- ✅ Legacy-Code warten
- ✅ Body mehrfach iterieren (wie
<c:forEach>) - ✅ Feinste Kontrolle über Lifecycle
- ✅ Kompatibilität mit alten Servern
Der Classic Tag Lifecycle
Das ist der Kern von BodyTagSupport!
Wenn ein Tag ausgeführt wird, durchläuft er mehrere Phasen:
[1] setPageContext() → Container setzt JSP-Context
[2] setParent() → Container setzt Parent-Tag (falls nested)
[3] setAttribute() → Container setzt alle Attribute
↓
[4] doStartTag() → Tag beginnt
↓
[5] setBodyContent() → Container gibt Body-Buffer
↓
[6] doInitBody() → Body-Initialisierung
↓
[7] doAfterBody() → Body wurde verarbeitet
↓ (kann mehrfach aufgerufen werden!)
[8] doEndTag() → Tag endet
↓
[9] release() → Cleanup (optional)
Was macht dieser Lifecycle?
Der Lifecycle definiert die Reihenfolge, in der der Container deine Tag-Handler-Methoden aufruft.
Die wichtigsten Methoden im Detail:
doStartTag() – Der Anfang
public int doStartTag() throws JspException {
// Wird aufgerufen, wenn Tag startet
// Return-Wert entscheidet, was als nächstes passiert
return EVAL_BODY_BUFFERED; // Oder SKIP_BODY
}
Mögliche Return-Werte:
EVAL_BODY_BUFFERED(2011) → Body verarbeiten, Buffer erstellenSKIP_BODY(0) → Body ignorierenEVAL_BODY_INCLUDE(1) → Body direkt ausgeben (deprecated)
doAfterBody() – Nach dem Body
public int doAfterBody() throws JspException {
// Wird aufgerufen, nachdem Body verarbeitet wurde
// Hier kannst du Body-Content lesen und manipulieren
return SKIP_BODY; // Oder EVAL_BODY_AGAIN
}
Mögliche Return-Werte:
SKIP_BODY(0) → Fertig, weiter zu doEndTag()EVAL_BODY_AGAIN(2) → Body nochmal verarbeiten! (Iteration!)
doEndTag() – Das Ende
public int doEndTag() throws JspException {
// Wird aufgerufen, wenn Tag endet
// Cleanup, finale Ausgabe
return EVAL_PAGE; // Oder SKIP_PAGE
}
Mögliche Return-Werte:
EVAL_PAGE(6) → JSP-Seite normal weiter verarbeitenSKIP_PAGE(5) → Rest der JSP überspringen (selten genutzt!)
Wichtig zu verstehen:
Die Return-Codes sind Integer-Konstanten. Du gibst sie zurück, um dem Container zu sagen, was er als nächstes tun soll.
In der Praxis bedeutet das:
Der Container ruft deine Methoden auf und reagiert auf deine Return-Werte:
Container: "doStartTag() bitte!" Du: "EVAL_BODY_BUFFERED" → Container erstellt Buffer Container: "doAfterBody() bitte!" Du: "EVAL_BODY_AGAIN" → Container ruft doAfterBody() nochmal auf! Du: "SKIP_BODY" → Container geht zu doEndTag() Container: "doEndTag() bitte!" Du: "EVAL_PAGE" → Container macht normal weiter
🟢 GRUNDLAGEN: Dein erstes BodyTagSupport Tag
Ziel:
Ein einfaches Tag, das seinen Body in Großbuchstaben umwandelt (wie gestern mit SimpleTag, aber mit BodyTagSupport).
Implementation
1. Tag Handler Klasse:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.BodyTagSupport;
import jakarta.servlet.jsp.tagext.BodyContent;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
public class UppercaseBodyTag extends BodyTagSupport {
@Override
public int doStartTag() throws JspException {
// Tag startet - wir wollen den Body verarbeiten
return EVAL_BODY_BUFFERED; // Wichtig: Buffer erstellen!
}
@Override
public int doAfterBody() throws JspException {
try {
// Body-Content holen
BodyContent bodyContent = getBodyContent();
String content = bodyContent.getString();
// In Uppercase umwandeln
String uppercased = content.toUpperCase();
// In Output-Stream schreiben
bodyContent.getEnclosingWriter().write(uppercased);
// Fertig - keine weitere Iteration
return SKIP_BODY;
} catch (IOException e) {
throw new JspException("Error in UppercaseBodyTag", e);
}
}
@Override
public int doEndTag() throws JspException {
// Tag endet - normal weitermachen
return EVAL_PAGE;
}
}
Was macht dieser Code?
Das ist dein erster Classic Tag Handler! Lass uns jeden Teil verstehen:
Die Vererbung von BodyTagSupportBodyTagSupport stellt uns alle Lifecycle-Methoden bereit. Wir überschreiben nur die, die wir brauchen.
doStartTag() – Der Start
return EVAL_BODY_BUFFERED;
Das sagt dem Container: „Erstelle einen Buffer für den Body-Content!“
Wichtig zu verstehen:EVAL_BODY_BUFFERED erstellt einen BodyContent-Buffer. Der Body wird NICHT direkt ausgegeben, sondern erst in diesem Buffer gesammelt.
doAfterBody() – Die Hauptarbeit
BodyContent bodyContent = getBodyContent();
getBodyContent() gibt uns Zugriff auf den Buffer mit dem Body-Content.
Der Unterschied zu SimpleTag:
Bei SimpleTag hatten wir getJspBody().invoke(writer).
Bei BodyTagSupport haben wir ein BodyContent-Objekt mit mehr Möglichkeiten!
Den Content verarbeiten:
String content = bodyContent.getString(); String uppercased = content.toUpperCase();
Wir holen den Content als String und transformieren ihn.
Ausgabe in den richtigen Stream:
bodyContent.getEnclosingWriter().write(uppercased);
Kritisch: getEnclosingWriter() gibt den echten Output-Stream der JSP zurück!
bodyContent selbst ist nur der Buffer. Wir müssen in den „enclosing writer“ schreiben, damit es im finalen HTML landet.
Return SKIP_BODY:
return SKIP_BODY;
Das sagt: „Ich bin fertig mit dem Body, geh weiter zu doEndTag().“
doEndTag() – Das Ende
return EVAL_PAGE;
Das sagt: „JSP-Seite normal weitermachen.“
In der Praxis bedeutet das:
- JSP enthält
<fleet:uppercaseBody>hello world</fleet:uppercaseBody> - Container ruft
doStartTag()→ Wir gebenEVAL_BODY_BUFFEREDzurück - Container erstellt Buffer und verarbeitet Body → „hello world“ landet im Buffer
- Container ruft
doAfterBody()→ Wir transformieren zu „HELLO WORLD“ und gebenSKIP_BODYzurück - Container ruft
doEndTag()→ Wir gebenEVAL_PAGEzurück - Fertig! User sieht „HELLO WORLD“
2. TLD-Eintrag:
<tag>
<name>uppercaseBody</name>
<tag-class>com.javafleet.tags.UppercaseBodyTag</tag-class>
<body-content>scriptless</body-content>
<info>Converts body content to uppercase using BodyTagSupport</info>
</tag>
3. In JSP verwenden:
<%@ taglib prefix="fleet" uri="http://javafleet.com/tags" %>
<fleet:uppercaseBody>
this will be converted to uppercase!
</fleet:uppercaseBody>
<!-- Output: THIS WILL BE CONVERTED TO UPPERCASE! -->
🟡 PROFESSIONALS: Iteration mit EVAL_BODY_AGAIN
Konzept
Der große Vorteil von BodyTagSupport:
Du kannst EVAL_BODY_AGAIN zurückgeben, um den Body mehrfach zu verarbeiten!
Use Case: Ein <fleet:repeat times="3"> Tag, das seinen Body 3x wiederholt.
Implementation: Repeat Tag
Ziel:
<fleet:repeat times="5">
<p>This line appears 5 times!</p>
</fleet:repeat>
Tag Handler:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.BodyTagSupport;
import jakarta.servlet.jsp.tagext.BodyContent;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
public class RepeatTag extends BodyTagSupport {
// Attribut
private int times;
// Counter für Iteration
private int iteration;
// Setter für Attribut (PFLICHT!)
public void setTimes(int times) {
this.times = times;
}
@Override
public int doStartTag() throws JspException {
// Initialisiere Counter
iteration = 0;
if (times > 0) {
return EVAL_BODY_BUFFERED; // Body verarbeiten
} else {
return SKIP_BODY; // times <= 0: Body überspringen
}
}
@Override
public int doAfterBody() throws JspException {
try {
// Body-Content holen
BodyContent bodyContent = getBodyContent();
String content = bodyContent.getString();
// Content ausgeben
bodyContent.getEnclosingWriter().write(content);
// Counter erhöhen
iteration++;
// Buffer leeren für nächste Iteration
bodyContent.clearBody();
// Noch mal wiederholen?
if (iteration < times) {
return EVAL_BODY_AGAIN; // Ja, nochmal!
} else {
return SKIP_BODY; // Nein, fertig!
}
} catch (IOException e) {
throw new JspException("Error in RepeatTag", e);
}
}
@Override
public int doEndTag() throws JspException {
// Cleanup
iteration = 0;
return EVAL_PAGE;
}
}
Was macht dieser Code?
Das ist ein Iterations-Tag – der Kern von BodyTagSupport! Lass uns die wichtigen Teile verstehen:
Der Iteration Counter
private int iteration;
Wir speichern, wie oft wir den Body schon verarbeitet haben. Das ist eine Instanzvariable des Tags.
doStartTag() – Initialisierung
iteration = 0;
Wir setzen den Counter auf 0, bevor wir starten.
Wichtig zu verstehen:
Tag-Instanzen können wiederverwendet werden! Immer in doStartTag() initialisieren, nicht im Konstruktor!
doAfterBody() – Die Iterations-Logik
bodyContent.getEnclosingWriter().write(content); iteration++; bodyContent.clearBody();
Kritisch: bodyContent.clearBody() leert den Buffer!
Warum? Weil der Body nochmal evaluiert wird bei EVAL_BODY_AGAIN. Ohne clearBody() hätten wir den alten Content noch im Buffer!
Die Entscheidung:
if (iteration < times) {
return EVAL_BODY_AGAIN; // Nochmal!
} else {
return SKIP_BODY; // Fertig!
}
Das ist der Kern: Solange iteration < times, geben wir EVAL_BODY_AGAIN zurück.
Was passiert bei EVAL_BODY_AGAIN?
Der Container:
- Evaluiert den Body nochmal
- Ruft
doAfterBody()nochmal auf - Wiederholt das, bis
SKIP_BODYzurückgegeben wird
In der Praxis bedeutet das:
<fleet:repeat times="3">
<p>Hello!</p>
</fleet:repeat>
Lifecycle:
doStartTag()→EVAL_BODY_BUFFERED, iteration=0- Body evaluiert →
<p>Hello!</p>in Buffer doAfterBody()→ Ausgeben, iteration=1, clearBuffer,EVAL_BODY_AGAIN- Body evaluiert →
<p>Hello!</p>in Buffer doAfterBody()→ Ausgeben, iteration=2, clearBuffer,EVAL_BODY_AGAIN- Body evaluiert →
<p>Hello!</p>in Buffer doAfterBody()→ Ausgeben, iteration=3, clearBuffer,SKIP_BODYdoEndTag()→EVAL_PAGE- Ergebnis: Drei
<p>Hello!</p>im HTML!
TLD-Eintrag:
<tag>
<name>repeat</name>
<tag-class>com.javafleet.tags.RepeatTag</tag-class>
<body-content>scriptless</body-content>
<info>Repeats body content n times</info>
<attribute>
<name>times</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>int</type>
</attribute>
</tag>
Verwendung:
<fleet:repeat times="5">
<p>This appears 5 times!</p>
</fleet:repeat>
<!-- Mit EL: -->
<fleet:repeat times="${numberOfRepeats}">
<p>Dynamic repetition!</p>
</fleet:repeat>
🟡 PROFESSIONALS: Iteration über Collections
Konzept
Der ultimative Use Case für BodyTagSupport:
Iteration über Collections – wie JSTL’s <c:forEach>!
Ziel:
<fleet:forEach items="${products}" var="product">
<p>${product.name}: ${product.price}€</p>
</fleet:forEach>
Implementation: forEach Tag
Tag Handler:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.BodyTagSupport;
import jakarta.servlet.jsp.tagext.BodyContent;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
public class ForEachTag extends BodyTagSupport {
// Attribute
private Collection<?> items;
private String var;
// Iterator für Collection
private Iterator<?> iterator;
// Setter
public void setItems(Collection<?> items) {
this.items = items;
}
public void setVar(String var) {
this.var = var;
}
@Override
public int doStartTag() throws JspException {
// Validierung
if (items == null || items.isEmpty()) {
return SKIP_BODY;
}
// Iterator erstellen
iterator = items.iterator();
// Erstes Element in PageContext setzen
if (iterator.hasNext()) {
Object currentItem = iterator.next();
pageContext.setAttribute(var, currentItem);
return EVAL_BODY_BUFFERED;
} else {
return SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException {
try {
// Body-Content ausgeben
BodyContent bodyContent = getBodyContent();
String content = bodyContent.getString();
bodyContent.getEnclosingWriter().write(content);
// Buffer leeren
bodyContent.clearBody();
// Nächstes Element?
if (iterator.hasNext()) {
// Nächstes Element in PageContext setzen
Object currentItem = iterator.next();
pageContext.setAttribute(var, currentItem);
// Nochmal iterieren!
return EVAL_BODY_AGAIN;
} else {
// Fertig - Cleanup
pageContext.removeAttribute(var);
return SKIP_BODY;
}
} catch (IOException e) {
throw new JspException("Error in ForEachTag", e);
}
}
@Override
public int doEndTag() throws JspException {
// Cleanup
iterator = null;
items = null;
return EVAL_PAGE;
}
}
Was macht dieser Code?
Das ist ein vollwertiger Collection-Iterator! Lass uns die Magie verstehen:
Die Attribute
private Collection<?> items; // Die Collection zum Iterieren private String var; // Der Variablenname in der JSP private Iterator<?> iterator; // Unser Iterator
Wichtig zu verstehen:items ist die Collection (z.B. List<Product>).var ist der Name, unter dem das aktuelle Element in der JSP verfügbar ist.iterator behalten wir zwischen den doAfterBody() Aufrufen!
doStartTag() – Setup
iterator = items.iterator(); Object currentItem = iterator.next(); pageContext.setAttribute(var, currentItem);
Das ist der Trick: Wir setzen das erste Element in den PageContext unter dem Namen var!
Was ist PageContext?
Der PageContext ist eine Art „Map“, die während der JSP-Verarbeitung verfügbar ist. Wenn wir dort pageContext.setAttribute("product", productObject) setzen, können wir in der JSP mit ${product} darauf zugreifen!
doAfterBody() – Die Iterations-Schleife
if (iterator.hasNext()) {
Object currentItem = iterator.next();
pageContext.setAttribute(var, currentItem);
return EVAL_BODY_AGAIN;
}
Bei jedem Durchlauf:
- Nächstes Element holen
- In PageContext setzen (überschreibt vorheriges!)
EVAL_BODY_AGAINzurückgeben- Container evaluiert Body nochmal → diesmal mit neuem Element!
In der Praxis bedeutet das:
<fleet:forEach items="${products}" var="product">
<p>${product.name}</p>
</fleet:forEach>
Lifecycle mit 3 Produkten:
doStartTag()→ product[0] in PageContext,EVAL_BODY_BUFFERED- Body evaluiert →
<p>Laptop</p>(product[0].name) doAfterBody()→ product[1] in PageContext,EVAL_BODY_AGAIN- Body evaluiert →
<p>Mouse</p>(product[1].name) doAfterBody()→ product[2] in PageContext,EVAL_BODY_AGAIN- Body evaluiert →
<p>Keyboard</p>(product[2].name) doAfterBody()→ Keine weiteren Elemente,SKIP_BODYdoEndTag()→EVAL_PAGE
Ergebnis:
<p>Laptop</p> <p>Mouse</p> <p>Keyboard</p>
Das ist genau wie JSTL’s <c:forEach> funktioniert!
TLD-Eintrag:
<tag>
<name>forEach</name>
<tag-class>com.javafleet.tags.ForEachTag</tag-class>
<body-content>scriptless</body-content>
<info>Iterates over a collection (like JSTL c:forEach)</info>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Collection</type>
</attribute>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
Verwendung:
<%
// Im Servlet:
List<Product> products = productService.getAll();
request.setAttribute("products", products);
%>
<!-- In JSP: -->
<fleet:forEach items="${products}" var="product">
<div class="product-card">
<h3>${product.name}</h3>
<p>Price: ${product.price}€</p>
<p>Stock: ${product.stock}</p>
</div>
</fleet:forEach>
🔵 BONUS: Nested Tags – Parent-Child Kommunikation
Konzept
Advanced Feature:
Tags können verschachtelt sein und miteinander kommunizieren!
Use Case:
Ein <fleet:table> Tag mit <fleet:column> Child-Tags:
<fleet:table items="${users}">
<fleet:column property="name" header="Name" />
<fleet:column property="email" header="Email" />
<fleet:column property="age" header="Age" />
</fleet:table>
Implementation
Parent Tag – TableTag:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.BodyTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
public class TableTag extends BodyTagSupport {
private Collection<?> items;
private Iterator<?> iterator;
private Object currentItem;
// Columns sammeln von Child-Tags
private List<ColumnTag> columns = new ArrayList<>();
public void setItems(Collection<?> items) {
this.items = items;
}
// Child-Tags registrieren sich hier!
public void addColumn(ColumnTag column) {
columns.add(column);
}
// Child-Tags können aktuelles Item holen
public Object getCurrentItem() {
return currentItem;
}
@Override
public int doStartTag() throws JspException {
if (items == null || items.isEmpty()) {
return SKIP_BODY;
}
iterator = items.iterator();
// Body erstmal evaluieren, um Columns zu sammeln
return EVAL_BODY_BUFFERED;
}
@Override
public int doAfterBody() throws JspException {
try {
// Beim ersten Mal: Table-Header schreiben
if (iterator.hasNext() && currentItem == null) {
writeTableHeader();
currentItem = iterator.next();
return EVAL_BODY_AGAIN; // Erste Row
}
// Row schreiben
writeTableRow();
// Body-Content verwerfen (wir generieren eigenes HTML)
getBodyContent().clearBody();
// Nächstes Item?
if (iterator.hasNext()) {
currentItem = iterator.next();
return EVAL_BODY_AGAIN;
} else {
writeTableFooter();
return SKIP_BODY;
}
} catch (IOException e) {
throw new JspException("Error in TableTag", e);
}
}
private void writeTableHeader() throws IOException {
StringBuilder html = new StringBuilder();
html.append("<table class='fleet-table'>\n");
html.append(" <thead>\n");
html.append(" <tr>\n");
for (ColumnTag column : columns) {
html.append(" <th>").append(column.getHeader()).append("</th>\n");
}
html.append(" </tr>\n");
html.append(" </thead>\n");
html.append(" <tbody>\n");
getBodyContent().getEnclosingWriter().write(html.toString());
}
private void writeTableRow() throws IOException {
StringBuilder html = new StringBuilder();
html.append(" <tr>\n");
for (ColumnTag column : columns) {
String value = column.getValueFromItem(currentItem);
html.append(" <td>").append(value).append("</td>\n");
}
html.append(" </tr>\n");
getBodyContent().getEnclosingWriter().write(html.toString());
}
private void writeTableFooter() throws IOException {
String html = " </tbody>\n</table>\n";
getBodyContent().getEnclosingWriter().write(html);
}
@Override
public int doEndTag() throws JspException {
// Cleanup
columns.clear();
iterator = null;
currentItem = null;
return EVAL_PAGE;
}
}
Child Tag – ColumnTag:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.TagSupport;
import jakarta.servlet.jsp.JspException;
import java.lang.reflect.Method;
public class ColumnTag extends TagSupport {
private String property;
private String header;
public void setProperty(String property) {
this.property = property;
}
public void setHeader(String header) {
this.header = header;
}
public String getHeader() {
return header != null ? header : property;
}
@Override
public int doStartTag() throws JspException {
// Parent-Tag finden
TableTag parent = (TableTag) findAncestorWithClass(this, TableTag.class);
if (parent == null) {
throw new JspException("ColumnTag must be nested inside TableTag!");
}
// Bei Parent registrieren
parent.addColumn(this);
// Kein Body - nur Config!
return SKIP_BODY;
}
public String getValueFromItem(Object item) {
try {
// Reflection: getProperty() auf item aufrufen
String methodName = "get" +
property.substring(0, 1).toUpperCase() +
property.substring(1);
Method method = item.getClass().getMethod(methodName);
Object value = method.invoke(item);
return value != null ? value.toString() : "";
} catch (Exception e) {
return "[Error: " + e.getMessage() + "]";
}
}
}
Was macht dieser Code?
Das ist Parent-Child-Kommunikation zwischen Tags! Lass uns verstehen, wie das funktioniert:
Das Konzept:<fleet:table> ist das Parent-Tag.<fleet:column> sind Child-Tags, die INS IDE des Table-Tags stehen.
Die Kommunikation:
Child-Tags rufen findAncestorWithClass() auf, um ihr Parent zu finden, und registrieren sich dort.
ColumnTag’s doStartTag():
TableTag parent = (TableTag) findAncestorWithClass(this, TableTag.class); parent.addColumn(this);
findAncestorWithClass() sucht in der Tag-Hierarchie nach einem Parent vom Typ TableTag.
Dann registrieren wir uns: Das Parent speichert uns in seiner columns-Liste!
TableTag sammelt Columns:
private List<ColumnTag> columns = new ArrayList<>();
public void addColumn(ColumnTag column) {
columns.add(column);
}
TableTag generiert HTML:
Beim ersten doAfterBody() Aufruf haben wir alle Columns gesammelt (weil der Body evaluiert wurde und alle <fleet:column> Tags sich registriert haben).
Jetzt iterieren wir über items und für jede Row über columns, um die Tabelle zu generieren!
In der Praxis bedeutet das:
<fleet:table items="${users}">
<fleet:column property="name" header="Name" />
<fleet:column property="email" header="Email" />
</fleet:table>
Lifecycle:
TableTag.doStartTag()→EVAL_BODY_BUFFERED- Body evaluiert → Beide
ColumnTags registrieren sich bei TableTag TableTag.doAfterBody()→ Generiert<table><thead>...mit gesammelten Columns- Iteriert über items, für jede Row ruft
EVAL_BODY_AGAIN - Jede Iteration generiert
<tr><td>...</td></tr> - Am Ende
</tbody></table>
Das ist wie Bootstrap-Table-Components arbeiten!
TLD-Einträge:
<tag>
<name>table</name>
<tag-class>com.javafleet.tags.TableTag</tag-class>
<body-content>scriptless</body-content>
<info>Generates HTML table from collection</info>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Collection</type>
</attribute>
</tag>
<tag>
<name>column</name>
<tag-class>com.javafleet.tags.ColumnTag</tag-class>
<body-content>empty</body-content>
<info>Defines a column in table tag</info>
<attribute>
<name>property</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>header</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
🟡 PROFESSIONALS: Best Practices
1. Return-Code Constants verwenden
✅ DO:
return EVAL_BODY_BUFFERED; return EVAL_BODY_AGAIN; return SKIP_BODY;
❌ DON’T:
return 2011; // Was bedeutet das?! return 2; // Unleserlich!
Die Konstanten sind selbst-dokumentierend!
2. Immer in doStartTag() initialisieren
✅ DO:
@Override
public int doStartTag() throws JspException {
iteration = 0; // Reset!
// ...
}
❌ DON’T:
public RepeatTag() {
iteration = 0; // Falsch!
}
Warum? Tag-Instanzen werden vom Container wiederverwendet (Pooling)!
3. bodyContent.clearBody() nicht vergessen
✅ DO:
@Override
public int doAfterBody() throws JspException {
// ... Content verarbeiten ...
bodyContent.clearBody(); // Wichtig!
return EVAL_BODY_AGAIN;
}
Sonst akkumuliert der Buffer bei jeder Iteration!
4. Cleanup in doEndTag()
✅ DO:
@Override
public int doEndTag() throws JspException {
// Cleanup
iterator = null;
items = null;
columns.clear();
return EVAL_PAGE;
}
Verhindert Memory Leaks bei Tag-Pooling!
5. Error Handling
✅ DO:
@Override
public int doAfterBody() throws JspException {
try {
// ... Code ...
} catch (IOException e) {
throw new JspException("Error in MyTag: " + e.getMessage(), e);
}
}
Wrappen von checked Exceptions in JspException!
💬 Real Talk: Warum BodyTagSupport wenn SimpleTag einfacher ist?
Java Fleet Küche, 14:00 Uhr. Nova hat gerade 2 Stunden mit BodyTagSupport gekämpft.
Nova: „Elyndra, ich verstehe nicht. Warum lernen wir BodyTagSupport? SimpleTag war doch SO viel einfacher!“
Elyndra: „Gute Frage! Drei Szenarien aus der Praxis:
Szenario 1: Legacy-Code
Letzten Monat habe ich ein 15 Jahre altes E-Commerce-System gewartet. Tausende Custom Tags – alle mit BodyTagSupport. Wenn ich die nicht verstehe, kann ich den Code nicht warten.
Szenario 2: Komplexe Iteration
Wir hatten einen Customer, der wollte ein komplexes Reporting-Tag. Iteration über mehrere Collections gleichzeitig, verschachtelte Loops, conditional rendering. Mit SimpleTag hätten wir das in 500 Zeilen Code machen müssen. Mit BodyTagSupport in 150 – weil der Lifecycle-Management vom Container übernommen wird.
Szenario 3: Tiefes Verständnis
Wenn du verstehst, wie JSTL’s <c:forEach> unter der Haube funktioniert, verstehst du auch, warum manche Performance-Probleme auftreten. Und wie du sie löst.“
Nova: „Okay, aber für neue Projekte nutze ich SimpleTag, right?“
Elyndra: „Zu 90% ja! SimpleTag für einfache bis mittlere Komplexität. BodyTagSupport nur wenn:
- Du Legacy-Code wartest
- Du sehr komplexe Iteration brauchst
- Du maximale Performance-Kontrolle willst
Real talk: Ich habe in den letzten 2 Jahren nur EINMAL ein neues BodyTagSupport-Tag geschrieben. Aber ich habe HUNDERTE gewartet.
Das ist wie Manual vs. Automatic Getriebe. Du fährst wahrscheinlich Automatic – aber du solltest wissen, wie Manual funktioniert.“
Nova: „Makes sense. Also lernen für Legacy, aber nutzen SimpleTag für Neues?“
Elyndra: „Exactly! Und jetzt weißt du, warum <c:forEach> so performant ist – es ist mit BodyTagSupport gebaut!“
✅ Checkpoint & Quiz
Mini-Challenge:
Erstelle ein <fleet:if> Tag mit BodyTagSupport, das:
- Ein
testAttribut vom Typbooleanhat - Den Body nur anzeigt, wenn
test == true - Mit
EVAL_BODY_BUFFEREDarbeitet
Quiz:
- Was ist der Unterschied zwischen
EVAL_BODY_BUFFEREDundSKIP_BODY? - Wann gibt man
EVAL_BODY_AGAINzurück? - Was macht
bodyContent.clearBody()und warum ist es wichtig? - Warum muss man in
doStartTag()initialisieren und nicht im Konstruktor? - Wie kommunizieren Child-Tags mit Parent-Tags?
- Was ist der Unterschied zwischen
bodyContent.getEnclosingWriter()undbodyContent.getWriter()?
Lösung: Am Anfang von Tag 7!
❓ FAQ
F1: Wann sollte ich BodyTagSupport statt SimpleTag nutzen?
Antwort:
Nutze BodyTagSupport wenn:
- ✅ Du Legacy-Code wartest (häufigster Fall!)
- ✅ Du sehr komplexe Iteration über mehrere Collections brauchst
- ✅ Du feinste Kontrolle über den Lifecycle willst
- ✅ Performance-kritische Tags mit vielen Iterationen
Nutze SimpleTag wenn:
- ✅ Du neuen Code schreibst
- ✅ Body einmal oder gar nicht verarbeitet wird
- ✅ Einfachere Wartbarkeit wichtiger ist
- ✅ Du die Wahl hast (90% der Fälle!)
Regel: SimpleTag first, BodyTagSupport nur wenn nötig!
F2: Warum heißt es EVAL_BODY_BUFFERED und nicht einfach PROCESS_BODY?
Historical Context:
Die Konstanten stammen aus JSP 1.2 (2001). Damals gab es drei Optionen:
EVAL_BODY_INCLUDE(1) – Body direkt ausgebenEVAL_BODY_BUFFERED(2011) – Body in BufferSKIP_BODY(0) – Body ignorieren
„EVAL“ = „Evaluate“ (auswerten)
„BUFFERED“ = Im Buffer sammeln
EVAL_BODY_INCLUDE ist deprecated – deshalb nutzen wir nur noch BUFFERED.
Die Zahlen (1, 2011, 0) sind die tatsächlichen Integer-Werte der Konstanten!
F3: Was passiert, wenn ich bodyContent.clearBody() vergesse?
Problem:
@Override
public int doAfterBody() throws JspException {
String content = bodyContent.getString();
// Content ausgeben...
// bodyContent.clearBody(); ← VERGESSEN!
return EVAL_BODY_AGAIN; // Body nochmal!
}
Resultat:
Bei EVAL_BODY_AGAIN evaluiert der Container den Body nochmal und HÄNGT den neuen Content an den alten an!
Beispiel:
<fleet:repeat times="3">
<p>Hello</p>
</fleet:repeat>
Ohne clearBody():
Iteration 1: "<p>Hello</p>" Iteration 2: "<p>Hello</p><p>Hello</p>" (verdoppelt!) Iteration 3: "<p>Hello</p><p>Hello</p><p>Hello</p>" (verdreifacht!)
Ergebnis: 6x statt 3x Hello! 😱
Lösung: IMMER clearBody() vor EVAL_BODY_AGAIN!
F4: Kann ich BodyTagSupport und SimpleTag mischen?
Ja, absolut!
In derselben TLD kannst du beides haben:
<taglib>
<!-- SimpleTag -->
<tag>
<name>uppercase</name>
<tag-class>com.javafleet.tags.UppercaseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
<!-- BodyTagSupport -->
<tag>
<name>forEach</name>
<tag-class>com.javafleet.tags.ForEachTag</tag-class>
<body-content>scriptless</body-content>
</tag>
</taglib>
Best Practice: Nutze das richtige Tool für jeden Use-Case!
F5: Wie debugge ich BodyTagSupport Tags?
Strategie:
1. Logging in Lifecycle-Methoden:
@Override
public int doStartTag() throws JspException {
System.out.println("RepeatTag.doStartTag() - times=" + times);
return EVAL_BODY_BUFFERED;
}
@Override
public int doAfterBody() throws JspException {
System.out.println("RepeatTag.doAfterBody() - iteration=" + iteration);
// ...
int result = (iteration < times) ? EVAL_BODY_AGAIN : SKIP_BODY;
System.out.println(" → returning " + result);
return result;
}
2. Content inspizieren:
String content = bodyContent.getString();
System.out.println("Body content: " + content);
3. PageContext dumpen:
Enumeration<String> attrs = pageContext.getAttributeNamesInScope(PageContext.PAGE_SCOPE);
while (attrs.hasMoreElements()) {
String name = attrs.nextElement();
System.out.println(" " + name + " = " + pageContext.getAttribute(name));
}
4. Breakpoints setzen: In NetBeans: Breakpoint in doAfterBody() setzen, dann Request ausführen und durch Lifecycle steppen!
F6: Kann ich mit BodyTagSupport auch über Maps iterieren?
Ja! Erweiterte ForEachTag-Version:
public class ForEachMapTag extends BodyTagSupport {
private Map<?, ?> items;
private String keyVar;
private String valueVar;
private Iterator<?> keyIterator;
public void setItems(Map<?, ?> items) {
this.items = items;
}
public void setKeyVar(String keyVar) {
this.keyVar = keyVar;
}
public void setValueVar(String valueVar) {
this.valueVar = valueVar;
}
@Override
public int doStartTag() throws JspException {
if (items == null || items.isEmpty()) {
return SKIP_BODY;
}
keyIterator = items.keySet().iterator();
if (keyIterator.hasNext()) {
setCurrentEntry();
return EVAL_BODY_BUFFERED;
} else {
return SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException {
try {
bodyContent.getEnclosingWriter().write(bodyContent.getString());
bodyContent.clearBody();
if (keyIterator.hasNext()) {
setCurrentEntry();
return EVAL_BODY_AGAIN;
} else {
pageContext.removeAttribute(keyVar);
pageContext.removeAttribute(valueVar);
return SKIP_BODY;
}
} catch (IOException e) {
throw new JspException("Error in ForEachMapTag", e);
}
}
private void setCurrentEntry() {
Object key = keyIterator.next();
Object value = items.get(key);
pageContext.setAttribute(keyVar, key);
pageContext.setAttribute(valueVar, value);
}
}
Verwendung:
<fleet:forEachMap items="${userAges}" keyVar="name" valueVar="age">
<p>${name} is ${age} years old</p>
</fleet:forEachMap>
F7: Bernd meinte, „BodyTagSupport ist deprecated“. Stimmt das?
Nein, nicht ganz! 😄
Real talk: Bernd verwechselt das mit EVAL_BODY_TAG (die alte Konstante), die tatsächlich deprecated ist.
BodyTagSupport selbst ist NICHT deprecated!
Es ist Teil der Jakarta Servlet Spec und wird weiterhin unterstützt. Aber:
Was Bernd meint:
- SimpleTag (JSP 2.0+) ist der modernere Ansatz
- Für neue Projekte wird SimpleTag empfohlen
- BodyTagSupport ist „Legacy-Style“ aber nicht veraltet
Analogie:
- BodyTagSupport = Manuelle Schaltung
- SimpleTag = Automatik
Beide funktionieren, beide haben ihren Platz. Automatic ist moderner, aber Manual ist nicht verboten!
Fun Fact: JSTL’s <c:forEach> nutzt BodyTagSupport – und JSTL ist alles andere als deprecated! 🎯
📚 Quiz-Lösungen
Hier sind die Antworten zum Quiz von oben:
Frage 1: Was ist der Unterschied zwischen EVAL_BODY_BUFFERED und SKIP_BODY?
Antwort:
EVAL_BODY_BUFFERED (2011):
- Container erstellt einen
BodyContent-Buffer - Body wird evaluiert und in Buffer geschrieben
- Tag kann Body-Content lesen und manipulieren
doAfterBody()wird aufgerufen
SKIP_BODY (0):
- Body wird komplett ignoriert
- Kein Buffer erstellt
doAfterBody()wird NICHT aufgerufen- Container geht direkt zu
doEndTag()
Use Cases:
// Body verarbeiten: return EVAL_BODY_BUFFERED; // Body ignorieren (z.B. bei times=0): return SKIP_BODY;
Frage 2: Wann gibt man EVAL_BODY_AGAIN zurück?
Antwort:
EVAL_BODY_AGAIN (2) gibt man in doAfterBody() zurück, wenn man den Body nochmal evaluieren will.
Use Cases:
- Iteration: Body mehrfach wiederholen
- Collection-Iteration: Für jedes Element Body evaluieren
- Conditional Loops: Solange Bedingung true, Body wiederholen
Beispiel:
@Override
public int doAfterBody() throws JspException {
iteration++;
if (iteration < times) {
return EVAL_BODY_AGAIN; // Nochmal!
} else {
return SKIP_BODY; // Fertig!
}
}
Wichtig: Bei EVAL_BODY_AGAIN wird der Body neu evaluiert und doAfterBody() nochmal aufgerufen!
Frage 3: Was macht bodyContent.clearBody() und warum ist es wichtig?
Antwort:
bodyContent.clearBody() leert den Body-Buffer.
Warum wichtig bei Iteration?
Ohne clearBody():
Iteration 1: Body = "Hello" Iteration 2: Body = "HelloHello" (angehängt!) Iteration 3: Body = "HelloHelloHello" (nochmal angehängt!)
Mit clearBody():
Iteration 1: Body = "Hello" → clearBody() Iteration 2: Body = "Hello" → clearBody() Iteration 3: Body = "Hello" → clearBody()
Best Practice:
@Override
public int doAfterBody() throws JspException {
// Content holen und ausgeben
String content = bodyContent.getString();
bodyContent.getEnclosingWriter().write(content);
// KRITISCH: Buffer leeren!
bodyContent.clearBody();
return EVAL_BODY_AGAIN;
}
Regel: Vor jedem EVAL_BODY_AGAIN → clearBody() aufrufen!
Frage 4: Warum muss man in doStartTag() initialisieren und nicht im Konstruktor?
Antwort:
Tag-Pooling!
Der Container erstellt NICHT für jeden Request neue Tag-Instanzen. Stattdessen:
Mit Pooling:
Request 1: Tag-Instanz erstellt → verwendet → in Pool Request 2: Instanz aus Pool → wiederverwendet! Request 3: Dieselbe Instanz → wiederverwendet!
Problem mit Konstruktor:
public class RepeatTag extends BodyTagSupport {
private int iteration = 0; // Konstruktor-Init
// Konstruktor wird nur EINMAL aufgerufen!
public RepeatTag() {
iteration = 0; // Zu spät!
}
}
Bei Request 2 hat iteration noch den Wert vom Ende von Request 1!
Lösung: doStartTag():
@Override
public int doStartTag() throws JspException {
iteration = 0; // Bei jedem Request neu!
return EVAL_BODY_BUFFERED;
}
doStartTag() wird bei jedem Request aufgerufen – perfekt für Initialisierung!
Frage 5: Wie kommunizieren Child-Tags mit Parent-Tags?
Antwort:
Mit findAncestorWithClass():
// In Child-Tag:
@Override
public int doStartTag() throws JspException {
// Parent finden
TableTag parent = (TableTag) findAncestorWithClass(
this, // Von mir aus suchen
TableTag.class // Nach diesem Typ suchen
);
if (parent == null) {
throw new JspException("Must be nested in TableTag!");
}
// Bei Parent registrieren
parent.addColumn(this);
return SKIP_BODY;
}
Parent-Tag:
public class TableTag extends BodyTagSupport {
private List<ColumnTag> columns = new ArrayList<>();
// Child-Tags registrieren sich hier!
public void addColumn(ColumnTag column) {
columns.add(column);
}
}
Der Ablauf:
- Container evaluiert Body von
<fleet:table> - Dabei werden alle
<fleet:column>Tags evaluiert - Jedes Column-Tag findet sein Table-Parent
- Jedes Column-Tag registriert sich beim Parent
- Table-Tag hat jetzt alle Columns gesammelt!
Frage 6: Was ist der Unterschied zwischen bodyContent.getEnclosingWriter() und bodyContent.getWriter()?
Antwort:
getEnclosingWriter():
- Gibt den echten Output-Writer der JSP zurück
- Content, der hier geschrieben wird, landet im finalen HTML
- Das willst du normalerweise!
getWriter():
- Gibt den Buffer-Writer zurück
- Content wird nur im Buffer gesammelt, nicht ausgegeben
- Für spezielle Use-Cases (mehrfache Verarbeitung)
Best Practice:
@Override
public int doAfterBody() throws JspException {
try {
String content = bodyContent.getString();
// In echten Output schreiben:
bodyContent.getEnclosingWriter().write(content);
// NICHT:
// bodyContent.getWriter().write(content); ← Landet nur im Buffer!
bodyContent.clearBody();
return SKIP_BODY;
} catch (IOException e) {
throw new JspException(e);
}
}
Merksatz: „Enclosing“ = „umschließend“ = der Writer der JSP, die das Tag umschließt!
🎉 Tag 6 geschafft!
Slay! Du hast es geschafft! 🚀
Das hast du heute gerockt:
- ✅ BodyTagSupport von Grund auf verstanden
- ✅ Den kompletten Classic Tag Lifecycle gemeistert
- ✅ Return-Codes (EVAL_BODY_BUFFERED, EVAL_BODY_AGAIN, SKIP_BODY) beherrscht
- ✅ Iteration über Collections implementiert
- ✅ Parent-Child-Kommunikation zwischen Tags
- ✅ Unterschied zwischen SimpleTag und BodyTagSupport klar
Von einfachen Uppercase-Tags zu vollwertigen Collection-Iteratoren!
Main Character Energy: Unlocked! ✨
Dein neues Skillset:
- Classic Tag Handler Expertise
- Body Content mehrfach verarbeiten
- Komplexe Tag-Hierarchien bauen
- Legacy-Code verstehen und warten
Real talk:
BodyTagSupport ist komplex – aber du hast es geschafft! Du verstehst jetzt, wie JSTL’s <c:forEach> unter der Haube funktioniert. Das ist hardcore Jakarta EE Knowledge!
Wie geht’s weiter?
Morgen (Tag 7): JPA vs JDBC – Konfiguration & Provider
Was dich erwartet:
- Was ist JPA und warum brauchen wir es?
- Der Unterschied zwischen JPA und JDBC
- persistence.xml konfigurieren
- EntityManager verstehen
- JPA-Provider (Hibernate, EclipseLink) einrichten
- Erste Entity-Klassen erstellen – das wird dein Game-Changer für Datenbank-Arbeit! 🔥
Brauchst du eine Pause?
Absolut! BodyTagSupport ist intensive Kost. Lass das heute sacken, spiel mit den Beispielen, und morgen geht’s mit JPA weiter.
Tipp für heute Abend:
Nimm die ForEachTag-Implementation und erweitere sie:
- Füge ein
startundendAttribut hinzu (wie bei echtem<c:forEach>) - Füge ein
stepAttribut hinzu (jedes n-te Element) - Zeige einen Index an (varStatus=“status“)
Learning by doing! 🔧
🔧 Troubleshooting
Problem: IllegalStateException – Cannot buffer a body content once it has been flushed
Ursache: Body wurde bereits ausgegeben, bevor EVAL_BODY_BUFFERED zurückgegeben wurde.
Lösung:
@Override
public int doStartTag() throws JspException {
// Erst Return-Code, DANN nichts mehr ausgeben!
return EVAL_BODY_BUFFERED;
}
Nichts vor return EVAL_BODY_BUFFERED in den Output schreiben!
Problem: Body wird mehrfach ausgegeben, obwohl SKIP_BODY zurückgegeben wird
Ursache: clearBody() vergessen vor EVAL_BODY_AGAIN.
Lösung:
@Override
public int doAfterBody() throws JspException {
bodyContent.getEnclosingWriter().write(bodyContent.getString());
bodyContent.clearBody(); // KRITISCH!
return someCondition ? EVAL_BODY_AGAIN : SKIP_BODY;
}
Problem: NullPointerException bei getBodyContent()
Ursache: getBodyContent() gibt null zurück, wenn EVAL_BODY_BUFFERED nicht zurückgegeben wurde.
Lösung:
@Override
public int doStartTag() throws JspException {
// MUSS EVAL_BODY_BUFFERED sein für Body-Verarbeitung!
return EVAL_BODY_BUFFERED;
}
Problem: Parent-Tag wird nicht gefunden (findAncestorWithClass() gibt null)
Ursache: Child-Tag ist nicht korrekt verschachtelt.
Lösung:
<!-- FALSCH: -->
<fleet:column property="name" />
<fleet:table items="${users}" />
<!-- RICHTIG: Column INSIDE Table! -->
<fleet:table items="${users}">
<fleet:column property="name" />
</fleet:table>
Problem: Attribute-Werte sind null in doAfterBody()
Ursache: Setter wurden vom Container nicht aufgerufen (Namenskonvention falsch).
Lösung:
// Attribut "times" → Setter "setTimes()"
// FALSCH:
public void settimes(int t) { ... }
// RICHTIG:
public void setTimes(int times) { ... }
JavaBeans-Konvention beachten!
📚 Resources & Links
Offizielle Dokumentation:
- Jakarta Tag Library Spec: https://jakarta.ee/specifications/tags/
- JSP Specification: https://jakarta.ee/specifications/pages/
- BodyTagSupport JavaDoc: https://jakarta.ee/specifications/tags/3.0/apidocs/jakarta/servlet/jsp/tagext/BodyTagSupport.html
Tutorials:
- Baeldung – JSP Custom Tags: https://www.baeldung.com/jsp-custom-tags
- Oracle – Tag Library Development: https://docs.oracle.com/javaee/5/tutorial/doc/bnalj.html
- TutorialsPoint – JSP Custom Tags: https://www.tutorialspoint.com/jsp/jsp_custom_tags.htm
Legacy aber lehrreich:
- Java EE 5 Tutorial – Classic Tags: https://docs.oracle.com/javaee/5/tutorial/doc/bnama.html
- Core Servlets – Custom Tags: http://www.coreservlets.com/JSF-Tutorial/jsf-tags.html
Best Practices:
- IBM Developer – Writing Custom JSP Tags: https://developer.ibm.com/articles/wa-jsptags/
- DZone – JSP Tag Development: https://dzone.com/articles/jsp-custom-tags
Vergleich SimpleTag vs. BodyTagSupport:
- Stack Overflow Discussion: https://stackoverflow.com/questions/tagged/bodytag+jsp
💬 Feedback?
War Tag 6 zu komplex? Zu viele Return-Codes? Mehr Beispiele gewünscht?
Schreib uns: feedback@java-developer.online
Wir wollen, dass du erfolgreich lernst!
Bis morgen! 👋
Elyndra
elyndra@java-developer.online
Senior Developer bei Java Fleet Systems Consulting
Java Web Aufbau – Tag 6 von 10
© 2025 Java Fleet Systems Consulting
Website: java-developer.online

