FacesTales

Meins oder deins? JSF 2.0 Scopes unter der Lupe

Lars Röwekamp, Matthias Weßendorf

Scopes zur sauberen Abgrenzung von Managed Bean Lifecycles gibt es seit der ersten JSF-Spezifikation. Für viele Entwickler sind die damit einhergehenden Möglichkeiten allerdings bei Weitem nicht ausreichend. Mit JSF 2.0 kommen nun neue Scopes ins Spiel. Gleichzeitig bietet der JSR 299 (Context and Dependency Injection for the Java EE Platform) interessante Scope Alternativen.

Basis Scopes

Bereits die erste Version der JSF-Spezifikation (2004) nutzte den aus der Servlet-Welt bekannten Mechanismus der Scopes zur Abgrenzung der Lebensräume von Managed Beans. JSF 1.0 stellte zu diesem Zweck vier Alternativen bereit: none, request, session und application. Der Request Scope hält den State genau für einen HTTP Request, der Session Scope für die gesamte Dauer einer Anwendersession und der Application Scope für alle Anwender einer Application. Erklärungsbedürftig scheint der None Scope zu sein, da das kein Scope im eigentlichen Sinne ist. Eine Bean mit dem Scope none wird immer dann neu erzeugt, wenn sie referenziert und anschließend nicht in einem eigenen Scope abgelegt wird. Vielmehr werden Beans mit dem Scope none bei ihrer Initialisierung automatisch an den Scope der sie aufrufenden Bean gebunden. Wir eine None Scope Bean innerhalb einer JSF-Seite direkt – via Unified EL – referenziert, wird die Instanz direkt nach dem Aufruf wieder verworfen.

Auch die anderen Scopes haben zum Teil ihre Besonderheiten. So überlebt eine Bean mit dem Scope request zwar ein Forward, nicht aber einen Redirect. Ein Nebeneffekt, der insbesondere bei JSF Newbies immer wieder zu Verwunderung führt. Hintergrund ist, dass bei einem Forward ein und derselbe HTTP Request verwendet wird, während es bei einem Forward zur Erzeugung eines neuen HTTP Requests kommt.

Möchte man also den State einer Anwendung über einen Request hinaus halten, ist man bisher auf den Session Scope angewiesen gewesen. Das hat den kleinen aber schwerwiegenden Nachteil, dass es innerhalb einer JSF-Anwendung bei mangelnder Disziplin des Programmierers schnell zu enorm aufgeblähten Sessions kommen kann. Generell gilt übrigens, dass eine Bean andere Beans nur dann referenzieren kann, wenn deren Scope none oder gleich bzw. größer dem eigenen Scope ist. Das scheint absolut sinnvoll zu sein. Ein ShoppingCart innerhalb des Session Scopes kann beispielsweise auf applikationsweite Daten, nicht aber auf Beans der verschiedenen Requests zugreifen. Woher soll der ShoppingCart auch wissen, welcher spezielle Request und somit welche Instanz der Bean gemeint sein soll?

JSF 2.0 Scopes

Die vier in JSF 1.0 eingeführten Scopes stellen eine gute Basis dar. JSF 2.0 erleichtert deren Verwendung, indem für jeden der vier Scopes eine eigene Annotation angeboten wird, sodass auf eine umständliche XML-Konfiguration der Managed Beans verzichtet werden kann: @NoneScoped, @RequestScoped, @SessionScoped, @ApplicationScoped. Das Communityfeedback der letzten Jahre hat aber gezeigt, dass die vier Scopes alleine für die Praxis nicht ausreichend sind. Typische Problemfelder sind das bereits oben angesprochene Redirect eines Requests, das mehrmalige Anzeigen derselben Seite, sowie die Verwendung einer mehrseitigen Conversation, wie man sie klassischerweise in einem Wizard findet. Bisher konnten all diese Szenarien nur mithilfe des Session Scopes als Workaround, einer 3rd Party Library oder aber der Speicherung der notwendigen Daten im Backend realisiert werden.

Für zwei der drei angesprochenen Problemfelder bietet JSF 2.0 eine direkte Lösung. Mit dem Scope flash wurde in JSF 2.0 die Möglichkeit geschaffen, den State einer Bean von einem Request an den nächsten zu übergeben. Das ist genau das Verhalten, das für ein Redirect eines HTTP Requests benötigt wird. Und auch für das Verweilen auf einer JSF View wurde in JSF 2.0 ein eigener Scope namens view eingeführt (@ViewScoped). Dieser ist solange gültig, bis die aktuelle View verlassen wird. Der Vorteil der beiden neuen Scopes ist, dass nun für die beschriebenen Szenarien auf den sehr unschönen Workaround des Session Scopes verzichtet werden kann. Was aber ist mit dem dritten Szenario, dem JSF-basierten Wizard? Auch hierfür gibt es mittlerweile eine Lösung, wenn auch nicht in der JSF-2.0-Spezifikation.

Java EE Scopes

Bereits in der letzten Ausgabe haben wir auf die Vorzüge der beiden JSRs 229 und 330 hingewiesen. Gleichzeitig haben wir am Beispiel der Annotationen @ManagedBean (JSF 2.0) und @Named(JSR 330) gezeigt, dass es einige Features gibt, die sowohl im JSF- als auch im JSR-299/330-Umfeld redundant vorhanden sind, und empfohlen, sich in der Welt von Java EE 6 auf die beiden JSRs 299 und 330 zu fokussieren. Ein ähnliches Bild ergibt sich auch für die Scopes. Auch hier gibt es starke Überschneidungen, da auch der JSR 299 die drei Scopes @RequestScoped, @SessionScoped, @ApplicationScoped zur Verfügung stellt. Interessant ist, dass es neben diesen drei genannten Scopes noch einen weiteren gibt, der bisher noch keinen Einzug in die JSF Spezifikation gefunden hat und daher bei Bedarf mithilfe von Zusatzbibliotheken bedient werden muss: @ConversationScope.

Anders als man das vielleicht vermuten würde, ist der Standard-Lifecycle einer Conversation gleichzusetzen mit dem Lifecycle eines Requests. Soll eine Conversation über mehrere Requests aufgespannt werden, muss sie als long-running gekennzeichnet und deren Start und Ende explizit programmiert werden. Zu diesem Zweck liefert CDI eine Build-in Bean vom Typ Conversation. Listing 1 zeigt ein entsprechendes Beispiel aus der CDI-Referenzimplementierung Weld [1].

@ConversationScoped
@Stateful
public class OrderBuilder {

private Order order;
private @Inject Conversation conv;
private @PersistenceContext(type = EXTENDED) EntityManager em;

...
public Order createOrder() {
order = new Order();
conv.begin();
return order;
}

public void saveOrder(Order order) {
em.persist(order);
conv.end();
}

@Remove
public void destroy() {}
}

Wie im Listing zu sehen, wird die Conversation in der Methode createOrder() mittels conv.begin() gestartet und in der Methode saveOrder() durch den Aufruf von conv.end() wieder beendet. Auch wenn der CDI Conversation Scope eine deutliche Verbesserung im Vergleich zu den JSF 2.0 Scopes darstellt, ist bei Weitem noch nicht die Funktionalität und der Komfort erreicht, wie man es von anderen 3rd Party Frameworks gewohnt ist. Es kommt zum Beispiel regelmäßig zu Problemen bei der parallelen Nutzung von Ajax Requests. Dass dies auch besser gelöst werden kann, zeigen alternative Lösungen wie Apache MyFaces Orchestra [2] oder Apache MyFaces Trinidad [3].

Missing Pieces

Verzichtet man zu Gunsten der allgemein gültigen CDI Scopes auf die spezifischen JSF Scopes, dann ergibt sich ein kleines aber nicht zu vernachlässigendes Problem. CDI bietet keinen View Scope an. CDI hat die Aufgabe, schichtenübergreifend technologieneutrale Features zur Verfügung zu stellen. Der JSF View Scope aber ist ausschließlich für die Präsentationsschicht konzipiert. Möchte man trotzdem CDI statt JSF Scopes nutzen und gleichzeitig nicht auf den View Scope verzichten, lässt sich dank des CDI Extensible Context Model ein eigener View Scope relativ einfach nachbauen. Ein Beispiel, wie das im Detail aussehen kann, findet sich im Blog von Steven Verborght [4]. Sowohl Apache OpenWebBeans [5] als auch Weld bieten eine @ViewScoped-Unterstützung an.

Ein ebenfalls interessanter Kandidat für die Zukunft stellt das noch junge Apache-MyFaces-Extensions-CDI-Projekt dar, das einen Teil der heute in Apache MyFaces Orchestra verfügbaren Features – und natürlich einige mehr – auf Basis von CDI anbieten wird.

Fazit

Die neuen JSF 2.0 Scopes sind ein erster wichtiger Schritt in die richtige Richtung. Schmerzlich vermisst wird nach wir vor ein @ConversationScoped zur Realisierung von typischer Wizard-Funktionalität. Dieser wiederum ist, genau wie die Klassiker @RequestScoped, @SessionScoped und @ApplicationScoped, Bestandteil der CDI-Spezifikation. Somit steht einer Verwendung von CDI nur das Fehlen des JSF-spezifischen @ViewScoped entgegen, der sich dank Extensible Context Model aber in CDI leicht nachimplementieren lässt.

Wie schon in der letzten Kolumne für die Managed Beans gilt also auch für die Scopes, dass einem Wechsel von der auf die Präsentationsschicht zugeschnittenen JSF-Variante hin zur allgemeinen CDI-Variante nichts im Weg steht. In der Praxis muss allerdings die Frage erlaubt sein, ob das gesamte Thema der Scopes nicht besser in einem generischen Layer behandelt werden sollte. Anstatt mehrere APIs mit demselben Ziel zu spezifizieren, scheint es durchaus sinnvoll zu sein, das Thema Scopes an einer zentralen Stelle zu platzieren, von der dann alle Webframeworks partizipieren können. Als möglicher Kandidat bietet sich die Servelt-Spezifikation an [6].

Geschrieben von
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Matthias Weßendorf
Matthias Weßendorf
Matthias Weßendorf arbeitet bei Red Hat, wo er sich mit WebSocket, HTML5 und weiteren Themen rund um das Next Generation Web beschäftigt. Blog: http://matthiaswessendorf.wordpress.com
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.