Logo Wissenstransfer Gerhard at CichnaDotCom

>> Wissensdatenbank / Programmieren von Webanwendungen

Verknüpfung von View und Model

Managed Beans

Web-Anwendungen unterscheiden sich von anderen Anwendungen hauptsächlich darin, dass sie mit einem Browser verwendet werden können und die Geschäftslogik zentral auf einem Server ausgeführt wird. Durch die klare Trennung der Darstellung vom Datenmodell bleibt das Modell einer Anwendung unabhängig von der Wahl der Darstellung gleich. Schließlich ist einem Artikel-Objekt "egal", ob dessen Attribute auf einer Webseite oder auf der Konsole ausgegeben werden. Daher ist auch davon auszugehen, dass das Modell in vielen Projekten zur Entwicklung von Web-Anwendungen bereits in Form von Java-Klassen vorliegt. Um die Wiederverwendung dieser Klassen zu gewährleisten führt JSF das Konzept der Managed Beans ein.

Bei Beans handet es sich um einfache Klassen des Datenmodells. Sie können mithilfe der Unified Expression Language (UEL) mit der Darstellungsschicht verknüpft werden. Doch auf ein Java-Objekt kann man nur zugreifen, wenn es zuvor instanziiert und initialisiert wurde. In einer Java-Anwendung können solche Aufgaben zum Beispiel in der main()-Methode erledigt werden. Aber eine mit JSF entwickelte Web-Anwendung besitzt keine vergleichbare Methode. Es muss also einen anderen Weg geben, die mit den JSF-Komponenten verknüpften Objekte zu instanziieren. Die vom Framework angebotene Lösung ist für Web-Entwickler denkbar günstig: JSF übernimmt die Verantwortung für das Erzeugen der Beans. Deshalb werden sie im JSF-Framework Managed Beans genannt.

Die Kennzeichnung einer bereits existierenden Klasse des Datenmodells als Managed Bean ist einfach, denn es muss nur die Annotation @ManagedBean hinzugefügt werden. Bei der zweiten Annotation @RequestScoped, welche von der Entwicklungsumgebung automatisch hinzugefügt wird, handelt es sich um die Gültigkeitsbereiche einer Managed Bean. Sie geben an, wie lange ein Objekt der Bean existiert bzw. über welchen Zeitraum JSF es bereitstellt. Zum Beispiel könnte ein Objekt nur für die Dauer einer HTTP-Anfrage gültig sein (@RequestScoped). Das heißt, es wird zu Beginn einer Anfrage erzeugt und nach der Erzeugung der HTTP-Antwort wieder von JSF gelöscht. Es ist offensichtlich, dass ein solcher Gültigkeitsbereich nicht immer sinnvoll ist. Beispielsweise darf der Warenkorb eines Online-Shops nicht nach jedem Mausklick neu initialisiert werden. Vielmehr müsste er über eine gesamte Browser-Sitzung gültig sein (@SessionScoped). Die folgende Tabelle bietet einen Überblick über alle möglichen Gültigkeitsbereiche sowie deren Zusammenhänge.

Gültigkeitsbereiche von Managed Beans
Annotation Beschreibung Anwendungsbeispiele
@NoneScoped Bean wird nach der Erzeugung wieder gelöscht,
sofern keine andere Bean sie nutzt.
Nützlich, wenn JSF für jeden UEL-Ausdruck eine
neue Instanz der Bean erzeugen soll.
@RequestScoped Bean wird nach der Beantwortung der HTTP-Anfrage
wieder gelöscht.
Daten (z.B. Listen der Artikel) sollen bei jedem Klick
neu aus der Datenbak geladen werden.
@ViewScoped Bean wird erst gelöscht, wenn der Benutzer zu
einer anderen Seite wechselt.
Daten sollen nicht bei jeder Anfrage neu geladen
werden.
@SessionScoped Bean wird erst gelöscht, wenn die Sitzung
erlischt (z.B. durch Timeout)
Der Warenkorb eines Besuchers des Online-Shops.
@ApplicationScoped Bean wird mit Start des Servers erzeugt und nie
gelöscht.
Ein Besucherzähler für die Web-Anwendung.

Abbildung: Gültigkeitsbereiche von Managed Beans

Man kann also eine vorhandene Java-Klasse mit nur zwei zusätzlichen Annotationen zu einer Managed Bean umwandeln und direkt in einer JSF-Komponente verwenden. Aber woher weiß JSF, dass mit artikel ein Objekt der Managed Bean Artikel gemeint ist? Schließlich ist nirgendwo eine Anweisung wie
Artikel artikel = new Artikel();
zu finden. Und wie kann JSF trotz der Sichtbarkeit private auf das Attribut artikel.name zugreifen?

Damit diese einfache Verwendung von Managed Beans funktioniert, müssen gewisse Konventionen eingehalten werden (man spricht in solchen Fällen auch vom Paradigma Convention over Configuration oder auch Coding by Convention). Sofern man sich an diese Regeln hält, muss man also keine Angaben über den Namen des Objekts (Objektbezeichner) oder die exakte Signatur der get- und set-Methoden machen, um auf das private Attribut zugreifen zu können.
In diesem Fall lauten die Regeln, dass

  1. Objektbezeichner immer den gleichen Namen wie die Klasse haben mit dem Unterschied, dass der Anfangsbuchstabe kleingeschrieben ist
  2. die Signaturen der get- und set-Methoden nach dem Muster "getAttributname" bzw. "setAttributname" vorliegen
Abbildung: Einhaltung der Regeln für UEL-Ausdücke macht Konfiguration überflüssig

Wenn man sich nicht an die Konventionen halten kann oder will, können Objektbezeichner und Methodensignaturen auch mittels attributierter Annotationen selbst definiert werden. Hierzu fügt man den bereits bekannten Annotationen Attribute hinzu. Wenn man zum Beispiel das Artikel-Objekt in den JSF-Komponenten nicht artikel, sondern einArtikel nennen möchte, kann man das mit dem Attribut name der Managed Bean-Annotation festlegen:

@ManagedBean(name = "einArtikel")
@RequestScoped
public class Artikel { ... }

Bislang wurden nur JSF-Komponenten zur Anzeige von Informationen verwendet. Um aber die Auswirkungen der unterschiedlichen Gültigkeitsbereiche an dem aktuellen Entwurf der Artikelverwaltung testen zu können, wird noch eine Möglichkeit zur Manipulation der Objekte benötigt. Als Beispiel wird eine Schaltfläche zum Löschen auf der Artikelübersicht gewählt.

Neben einer gewöhnlichen, meist optisch nicht sehr ansprechenden Schaltfläche (h:commandButton), kann hierfür auch ein Bild verwendet werden, das von einem Hyperlink umschlossen ist. Diese Lösung wird mit der JSF-Komponente h:commandLink realisiert. Der Vorteil dieser Komponente ist, dass man mit ihrem action-Attribut eine Java-Methode aufrufen kann. Sie wird aufgerufen, sobald der Besucher der Webseite auf den Link klickt. Weil die Aufgabe der Schaltfläche darin besteht, den Artikel zu der entsprechenden Zeile zu löschen, bietet sich an, unmittelbar die remove-Methode der Artikelliste in das action-Attribut einzutragen. Als Parameter der Methode wird das Stellvertreter-Objekt (artikel) der Tabelle verwendet:

<h:commandLink action="#{shop.sortiment.remove(artikel)}"/>

Hierbei handelt es sich nicht um einen ganz normalen Hyperlink, der eine einfache HTTP-Anfrage mit einer vorab bekannten URL auslöst. Solche komplexeren HTTP-Anfragen können nur im Zusammenhang mit einem Formular gesendet werden. Bezogen auf JSF-Webanwendungen bedeutet das, dass die h:commandLink-Komponente von einer h:form-Komponente umschlossen werden muss. Zusammen mit dem eingebetteten Bild für die Schaltfläche sieht die neue Spalte nun so aus:
Abbildung: Einsatz der JSF-Komponente h:commandLink zur Manipulation einer Bean

Zum Testen startet man das Projekt über die Entwicklungsumgebung, wartet bis die Seite im Browser geladen wurde und klickt auf eine der Schaltflächen. Die Seite wird neu geladen und der entsprechende Artikel ist aus der Liste verschwunden. Dabei tritt die Warnmeldung auf, dass es zu der Aktion noch keine Navigationsregel gibt.

Ein paar kurze Experimente sollen zeigen, wie sich die unterschiedlichen Gültigkeitsbereiche auf das Verhalten der Web-Anwendung auswirken. Als Gegenstand der Untersuchung wird die Shop-Klasse ausgewählt, die von der Entwicklungsumgebung standardmäßig mit dem Gültigkeitsbereich @RequestScoped erstellt wurde. Man würde daher erwarten, dass die Veränderungen an dem Sortiment nach jeder neuen Anfrage wieder auf den Ursprungszustand zurückgesetzt werden. Um das zu testen, muss man allerdings wissen, dass es mit modernen Browsern manchmal nicht ganz einfach ist, eine wirklich neue Anfrage zu einer Internetseite abzusetzen. Das ist immer dann der Fall, wenn die Seite zuvor durch eine Aktion in einem Formular (hier: das Löschen eines Artikels aus der Liste) anhand von Parametern dynamisch erzeugt wurde. Die Browser erkennen diesen Zusammenhang meist beim Aktualisieren der Seite (hervorgerufen z.B. durch die Taste "F5") und fragen den Benutzer, ob er die Aktion wiederholen möchte. Das löst aber nicht eine neue Anfrage aus, sondern führt dazu, dass dieselbe Seite erneut angezeigt wird. Um stattdessen tatsächlich eine neue HTTP-Anfrage zu starten, muss man mit der Maus in das Adressfeld klicken und die Eingabetaste drücken.

Man kann nun beobachten, dass durch das Abschicken der neuen Anfrage ein neues Shop-Objekt von JSF erzeugt und mit den Initialwerten gefüllt wurde. Deswegen werden wieder alle Artikel angezeigt. Eine ähnliche Wirkung wird erzielt, wenn man mehr als einen Artikel zu löschen versucht: Vor jedem Löschvorgang wird das Sortiment wieder zurückgesetzt, sodass immer nur der zuletzt gelöschte Artikel fehlt.

Selbstverständlich ist das nicht das gewünschte Verhalten. Man möchte vielmehr, dass die Artikel dauerhaft aus dem Sortiment entfernt werden und so starten wir einen neuen Test mit dem Gültigkeitsbereich @SessionScoped. Nun lassen sich die Artikel nacheinander löschen. Es wird also nict mehr nach jeder Anfrage oder beim Aktualisieren der Seite das Shop-Objekt auf den Ursprungszustand zurückgesetzt. Bei einem Aufruf der Seite durch einen anderen Browser sind jedoch wieder alle Artikel vorhanden. Das liegt daran, dass ein zweiter Browser eine eigene Sitzung startet und aufgrund des neuen Gültigkeitsbereiches das Shop-Objekt nunmehr an eine solche Sitzung gebunden ist.

Im letzten Experiment wenden wir den Gültigkeitsbereich @ApplicationScoped auf die Managed Bean an. Die im Shop verwaltete Artikelliste soll unabhängig von einzelnen Anfragen, HTML-Seiten oder Browser-Sitzungen existieren. Ein Test zeigt sofort, dass auch mit unterschiedlichen Browser-Sitzungen und sogar von unterschiedlichen Geräten aus trotzdem auf dasselbe Shop-Objekt zugegriffen wird.