CDI: Typsichere pluggable Injection

Nachdem im vorherigen Beitrag über CDI, die „Contexts Dependency Injection“ in Java EE 6, die Basics wie Deklaration und Injection einer Bean behandelt wurden, so liegt der Fokus diesmal auf den flexiblen Mechanismen, mit denen CDI genutzt werden kann. Die Beschreibung ist dabei nicht allumfassend, sondern soll nur einen Einblick bieten, welche Vorteile die Nutzung von CDI in Komponenten-Architektur und -Implementierung bringt.

Zur Demonstration wird ein kleiner Rechenautomat entwickelt, der nach und nach um mehrere Rechenoperationen erweitert wird. Ein abstraktes simples Beispiel, welches mehrere Standard-Technologien kombiniert: Bean Validation, JavaServer Faces 2 mit Facelets, und natürlich CDI. Die Codebeispiele sind teils sinnvoll verkürzt. Die komplette lauffähige Web Application (WAR) steht am Ende des Beitrags verlinkt als Download bereit.

Oberfläche aus dem Java EE 6 Baukasten

Zum Testen der Funktionen wird JSF2 genutzt, welches ebenfalls zum leichtgewichtigen Java EE 6 Web Profile gehört und somit neben CDI immer zur Verfügung steht. Die Oberfläche bietet die Möglichkeit zwei Zahlen einzugeben. Die Zahlen können anschliessend per Knopfdruck addiert werden. Alternativ können „alle Operationen“ ausgeführt werden – was sich hierhinter versteckt, lichtet sich später. Die Seite wird standardkonform per XHTML (seit JSF2) wie folgt definiert:

<h:form>
    Value1:
    <h:inputText value="#{calculationService.input.value1}" 
        id="input1"/>
    <h:message for="input1" />

    Value2:
    <h:inputText value="#{calculationService.input.value2}"
        id="input2"/>
    <h:message for="input2" />

    <h:commandButton
        action="#{calculationService.performAddition}"
        value="addition" />
    <h:commandButton
        action="#{calculationService.performAll}"
        value="all operations" />
</h:form>
<ui:repeat value="#{calculationService.output}" var="line">
    <h:outputText value="#{line}" />
</ui:repeat>

Es werden zwei Eingabefelder h:inputText definiert, welche beide an Properties einer Bean calculationService.input gebunden werden. Über h:commandButton werden die angesprochenen Features, nämlich Addition und „alle Operationen“, angeboten. Schlussendlich werden über den Facelets-Loop ui:repeat die Ausgabezeilen aus dem calculationService ausgelesen und dargestellt. Vielleicht ist es aufgefallen: Für beide Eingabefelder gibt es jeweils ein h:message Block. Diese Blöcke stellen eventuell aufgetretene Validierungsmeldungen attributsspezifisch dar.

Validation Constraints im Entity

Die Fehlermeldungen der Validierung werden zwar automatisch generiert, erfordern jedoch Validierungsregeln. Daher ein Blick auf die Bean, in welche über die Eingabefelder geschrieben wird:

public class CalculationValue implements Serializable {
    @NotNull
    private Long value1;
    @NotNull
    private Long value2;
}

Man sieht hier Standard JSR-303 Bean Validation Annotations. In diesem Fall wird geprüft, ob in den Werten der Bean überhaupt Daten hinterlegt wurden. Die Bean Validation gehört ebenfalls zum Java EE 6 Web Profile. JSF2 prüft in der Process Validation Lifecycle Phase auf Bean Validation Constraints. Die Constraints werden dabei direkt in der Entity definiert, genau wie in diesem Beispiel. Zusätzlich wird die Bean Validation auch in nachgelagerten Schichten, zum Beispiel in einem JPA2 Persistency Layer, sichergestellt.

Addition per CDI

Die CalculationValue Entity ist als Property input in der Bean calculationService hinterlegt. Ausserdem werden bei den Button-Actions Methoden aus der Bean calculationService aufgerufen. Höchste Zeit also, einen Blick hinein zu werfen:

@Named
@SessionScoped
public class CalculationService implements Serializable {

    private List<String> output;

    @Inject
    private transient Addition addition;

    @Inject
    @LargeValue
    private CalculationValue input;

    public String performAddition() {
        output.clear();
        output.add( addition.operate(input) );
        return "index";
    }

Der CDI-geschulte Betrachter sieht natürlich sofort, dass es sich hierbei um eine Bean aus dem CDI Kontext handelt. Per @SessionScoped wird ihre Lebensdauer auf die Benutzer-Session erweitert. Dies ist nur für dieses Beispiel so gewählt und keine Voraussetzung. Der Standard @RequestScoped bzw. keine Angabe funktioniert ebenso. Die Bean kann ohne weitere Konfiguration direkt im JSF2 EL genutzt werden.

Als nächstes fällt die Injection @Inject einer Addition Bean auf. Diese Bean kapselt die Logik der Addition. Genau diese Logik wird in der performAddition() Methode aufgerufen. Dabei wird der Addition der Eingabewert input übergeben, welcher zuvor aus der JSF2 View gesetzt wurde. Die Addition wird dabei value1 und value2 aus input addieren. Klingt logisch, oder?

Eine Bean, viele Produzenten

Das Datenmodell input ist eine Instanz vom Typ der oben eingeführten CalculationValue Entity. Dafür benötigt man kein CDI, eine normale Deklaration ohne Annotations wäre völlig ausreichend. Trotzdem wird dem Member @Inject und @LargeValue vorangestellt. In diesem Beispiel ist das Ziel, eine explizite Initialisierung des Members zu erreichen. Die Werte von input werden zwar vom JSF2 Formular beim Submit gesetzt, allerdings werden bereits bei der Anzeige des Formulars die aktuellen Werte von input dargestellt. Es handelt sich hier also schlicht um das Setzen von Default Werten.

An dieser Stelle wird eine Entkopplung mittels CDI etabliert. Genauer gesagt sieht man hier einen Weg, das Factory-Pattern zu ersetzen. Durch das @Inject sucht CDI nach Möglichkeiten, die gewünschte Bean zu instanziieren. Das kann wie bereits geschehen per @Named direkt in der Bean-Klasse ermöglicht werden, eine mächtigere Variante aber ist, eine Methode zur Instanziierung zu nutzen:

@Named
public class ValueProducer {

    @Produces
    @LargeValue
    public CalculationValue initLarge() {
        final CalculationValue input = new CalculationValue();
        input.setValue1(2048L);
        input.setValue2(1024L);
        return input;
    }
    
    @Produces
    @SmallValue
    public CalculationValue initSmall() {
        final CalculationValue input = new CalculationValue();
        input.setValue1(6L);
        input.setValue2(2L);
        return input;
    }    
}

Diese Klasse (selbst CDI Bean) bietet zwei Methoden, welche beide mit @Produces annotiert sind. Durch diese Annotation weiss CDI, dass die Methoden CDI-Beans vom jeweiligen Return-Typ der Methode (hier: CalculationValue) herstellen kann. Dieses Wissen wird bei Injections genutzt, sofern keine passende Bean-Instanz bereits im Kontext vorhanden ist. Das heisst: Wenn eine @Inject Annotation gefunden wird, wird zuerst im Kontext geprüft ob bereits eine passende Bean Instanz vorliegt. Falls nein, wird die @Produces annotierte Methode aufgerufen und die dort erzeugte Bean injected. Dabei kann in der Methode natürlich auch komplexe Logik zur Generierung der Bean angewandt werden.

Nun wurden im ValueProducer jedoch zwei Methoden mit @Produces versehen. Und zu allem Unglück geben sogar beide Methoden den gleichen Typ zurück, beides mal CalculationValue! Welche Methode wird denn nun aufgerufen?

Bean-Auslese

Dieser Fall ist gar nicht so abwegig wie man denken könnte. Man stelle sich zum Beispiel vor: Zwei Datenbanken werden von einer Applikation genutzt. Nun könnte man Connection Instanzen injecten. Je nach Fall muss die Connection dann zur ersten oder zur zweiten Datenbank verbinden; trotzdem sind beide Klassen vom selben Typ.

CDI bietet hier eine elegante, weil typsichere Lösung. Im Beispiel werden die zwei produzierenden Methoden mit den Annotationen @LargeValue und @SmallValue unterschieden. Die gleichen Annotationen werden dann bei der Injection der Bean benutzt. Somit kann gesteuert werden, welche Methode zur Instanziierung genutzt wird. Trotzdem bleibt die Entkopplung erhalten: Der ValueProducer wird von keiner Klasse importiert.

Was aber ist @LargeValue eigentlich für eine Annotation? Eine eigene! Und die sieht so aus:

@Target( { ElementType.TYPE, ElementType.METHOD, 
ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface LargeValue {
}

Wie man sieht, hält sich der Umfang doch in Grenzen. Aus CDI ist hierbei nur der @Qualifier Bezeichner, welcher CDI signalisiert dass diese Annotation zur Typisierung einer Bean dient. Der Code von @SmallValue sieht, bis auf den Namen, identisch aus.

Mittels @Produces Methoden kann also eine hohe Dynamik erreicht werden. Um trotzdem gezielt die passenden Beans herauszufiltern, bieten sich eigene @Qualifier an. Dies erhöht auch die Lesbarkeit des Codes, da die eigenen Annotationen auch entsprechend der Business Domain benannt werden können, z.B. @TopOffer.

Warum nicht einfach alles?

Eine noch höhere Flexibilität kann durch die Injection von Instanzlisten erzielt werden. Dies ist die gegenteilige Richtung von der gezielten Injection mittels @Qualifier : Es werden alle Beans im CDI Kontext eines Typs injected. Standard-Case für dieses Feature ist ein Plugin Mechanismus: Es kann eine Library eingebunden werden, welche das entsprechende Interface implementiert und sich dem CDI Kontext z.B. per @Named bekannt macht. Diese Implementierung ist dann überall verfügbar, ohne Wissen über die eigentliche Klasse oder Library.

Eine Erweiterung des Beispiels bringt dies näher. Der Rechenautomat soll jetzt endlich sein „alle Operationen“ Feature erhalten. Dabei werden alle im Automat verfügbaren Rechenoperationen nacheinander angewandt. Bisher ist dies nur die Addition, daher wird der Automat nun um neue Beans für Subtraktion, Multiplikation und Division erweitert. Alle diese Beans (auch die bereits existierende Addition) implementieren folgendes Interface:

public interface CalculationOperation {
    public String operate(CalculationValue input);
}

Es gibt nun vier CalculationOperation Implementierungen, welche alle via @Named im CDI Kontext als Bean registriert sind.

Diese Implementierungen werden gesammelt im CalculationService herangezogen und in der JSF Action Methode genutzt:

    ...
    @Inject
    private transient
        Instance<CalculationOperation> operations;
    
    public String performAll() {
        output.clear();
        for (CalculationOperation operation: operations) {
            output.add(operation.operate(input));
        }
        return "index";
    }

Instance ist ein Generic aus CDI. Die Injection mittels Instance zieht alle Instanzen im CDI Kontext dieses Typs heran. Weil hier ein Interface genutzt wird, werden also alle Implementierungen dieses Interfaces injected. Eine zusätzliche Einschränkung mit @Qualifier ist übrigens auch noch möglich. Nun kann man einfach über die Liste der Instanzen iterieren und die Methoden des Interfaces nutzen. Die Klasse CalculationService hat trotzdem keinerlei Referenzen auf eine der Implementierungen des CalculationOperation Interface. Es könnte nun zum Beispiel ein JAR mit zusätzlichen Implementierungen hinzugefügt werden, diese Operationen würden dann automatisch an dieser Stelle ausgeführt.

Application und Codebeispiele Download

Alle Beispiele als ausführbare Web Application (WAR) einschliesslich Quellcode finden Sie hier als Download (ZIP, 13kB). Zur Ausführung wird ein Java EE 6 Application Server benötigt (Web Profile möglich), zum Beispiel den GlassFish Server 3.0.

Das Ende und doch nur am Anfang

CDI bietet noch vieles mehr als in den Blog-Postings gezeigt werden konnte. Interceptoren beispielsweise sind ein spannendes Thema, weil hier Features wie die Transaktionalität der EJBs recht einfach nachgebaut werden kann. Trotzdem hoffe ich dass die Art, wie mit CDI in Java EE 6 gearbeitet werden kann, etwas sichtbar gemacht wurde. CDI ist eine attraktive weil leichtgewichtige Alternative zu EJB. Die duchgängige Unterstützung im schlanken Java EE 6 Web Profile macht diese Technologie für Web Applications besonders attraktiv.

Aber auch beim grossen Bruder EJB3.1 hat sich viel Spannendes getan! Mehr dazu bald an dieser Stelle.

Bis dahin beantworte ich gerne Rückfragen rund um CDI und/oder Java EE 6.

CDI: Leichtgewichtige Injection in Java EE 6

Bei der Umsetzung von Java Enterprise Projekten steht man (besser) früher oder (schlechter) später vor der Wahl eines geeigneten Komponenten-Frameworks. In der Vergangenheit fiel die Wahl dabei häufig auf Spring, da es gegenüber EJB, dem Java Standard, lange leichtgewichtiger und moderner daherkam. Mit EJB3 hatte sich dies im Java EE 5 bereits ein wenig geändert, da nun eine Konfiguration über Annotations ermöglicht und die Anzahl der notwendigen Interfaces reduziert wurde. Trotzdem blieb EJB ein Schwergewicht, weil es nur auf den grossen, umfangreichen und komplexen Java EE Applikationsservern eingesetzt werden konnte. Dies war für viele Projekte ein schlagender Grund um auf EJB zu verzichten.

Für jeden das passende Profil

Mit Java EE 6, das seit Ende 2009 verfügbar ist, hat Sun bzw. inzwischen Oracle mächtig in Sachen Leichtigkeit aufgeholt. Die Applikationsserver müssen nun nicht mehr den kompletten Satz an Features des Standards unterstützen. Stattdessen wurden „Profiles“ eingeführt, spannend ist im Umfeld der Web Applications natürlich das „Web Profile“. Die Tabelle bei ehemals Sun gibt einen guten Überblick, welche Features dort unterstützt werden und welche entfallen. Klar, die Reduktion kostet Opfer: Gerade Integrations-Technologien wie JMS, JCA, JAX-WS oder JAX-RS fallen weg.
Dafür wird aber der gängige Stack für Web Applications vollumfänglich unterstützt, sowohl in View (Servlet, JSF2), Komponenten (EJB, CDI) als auch Persistenz (JPA, Bean Validation). Eine solche Web Application kann auch, und das ist neu in Java EE 6, als WAR Datei deployed werden. Vorbei die Zeiten der XML-überfluteten EARs. EJB3.1 ist als Lite Ausprägung im Web Profile enthalten. Lite bedeutet u.a. den Verzicht auf MessageDriven Beans und die neue asynchrone Ausführung von Methoden. Die Application Server können sich mit dem neuen Profil deutlich schlanker formen und reduzieren damit sowohl Administrationsaufwand als auch Betriebskosten.

Diese neu gewonnene Attraktivität von Java EE ist spürbar: Auf Konferenzen wie der W-JAX 2010 war das Interesse an den Standard Frameworks sehr gross. In Workshops wie dem Java EE 6 Roundtrip von Adam Bien konnte man froh sein, einen Sitzplatz zu ergattern.

Ein neues Komponentenmodell

Ein wichtiger Bestandteil von Java EE 6 Web Profile ist die „Contexts and Dependency Injection“, kurz CDI. Hierbei handelt es sich um ein weiteres Komponentenmodell, also ähnlich wie EJB. CDI verfolgt aber andere Ziele: Es enthält kein automatisches Transaktionshandling, hat keine Remote und Local Interfaces und hält hinsichtlich der mitgelieferten Features sicherlich keinen Vergleich zu EJB stand.

Dafür besitzt es aber eine nochmals erhöhte Flexibilität, die gerade bei Web Applications in vielen Schichten sinnvoll ist. Denn, soviel sei vorweg genommen, es ist durchaus ein gangbarer und oft sinnvoller Weg, CDI mit EJB zu kombinieren. Beispielsweise könnte eine JSF2 View an einen CDI View Layer gekoppelt sein. Die Business Logik im nachgelagerten Layer kann dann transaktionssicher per EJB gekapselt sein. Die danach folgende Persistenz kann wieder per CDI flexibel gehandhabt werden. Dies ist, wie gesagt, nur ein Beispiel; trotzdem zeigt es bereits einerseits wie flexibel auf die Anforderungen des Kundensystems eingegangen werden kann, andererseits aber auch dass bei der Architektur des Systems Beratung zwingend notwendig ist.

Ganz kurzer Ausritt in die Theorie: CDI wird durch den JSR-299 spezifiziert. Dieser Standard bildet einen Layer auf dem JSR-330 „Dependency Injection“. Man benutzt also eigentlich beide Standards.

Einbindung von CDI in die Web Application

Zurück zur Praxis. Um eine Library vom Container bzw. dessen CDI-Implementierung (Referenz ist JBoss Weld) für CDI zu scannen, muss eine beans.xml Datei angelegt werden. Keine Angst, die „Hell of XML“ bricht nicht aus, denn die Datei bleibt in der Regel bis auf das Root-Element leer. Sie dient nur als Notifizierung dass die Library (kann auch ein JAR sein) gescannt werden soll.

CDI besteht wie üblich aus Scopes und Beans. Die Steuerung erfolgt über Annotations. Eine Bean kann per @Named zum CDI Kontext hinzugefügt werden. Sie ist dann per Standard im Request Scope. Über gezieltes Scoping wie @SessionScoped kann eine Bean auch mehrere Requests überleben. Interessant ist dabei, dass im Gegensatz zu anderen Komponentenmodellen, z.B. JSF2 @ManagedBean, erzwungen wird dass eine passivierbare Bean, also zum Beispiel @SessionScoped, java.io.Serializable implementiert. Ist dies nicht der Fall, versagt CDI / Weld den Start. Dies beugt vielen unangenehmen Überraschungen vor, falls später einmal Sessions passiviert und wiederhergestellt werden.

@Named
public class Addition {
    // ...
}

Wenn hier kein Parameter an die Annotation hinzugefügt wird, wird sie automatisch mit dem Klassennamen benannt, hier also „addition“. Man kann aber via Parameter den Namen auch selbst festlegen.

Der umgekehrte Weg, also die Nutzung einer im CDI Kontext registrierten Bean, kann auf mehrere Wege geschehen. Innerhalb einer anderen CDI Bean kann per @Inject Annotation darauf zugegriffen werden. Die Deklaration erfolgt typsicher. Der Container wird bei Initialisierung dieser Bean entweder eine im Scope vorhandene Bean injecten oder eine neue Bean erzeugen und an diese Stelle referenzieren.

    @Inject
    private Addition addition;

Wie man sieht wurde kein textueller Name bei der Annotation genutzt. Es reicht die Typangabe, um die Bean zu lokalisieren. Dabei kann es durchaus Fälle geben, in denen mehrere Beans vom gleichen Typ im Kontext vorhanden sind. Es gibt jedoch auch hier Mittel, typsicher die richtige Variante herauszufinden. Dazu später mehr.

Die Injection funktioniert auch innerhalb von Servlets, hier ein Beispiel in Servlet API 3.0:

@WebServlet(name="CDIServlet", urlPatterns={"/cdi"})
public class CDIServlet extends HttpServlet {
    
    @Inject
    private Addition addition;
    // ...
}

Da verwundert es nicht, dass auch ein Zugriff aus der Expression Language in JSF2 möglich ist:

    <h:outputText value="#{addition.text}" />

Hier muss nun erstmals der mit @Named implizit eingeführte Name der Bean genutzt werden. EL ist leider nicht typsicher.

Und so geht es weiter

Mit den nun vorgestellten Mitteln können Beans in CDI registriert sowie wieder referenziert werden. Dies ist jedoch nur die Grundlage für erweiterte Features, welche CDI dann richtig interessant machen. Im in Kürze folgenden Teil geht es daher tiefer in den Code: Wie man Beans ohne @Named und aus Methoden heraus in CDI erstellt, wie man Mengen von Beans injected und damit einen echten Plugin Mechanismus baut, sowie wie man mehrere Beans der gleichen Klasse im Kontext trotzdem typsicher unterscheiden kann.

Bis dahin beantworte ich gerne Rückfragen rund um CDI und/oder Java EE 6.

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.