31. Juli 2013 | 4 min lesezeit

Fluent API meets JSF

Ein Builder im Fluent-API-Style ist ein eleganter Weg um Objekte zu erzeugen. Das Builder-Pattern an sich war Thema meines letzten Posts. Leider stößt das Pattern aktuell im Zusammenspiel mit JSF an seine Grenzen.

Um dessen Nutzung in der Expresison Language (EL) von JSF zu ermöglichen, müsste das Domain Model Properties im JavaBean-Style beinhalten.

Mittels EL auf Objekte zugreifen

Das ist natürlich nicht ganz die Wahrheit. JSF bietet so einige Möglichkeiten um mittels EL auf Properties zuzugreifen: neben den JavaBean Properties, ist der Zugriff auf den Inhalt von Arrays, Listen und Maps möglich – durchaus nützlich ist hierbei, dass wir das Prinzip durch den ELResolver einfach an unsere Bedürfnisse anpassen können. Die wesentliche Aufgabe des ELResolvers ist die Evaluierung von Expressions, wie z.B. #{address.city} (was übrigens das Gleiche ist wie #{address[`city`]}). Um jetzt zu erreichen, dass solch eine Expression auf den Fluent-Style Builder zugreifen kann (z.B. durch eine Aufruf von AddressBuilder.withCity(String)), müssen ein eigener ELResolver geschrieben werden.

Wenn JSF nun auf die Expression #{address.street} trifft, werden zunächst alle ELResolver gefragt, ob diese ein Objekt mit dem Namen „address“ kennen. Das passiert indem die Methode getValue(ELContext context, Object base, Object property) auf jedem ELResolver aufgerufen wird – der base Parameter hat dabei den Wert null und der Parameter property ist ein String mit dem Wert „address“. Um die Auflösung von „address“ müssen wir uns allerdings nicht selber kümmern, da CDI die entsprechende Bean zurückgeben wird. Nun kommen wir zu dem Teil „street“ aus der Expression. JSF ruft hier ebenfalls auf jedem ELResolver die Methode getValue(ELContext context, Object base, Object property) auf. Dieses Mal wird der Parameter base allerdings unser AddressBuilder sein und property hat den Wert „street“. Was müssen wir nun in unserem ELResolver machen? Für den ersten Versuch gehen wir mal davon aus, dass der AddressBuilder für jede Property einen Getter besitzt. In diesem Fall könnten wir einfach von einem bestehenden ELResolver ableiten – nämlich von javax.el.BeanELResolver und JSF würde erkennen, dass unser ELResolver für diese Expression zuständig ist. Der BeanELResolver ist dafür zuständig Properties im JavaBean-Style zu evaluieren. Zu einem späteren Zeitpunkt werde ich noch darauf eingehen, was gemacht werden muss, wenn der Builder keine Getter enthalten soll. Für jetzt soll der einzige Unterschied zwischen unserem FluentELResolver und dem javax.el.BeanELResolver in den Schreib-Operationen liegen. Dazu überschreiben wir die Methoden boolean isReadOnly(ELContext context, Object base, Object property) und void setValue(ELContext context, Object base, Object property, Object value). In diesen Methoden prüfen wir, ob das base Objekt eine Methode hat, welche eine Fluent-API Methode für die Property sein könnte:

Aus Performance-Gründen sollte das Ergebnis der getFluentMethod-Methode natürlich gecached werden. Die Implementierung bleibt in diesem Beispiel aber dem Leser überlassen – abgesehen davon, dass der Code noch nicht komplett ist. Wie vorher erwähnt, läuft JSF über alle ELResolver um herauszufinden, ob diese für die entsprechende Property zuständig sind. Wie kann JSF aber wissen, dass unser Resolver für die Property zuständig ist? Hierzu müssen wir den ELContext darüber in Kenntnis setzen. Dies geschieht durch den Aufruf von context.setPropertyResolved(true). Wir fügen also diesen Aufruf und ein paar Null-Checks zu unserem Code hinzu:

Registrierung des ELResolvers

Den FluentELResolver zu registieren ist ziemlich einfach. Hierzu muss Folgendes in der faces-config.xml stehen:

Builder ohne Getter

Bisher sind wir davon ausgegangen, dass unser Builder Getter-Methode hat um lesend auf die Felder zuzugreifen, die mittels Fluent API gesetzt werden sollen. Was machen wir nun, wenn der Builder keine Getter hat? Dazu brauchen wir nun nicht mehr von dem BeanELResolver ableiten, sondern können direkt von dem ELResolver ableiten und implementieren die übrig gebliebenen Methoden selbst (allen voran getValue). Für die Implementierung von getValue würde ich Vorschlagen, dass die verfügbaren Felder der Klasse mittels Reflection ermittelt werden. Das sollte in den meisten Fällen funktionieren. Wenn es sich bei dem Builder um einen Inner Class Builder handelt, siehe meine letzten Post, dann sollte zusätzlich die deklarierende Klasse des Builders durchsucht werden. Ein lauffähiges Beispiel gibt es hier. Technisch gesehen würde es möglich sein einen ELResolver zu implementieren, der ausschließlich auf Reflection basiert (auch für die Methode setValue). Wir könnten z.B. einen FieldAccessELResolver implementieren. Bitte macht das nicht! Dadurch würde jegliche Validierungen verloren gehen, die der Builder durchführt, da dessen Methoden nicht mehr benutzt werden würden. Im Normalfall führen Getter keine Validitäts-Prüfungen durch, weshalb es durchaus OK ist, hier Field-Access zu verwenden. Im Übrigen läuft der Field-Access Ansatz, sowie die oben vorgeschlagene Lösung in Probleme, wenn wir eine Contextual Reference von CDI an der Hand haben, da es sich dabei um einen Proxy handelt und dessen Felder somit nicht gesetzt sind.


Keine Kommentare

Kontakt

OPEN KNOWLEDGE GmbH

Standort Oldenburg:
Poststraße 1, 26122 Oldenburg

Standort Essen:
II. Hagen 7, 45127 Essen