Neu in JSF2, Teil 1: Facelets und GET Requests

Mittlerweile ist Java EE 6 bereits mehr als sechs Monate alt. Höchste Zeit also für Java Entwickler und Architekten, sich mit den Neuerungen zu befassen. Als Dienstleister rund ums Web stehen für Namics natürlich die neuen Features rund um Web-Applikationen im Fokus. Eines der wichtigsten Unterprojekte von Java EE in diesem Umfeld sind die JavaServer Faces (JSF). Dieses Framework für den View-Layer der Applikation hat inzwischen einige Jahre auf dem Buckel, wurde jedoch von ehemals Sun, nun Oracle, stetig weiterentwickelt und hat viele Verbesserungen der Open Source Gemeinde in den neuen Standard 2.0 aufgenommen. JSF gehört zu den Bestandteilen von Java EE, bei denen die weitreichendsten Neuerungen vorgenommen wurden. Wir bei Namics haben bereits ein grösseres Projekt unter Verwendung von JSF2 durchgeführt und uns daher etwas genauer damit beschäftigt.

Im Rahmen einer kleinen Reihe im Blog werde ich ein paar interessante Neuerungen des Frameworks vorstellen. Bestandteil jedes Artikels ist immer auch ein lauffähiges Beispiel-Projekt, welches die besprochenen Features sowohl zur Laufzeit als auch im enthaltenen Quellcode illustriert.

Setup

Applikationen mit JSF2 sind immer noch normale Java-Web-Applikationen ohne spezielle Abhängigkeiten bezüglich Java EE 6. Damit sind sie auch in jedem halbwegs aktuellen Servlet-Container lauffähig und erfordern keinen speziellen Applikationsserver. Falls im Web-Container vorhanden, kann man jedoch die Vorzüge von Servlet 3.0, einer weiteren Java EE 6 Neuerung, nutzen. Durch dessen annotation-based Konfiguration von Servlets und Filtern erfordert JSF2 keine Konfiguration mehr. Es reicht, die Libraries in die Applikation aufzunehmen, den Rest erledigt der Container automatisch. Das FacesServlet, welches den JSF-Kontext bei Zugriffen initialisiert, wird dann auf „*.jsf“ gemappt. Gerade zum Aufsetzen des Projekts ist das sehr praktisch und erlaubt es dem Entwickler, sofort loszulegen.

Durch Servlet 3.0 kann man sogar auf den Deskriptor der Applikation, die web.xml Datei, komplett verzichten. Dasselbe gilt für die JSF-eigene Konfigurationsdatei „faces-config.xml“. Sie ist optional und dank der neuen annotation-based Konfiguration von JSF selbst wird sie auch nur noch in speziellen Fällen benötigt. Hierzu später mehr.

Diese Erleichterungen sind optional, man kann JSF weiterhin auch in der web.xml konfigurieren um auch noch für Servlet 2.x lauffähig zu bleiben, und eine erstellte faces-config.xml Datei wird ebenfalls genutzt sofern sie vorhanden ist.

Um die Vorteile demonstrieren zu können, nutzt die angehängte Beispielapplikation Servlet 3.0, z.B. im neuen Apache Tomcat 7.0 .

View

Mach’s gut, JSP – Hallo Facelets!

Die fundamentale Änderung in JSF2 im Vergleich zur Vergangenheit ist sicherlich der Wechsel der View-Technologie. Früher waren JavaServer Pages (JSP) hierfür der Standard. Nach anfänglich starker Kritik wurde dessen Integration zwar verbessert, erreichte aber nie ein Niveau mit dem alle Beteiligten zufrieden sein konnten.

Bereits vor Release von JSF2 hatte sich Facelets als quasi-Standard etabliert. Facelets ersetzt JSP durch ein XML-Format. Es bietet deutlich flexiblere Templating-Features, eine eigene Taglibrary sowie die Möglichkeit, das Gros der Standard Tag Library (JSTL) weiterhin zu nutzen. Ein weiterer Vorteil ist dass, wenn entsprechend eingesetzt (jsfc Attribut), der Code auch zur Entwicklungszeit gültiges XHTML darstellt. Dadurch kann die Seite sowohl vom Java- als auch vom Frontend-Entwickler direkt bearbeitet werden.

Mit JSF2 ist nun Facelets die Standard View Description Language (VDL). Bis auf ein paar Namensanpassungen wurden die Facelets 1.x dafür weitgehend übernommen, die Unterstützung von JSP wurde dagegen abgekündigt. Die VDL von JSF2 ist also genau gesehen nicht neu, sondern nur übernommen worden. Daher beschäftige ich mich in diesem Artikel auch nicht näher mit den Standard-Features von Facelets.

Warum GET das bessere POST ist

Denn es gibt auch abseits des VDL-Wechsels spannende Neuerungen. Eine davon ist der stark verbesserte Support von HTTP GET Requests. Früher lief eine Anfrage des Benutzers in JSF oft nach dem POST – Redirect – GET Pattern ab: Zuerst eine Action/Navigation per POST auslösen, dann den Redirect zur Ziel-View an den Benutzer senden, der dann schliesslich mit einem neuerlichen GET die neue View lädt. Anders waren Features wie Bookmarks oder Refresh-Support für den Browser des Benutzers kaum lösbar. Dies wird nun durch einige neue Features rum um die Parameter-Verarbeitung bei GET Requests in vielen Fällen möglich.

f:viewParam

Ein GET Parameter kann vor Rendering einer View in eine Bean übernommen werden. Dies funktioniert im neuen f:metadata Block einer View.

<f:metadata>
<f:viewParam name="base" value="#{calculator.base}" />
</f:metadata>

Ein eventuell vorhandener Parameter „base“ wird somit in die Bean „calculator“ übernommen. Es handelt sich hierbei um eine normale Component, was den Vorteil hat dass alle Konverter und Validatoren ebenfalls auf View-Parameter angewandt werden können. Gerade bei GET Requests ist dies essentiell, denn durch simple Änderung der URL kann der Benutzer hier jegliche Werte eingeben. Zusätzlich unterstützt die Component auch das übliche required Flag, um eine Angabe zu erfordern. Kombiniert kann dies dann wie folgt aussehen.

<f:metadata>
<f:viewParam name="base" value="#{calculator.base}"
required="true">
<f:validateDoubleRange minimum="1" />
</f:viewParam>
</f:metadata>

Schlägt eine Validierung fehl, wird der Wert nicht in die Bean übernommen und es tritt ein normaler JSF-Validierungsfehler auf, d.h. es wird eine FacesMessage zur möglichen Ausgabe erzeugt.

PreRenderViewEvent

Per View-Parameter kann also ein Wert vor Rendering der View in die Beans übernommen werden. Meistens will man dies, um die View entsprechend dem Parameter unterschiedlich zu gestalten. Zum Beispiel um einen Datensatz zu laden. Dafür wird eine zusätzliche Methode benötigt, die nach dem Abfüllen der Beans aufgerufen wird, aber noch vor dem Rendering der Seite. Genau zu diesem Zeitpunkt wird der PreRenderViewEvent geworfen. Im f:metadata Block kann man zu diesem Zweck einen Event-Listener registrieren. Der Listener besteht aus der Methode einer Bean; sie muss public sein, sollte keinen Rückgabewert liefern und entweder keinen Parameter, oder ein ComponentSystemEvent akzeptieren. Der Listener wird dann nach der Übernahme der Parameter aufgerufen, und zwar unabhängig von den Validierungen. Es bietet sich daher an, auf fehlgeschlagene Validierungen zu prüfen, bevor man die Parameter nutzt. Mit der neuen Methode isValidationFailed() kann seit JSF2 im FacesContext einfach geprüft werden, ob eine der Validierungen fehlgeschlagen ist.

<f:metadata>
...
<f:event type="preRenderView"
listener="#{calculator.calculate}" />
</f:metadata>
public void calculate() {
FacesContext ctx = FacesContext.getCurrentInstance();
if (!ctx.isValidationFailed()) {
...
}
}

Neben der Initialisierung der Daten für eine View hat die Listener Methode noch weitere Möglichkeiten. So kann sie über den NavigationHandler sogar das Rendering auf eine andere View umleiten, beispielsweise bei fehlender Authorisierung des Benutzers.

h:link

Da man nun sehr einfach GET Parameter abfüllen und verwenden kann, besteht natürlich auch erhöhter Bedarf danach, GET Links zu erstellen. Zur Navigation wurde in JSF bisher hauptsächlich auf h:commandLink und h:commandButton gesetzt. Diese erzeugten POST Requests. Zwar konnte mit h:outputLink auch ein GET Link generiert werden; diese Component wendet jedoch kaum Logik an, sodass man die Link Generierung sowie das Anfügen eventueller Parameter manuell vornehmen musste.

Hier setzt die neue h:link Component an. Sie erfordert eine logische View-ID, die dann über die Navigation Rules zur URI aufgelöst wird. Im einfachsten Fall entspricht die View-ID dem Pfadnamen der Seite in der Web-Applikation ohne Dateiendung. Übrigens ist auch dies neu: Bisher musste man selbst für dieses simple Mapping Datei->View-ID eine Navigation Rule im XML erstellen. Eine erneute Vereinfachung, um sehr schnell mit der Entwicklung loszulegen.
Ausserdem kann man der h:link Component noch Parameter über den Standard-Tag f:param mitgeben, diese Parameter werden automatisch als Query-Parameter an die URL angehängt.

<h:link outcome="calculate" value="Next page">
<f:param name="base" value="8"/>
</h:link>

Der daraus generierte Link kann dann z.B. so aussehen: /calculate.jsf?base=8 . Es sind beliebig viele Parameter kombinierbar. Bei komplexen Seiten kann es hilfreich sein, alle View-Parameter mitzunehmen, also an den generierten Link weiterzugeben. Das geht per includeViewParams Attribut. Zusätzlich angegebene f:param Tags können die Liste erweitern oder einzelne Parameter aus den includeViewParams überschreiben.

<h:link outcome="calculate" includeViewParams="true"
value="Next page"
<f:param name="base" value="8"/>
</h:link>

Der resultierende Link wird alle View-Parameter der aktuellen Seite umfassen, sowie zusätzlich den Parameter „base“ mit dem Wert 8, egal ob bereits ein View-Parameter mit dem Namen „base“ existierte oder nicht.

Damit bringt JSF2 nun endlich die Möglichkeit, umfassend mit GET-Requests zu arbeiten und somit dem Benutzer ein einfacheres, mehr RESTful Verhalten in der Applikation zu bieten. Es gibt Zusätze zu JSF, die dies noch ausbauen und ein echtes REST-Verhalten erzeugen können (z.B. PrettyFaces), aber jetzt sind auch die mitgelieferten Werkzeuge dafür tauglich und in der Komplexität akzeptabel.

Download

Lauffähige Beispiel-Applikation inkl. Quellcode

Ausblick

Dieser kurze Einstieg in JSF2 war nur der Anfang. An den Managed Beans wurden Änderungen vorgenommen sowie neue Scopes eingeführt. Es gibt nun mehrere Möglichkeiten, eigene Components zu erstellen. Die Validierung wurde entscheidend erweitert. Im kommenden Teil 2 der Reihe rund um JSF2 wird es um eine ganz zentrale Neuerung von JSF2 gehen: Die Einführung von out-of-the-box Unterstützung für Ajax.

Fragen und Diskussionen rund um JSF2 und Java EE 6? Gerne als Kommentar im Blog oder per E-Mail.