Die Software-Architekten GmbH

Java Enterprise Development • Web Content Management • E-Commerce

JSF, Facelets und JSTL – Komponenten und Tags im JSF-Lifecycle

Dipl.Inf. Oliver Nolden

Eine Tatsache, die bei der Umsetzung von Webanwendungen mit JSF und Facelets immer wieder für Irritationen sorgt ist die unterschiedliche Behandlung von TagHandlern und ComponentHandlern im JSF-Lifecycle. Besonders die Verschachtelung von Tags dieser beiden Klassen kann zu unvorhersehbarem Verhalten oder schlechter Performance führen, wenn die Funktionsweise unklar ist. Leider wird diesem Umstand in der JSF-Dokumentation viel zu wenig Beachtung geschenkt, daher möchte ich hier einmal näher darauf eingehen.

Zunächst ein kurzer Überblick über die einzelnen Phasen im JSF-Lifecycle mit Facelets. Dieser beginnt mit der Kompilierphase, während der aus den Facelet-Definitionen Komponenten erstellt werden, die zum Komponentenbaum zusammengefügt werden. Anschließend werden auf dem fertigen Baum die klassischen JSF-Phasen durchlaufen (Restore View, Apply request values, validations, update model, …). Entscheidend in diesem Zusammenhang ist, dass die Kompilierphase immer komplett getrennt von dem Rest ausgeführt wird, mit dem Ziel, den Komponentenbaum initial zu erzeugen.

TagHandler vs. ComponentHandler

Warum sollte man das wissen? TagHandler und ComponentHandler werden in der Praxis oft kombiniert, ohne dass dem Entwickler die Unterschiede klar sind. Wichtig: ComponentHandler stellen Tags dar, die in der JSF-Phase Komponenten erzeugen, die entsprechend im Komponentenbaum landen. Dagegen werden TagHandler in der Kompilierphase ausgewertet und stellen selbst keine Komponenten dar – sie dienen jedoch der programmatischen Erzeugung des Komponentenbaumes.

Für zusätzliche Verwirrung sorgen Tags, die ähnliche Konstrukte beschreiben. Beispiele für TagHandler und ComponentHandler:

TagHandler ComponentHandler
c:forEach ui:repeat
c:if h:inputText
c:set h:outputText
ui:include h:dataTable

Wie man sieht, gibt es keine Namenskonvention, die bei der Klassifizierung helfen könnte, obwohl falsch genutzte Tags stundenlange Fehlersuche bedeuten können.

Inkompatible Tags

Aber wo liegt nun das Problem? Hier einige Beispiele:
Ein klassischer Stolperstein sind c:forEach und ui:repeat – beide Tags beschreiben ein Schleifenkonstrukt. Der Unterschied: ui:repeat ist eine Komponente im JSF-Lebenszyklus, c:forEach wird nur in der Kompilierphase ausgeführt und ist selbst keine Komponente. Was bedeutet dies? Betrachten wir zwei Codebeispiele, von denen man erwarten könnte, dass sie das gleiche Ergebnis erzeugen:

<c:forEach var="row" items="#{bean.rows}">
  <h:outputText value="#{row.text}" />
</c:forEach>

In diesem Fall wird die Schleife in der Kompilierphase ausgeführt. Mit jeder Iteration wird eine Komponente h:outputText erzeugt und im Komponentenbaum hinzugefügt.

<ui:repeat value="#{bean.rows}" var="row">
  <h:outputText value="#{row.text}" />
</ui:repeat >

Da ui:repeat selbst eine Komponente ist wird diese mit ihrer verschachtelten Komponente h:outputText selbst dem Komponentenbaum hinzugefügt – hier geschieht keine Vervielfältigung.

Die grundsätzliche Funktionsweise ist damit klar. Obwohl hier bei großen Datenmengen der Komponentenbaum aufgebläht wird, was zu Performanceeinbußen führen kann, funktioniert der Code noch wie erwartet. Anders kann es aussehen wenn z.B. c:if für bedingte Ausgaben in Komponenten verwendet wird:

<h:dataTable values="${zahlen}" var="zahl">
  <h:column>
    <c:if test="${zahl > 5}">
      <h:outputText value="${zahl}"/>
    </c:if>
  </h:column>
</h:datatable>

Hier könnte man erwarten dass immer wenn die Bedingung zu true evaluiert eine Zeile erzeugt wird. Tatsächlich wird die Tabelle leer sein. h:dataTable ist eine Komponente, c:if wird jedoch in der Kompilierphase ausgewertet, und zwar genau einmal. Das Ergebnis ist dann false. Die Lösung wäre hier die Verwendung des Attributes „rendered“ der Komponente h:outputText.

Sogar aus derselben Bibliothek stammende Tags können von dieser Unsicherheit betroffen sein. Beispielsweise beim Versuch eines dynamischen includes mit ui:repeat und ui:include:

<ui:repeat value="#{bean.rows}" var="row">
   <ui:include src="#{row.include}"/>
</ui:repeat>

ui:include ist hier ein TagHandler der in der Kompilierphase ausgewertet wird. Dieses ist genau einmal der Fall und scheitert an der fehlenden Variable row.include, die erst in der render phase durch die Komponente ui:repeat verfügbar wird. Die Lösung wäre hier die Nutzung von c:forEach.

Fazit

Obwohl es gerne übergangen wird, ist der Schlüssel zum erfolgreichen, frustfreien Einsatz von JSF und Facelets die Kenntnis der verschiedenen Phasen des JSF-Lifecycle und der Kompilierphase. Der Unterschied zwischen TagHandlern und ComponentHandlern sollte präsent sein, damit unvorhersehbares Verhalten und langwieriges debugging vermieden wird.

Quellen und weiterführende Links zum Thema:
http://wiki.java.net/bin/view/Projects/FaceletsFAQ#Why_doesn_t_my_c_if_ui_repeat_ui
http://www.ninthavenue.com.au/blog/c:foreach-vs-ui:repeat-in-facelets
http://www.znetdevelopment.com/blogs/2008/10/18/jstl-with-jsffacelets/

Tagged , , , , ,

Über Oliver Nolden

Dipl.Inform. Oliver Nolden ist seit April 2006 für die Software-Architekten als Consultant und seit Januar 2010 mit dem Schwerpunkt Web Content Management als Senior Consultant tätig. Seit 2011 ist er als Entwicklungsleiter verantwortlich für die internen Entwicklungsprozesse und kümmert sich gleichzeitig um die Integration neuer Technologien und bewährter Methoden der professionellen Softwareentwicklung.

Alle Beiträge von Oliver Nolden →

Verwandte Beiträge

Hinterlassen Sie einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

Be social
Um nichts zu verpassen, abonnieren Sie unseren RSS Feed.
RSS Feed abonnieren
Präsenz in sozialem Netzen