JavaServer Faces

Vorstellung des Standard-Framework von Sun

Danny Falss


[ Inhalt ] ... [ Einführung ] ... [ Eigene Erweiterungen ]

Aufbau und Techniken von JSF


Rollenkonzept

Bei der Entwicklung von JSF war ein wichtiges Ziel, dass die einzelnen Aufgaben innerhalb eines Projektes voneinander getrennt sind. Für diese Trennung wurden verschiedene Rollen eingeführt.

Komponentenentwickler schreiben eigene JSF-Komponenten, deren Tags von Seitenautoren verwendet werden. Zusammen mit JSF-eigenen Tags und HTML entsteht dann eine komplette Seite.

Die Aufgabe der Applikationsentwickler ist es die Anwendungslogik hinter den Seiten zu implementieren. Also Daten der Seiten aufzubereiten und Events der (eigenen) JSF-Tags zu verarbeiten.

Tool-Hersteller sollen Softwarelösungen für (einfachere) Erstellen eines JSF basierten Projektes entwickeln.

[ nach oben ]


Schichtenkonzept

Die Zielsetzung bei der der Entwicklung von JSF war es ein multifunktionales, aber auch leicht zu wartendes Framework zu entwickeln. Daher wurde auf Mehrschichtigkeit gesetzt,um eine klare Trennung zwischen den Aufgaben des Systems zu erreichen:

Für die Darstellung stehen zwei Tag-Bibliotheken zur Verfügung. Zum einen die Core-Bibliothek, die Standardfunktionen umfasst und zum anderen die HTML-Bibliothek, die Tags für alle HTML-Komponenten enthält. Eine Auflistung aller Tags ist unter http://horstmann.com/corejsf/jsf-tags.html einzusehen.

JSF setzt bei der Schichtentrennung ebenso wie viele andere Frameworks auf das MVC-Controller-Konzept

[ nach oben ]


Modell-View-Controller-Konzept

Das MVC-Konzept unterstützt die Aufteilung der Schichten aus dem vorherigen Kapitel. Es wird eine Entkopplung der Elemente erreicht. Dabei ist das Modell für die Datenhaltung zuständig. Die Präsentation der Daten erfolgt durch die View und der Controller vermittelt zwischen diesen beiden Komponenten.
MVC Konzept

Das MVC-Konzept arbeitet dabei ereignisorientiert. Eine View registriert sich im Modell und wird dann bei Änderungen informiert, so dass sich die View die neuen Daten abholen kann. Sollen durch eine View Daten geändert werden, so wir ein entsprechendes Ereignis im Controller ausgelöst und der Controller aktualisiert die Daten im Modell, das dann wiederum die View benachrichtigt.

Durch die strikte Trennung soll erreicht werden, dass sich die jeweiligen Elemente nur noch mit der ihnen zugedachten Aufgabe befassen und so nicht nur die Wartbarkeit der Anwendung erhöht wird, sondern auch Wiederverwendbarkeit erreicht wird.

Ein Designer kann die Views pflegen, ohne wie bei PHP oder JSP-Seiten Codeschnipsel in die Seiten einbauen zu müssen, da er mittels der vom Framework zur Verfügung gestellten Core- und HTML-Tags die Seite erstellt. Zusätzlich kann er noch die eigenen Erweiterungen, also eigenen Komponenten, verwenden. Lediglich die Schnittstelle zum Controller muss mit dem Applikationsentwickler abgestimmt werden. Daraus ergibt sich, dass die View-Komponente leicht austauschbar wird. Dadurch können unter identischen, logischen Abläufen und auf gleichen Daten andere Sichten erzeugt werden, z.B. eine XML Ausgabe. Unter Verwendung des gleichen Modells ist es auch denkbar eine Swing-Anwendung anstelle einer JSP-Anwendung zu erstellen, dazu ist aber auch ein neuer Controller notwendig.

Die Aufgabe des Controllers im Rahmen des MVC-Konzeptes besteht darin die Ablaufsteuerung zu übernehmen. Des weiteren kümmert er sich um Datenaktualisierungen im Modell nach Eintreffen eines entsprechenden Ereignisses. Er wird wie schon erwähnt von Applikationsentwicklern gepflegt.

Bei der Wiederverwendbarkeit ist vor allem die Verwendung von eigens erstellten Komponenten zu sehen, denn sind diese einmal erstellt, so können sie in jeder Anwendung verwendet werden. Auch Modelle sind wiederverwendbar, aber Views und Controller meistens nur gemeinsam aus der vorher schon erwähnten engen Verbindung.

Nachteile der strikten Trennung sind ein erhöhter Aufwand bei der Erstellung und der Ablauf ist nicht immer sofort zu erkennen, wodurch das Verständnis erschwert wird.

Ausgehend von der Schichtentrennung lassen sich das Frontend bzw. Backend im MVC-Konzept eindeutig der View bzw. dem Modell zuordnen und die Ablaufsteuerung sowie Aktualisierung der Daten übernimmt der Controller. Die Geschäftslogik der Anwendung kann entweder im Modell oder im Controller untergebracht werden. Bei umfangreichen Projekten wird diese sogar oft komplett ausgelagert, so dass sie sauber getrennt von JSF abläuft. Mittels EJBs (Enterprise Java Beans) können Transaktions-, Namens- oder Sicherheitsdienste realisiert werden, sie bilden einen eigenständigen Teil. Generell empfiehlt es sich für die Geschäftslogik das Entwurfsmuster der Fassade zu verwenden, da dadurch die Schnittstelle zu der Geschäftslogik leicht durch neue Funktionen erweitert werden kann bzw. vorhandene Funktionen transparent ausgetauscht werden können.

[ nach oben ]


JSF Expression Language

Die Verknüpfung zwischen Views und dem Controller erfolgt mittels der JSF EL. Damit ist es möglich innerhalb einer View auf Methoden zu zugreifen, dieses wird z.B. für Listener benötigt und nennt sich Method-Binding. Auch der Zugriff auf Variablen von Klassen kann erfolgen, so genanntes Value-Binding. JSF EL Ausdrücke sehen dabei so aus: #{Klasse.ziel}. Die Übergabe von Parametern ist dabei nicht direkt möglich.

[ nach oben ]


Bean-Management, Managed-Beans, Binding und Backing-Beans

JavaBeans sind Java Klassen, die zu Speicherung von Benutzer- oder Anwendungswerten verwendet werden. Die Verwaltung der Klassen beziehungsweise genauer die Verwaltung der Objekte übernimmt JSF. In JSP wurden diese JavaBeans in jeder Seite eingebunden. Managed-Bean bezeichnet dabei das Verfahren dieser (globalen) Bereitstellung, sprich die Verwaltung der Beans. Die Inkarnation erfolgt nach dem Lazy loading Prinzip. D.h. sie erfolgt immer erst dann, wenn auf eine Bean zum ersten Mal zugegriffen wird. Dazu kann zentral über die Konfigurationsdatei gesteuert werden, welchen Gültigkeitsbereich die erzeugten Objekte haben:

<managed-bean>
  <description>Verwaltet alle Teilnehmer</description>
  <managed-bean-name>MemberControllerBean</managed-bean-name>
  <managed-bean-class>sf.controller.MemberController</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Vgl. dazu Beispiel /Webroot/WEB-INF/tutadmin/faces-config.xml

Über ValueBinding können diese JavaBeans mit Komponenten verknüpft werden, so dass eine Kopplung zwischen dem Modell und der Sicht entsteht. Die verknüpften Beans heißen dann Backing-Beans. Sie gehören aus JSF-Sicht zur Schicht des Modells und werden auch als Modellobjekte bezeichnet. Sie besitzen Getter- und Setter-Methoden für ihre Membervariablen und können auch Aktions- sowie Validierungsmethoden bereitstellen. Allerdings ist es sauberer die Methoden in einen Controller zu kapseln, um das Modell möglichst unabhängig zu halten.

<h:inputText id="name" value="#{MemberControllerBean.currentMember.name}">
Vgl. dazu Beispiel /Webroot/tutadmin/member_edit.jsp

In MemberControllerBean wird auf eine Variable namens currentMember zugegriffen von der wiederum die Eigenschaft name interessiert. Der interne Zugriff erfolgt über die Getter- und Setter-Methoden: .getCurrentMember.getName() bzw. .getCurrentMember.setName(neuerWert).

[ nach oben ]


Überblick über JEE-Server

Das JSF-Framework läuft innerhalb des JEE-Servers als Servlet, also ein serverseitiges Applet. Anfragen die den Server erreichen werden über das JSP-Servlet an das JSF-Servlet weitergeleitet und dieses kann die Antwort erstellen.
JEE Server Aufbau

Bekannte Applikation Server für Java-Anwendungen sind zum einen Glassfish von SUN (auf Basis des Java-Application-Servers) sowie Apache Tomcat. Um aus einer entsprechenden Anfrage (Request) eine Antwort zu generieren (Response) existiert innerhalb des Frameworks ein gewisser Grundmotor, der durchlaufen wird: der Lebenszyklus

[ nach oben ]


JSF Lifecycle

In Abhängigkeit von der Request- bzw. Responseart werden verschiedene Aktionen von Framework ausgeführt. Dabei werden bei der Arbeit mit JSF generell je zwei Arten von Request (Anfrage) und Response (Antwort) unterschieden:

Die Requests und Responses können in beliebiger Kombination auftreten, so kann es vorkommen, dass von einer reinen HTML-Seite (Non-Faces-Request) zu einer Faces-Seite (Faces-Response) verwiesen wird. Dieses ist meistens dann der Fall, wenn die entsprechende Anwendung gestartet wird. Umgekehrt folgt beim Verlassen einer Faces-Anwendung einem Faces-Request eine Non-Faces-Response. Der Standardfall bei der Arbeit mit JSF ist der Faces-Request gefolgt von einer Faces-Response. Dieses wird auch der Standard Lebenszyklus eines Request genannt und soll hier nun genauer betrachtet werden, da an diesem Ablauf viele Eigenschaften von JSF erklärt werden können.
Lifecycle
Quelle:
http://java.sun.com/javaee/5/docs/tutorial/doc/images/jsfIntro-lifecycle.gif

Restore View
Um das Eingangs erwähnte Dialoggedächtnis zu realisieren ist es notwendig die (Formular)Zustände zu speichern und wiederherzustellen. Dabei werden zwei Fälle unterschieden: Beim erstmaligen Abruf einer Seite existiert noch kein View-Objekt. Daher muss eines erzeugt werden, in dem ein leerer Komponentenbaum angelegt und im FacesContext einhängt wird. Danach kann sofort zur Render Response-Phase gesprungen, da keine Verarbeitung von übergebene Werten etc. nötig ist. Sollte bereits ein Komponentenbaum im FacesContext existieren, so wird dieses View-Objekt geladen und mit Validatoren, Konvertern und Listenern verknüpft.

Apply Request Values
In dieser Phase werden die übertragenen Werten des Requests (also die Daten des abgeschickten Formulars) in den Komponentenbaum übernommen, also in den entsprechenden Komponenten gesetzt. ActionEvents werden hier generiert, z.B, das Drücken des Buttons durch den der Request erzeugt wurde. Dabei ist wichtig dass hier noch keine Änderung des Modells erfolgt, sondern nur die String-Werte vorbereitet werden.

Process Validation
Anhand von Konvertern (aber auch Renderern) werden die gespeicherten Werte der vorherigen Phase in die Zielformate (Modelldatentypen) überführt. Anschließend werden alle Werte der Komponenten mittels registrierter Validatoren überprüft. Im Fehlerfall werden dabei üblicherweise komponentenbezogene Meldungen generiert und es wird mit der Render Response-Phase fortgefahren. Wodurch dieselbe Seite gerendert wird, da ihr Komponentenbaum noch FacesContext liegt.

Update Model Values
Wenn bis hier hin kein Fehler auftrat, so werden die überprüften Werte in das Modell übernommen. Dabei werden ValueChangeEvents generiert, falls sich ein Wert geändert hat. Nach dieser Phase werden die registrierten Listener über Wertänderungen informiert.

Invoke Application
Alle Ereignisse der Anwendungsebene werden verarbeitet. So wird z.B. die nachfolgende Seite ermittelt und ihr Komponentenbaum im Kontext abgelegt. Nach dieser Phase werden alle registrierten ActionListener benachrichtigt.

Render Response
Der im Kontext befindliche Komponentenbaum wird ausgegeben. Dazu wird die encode-Methode jeder Komponente ausgeführt. Wird also die Phase Invoke Application übersprungen so erfolgt das Rendern derselben Seite.

Process Events
Zum einen werden die für Events registrierte Listener benachrichtigt, als auch PhaseListener benachrichtigt. Letztere können den Ablauf beeinflusse oder nebenläufige Tätigkeiten angestoßen (z.B. Logging). Generell wird bei schweren Fehlern die Verarbeitung abgebrochen (Response Complete) oder vorzeitig eine Ausgabe erzeugt (Render Response).

[ nach oben ]


Das Dialoggedächnis mittels Zustandsspeicherung (State Saving)

Es kann in der Konfigurationsdatei ausgewählt werden ob Clientseitig mittels Serialisierung und Hidden-Fields in Formularen oder Serverseitig mittels einer Session die Zustände gespeichert werden sollen. Nachteil von serverseitiger Speicherung ist das Problem des Timeouts nach einer bestimmten inaktiven Zeit, sowie die potentielle Angreifbarkeit des Systems z.B. durch das Senden einer großen Anzahl von Anfragen an den Server (für jede Anfrage wird eine Session eröffnet und Resourcen verbraucht). Das Speichern beim Client hingegen erhöht den Datenverkehr erheblich, da die gesamten Sitzungsdaten immer in den Seiten codiert vorliegt. Außerdem ergibt sich zudem die Gefahr, dass ein bösartiger Benutzer es schafft die Session zu verändern, z.B. ein Login vorzutäuschen.

[ nach oben ]


Validierung und Konvertierung

Wie beschrieben müssen Daten vor der Übernahme in das Modell umgewandelt und überprüft werden. Dabei ermöglichen es Validatoren Stringeingaben einer Bereichsprüfung zu unterziehen. Konverter werden von JSF bei ValueBindings implizit ausgeführt, können aber auch explizit angegeben werden. JSF liefert drei Standardvalidatoren:

Und darüber hinaus noch etliche Konverter für das Umwandeln von Strings aus Eingabekomponenten in die entsprechenden Java-Datentypen u.U.:

Id Klasse
javax.faces.BigDecimal javax.faces.convert.BigDecimalConverter
javax.faces.BigInteger javax.faces.convert.BigIntegerConverter
javax.faces.Boolean javax.faces.convert.BooleanConverter
javax.faces.Byte javax.faces.convert.ByteConverter
javax.faces.Character javax.faces.convert.CharacterConverter
javax.faces.DateTime javax.faces.convert.DateTimeConverter
javax.faces.Double javax.faces.convert.DoubleConverter
javax.faces.Float javax.faces.convert.FloatConverter

[ nach oben ]


Navigationskonzept

Nach der Übernahme der Daten erfolgt das Ausführen der eigentlichen Anwendung. In diesem Rahmen ist auch die Ablauflogik zu sehen. Die Verknüpfung der einzelnen Views ist nicht statisch in der jeweiligen Seite unterzubringen, sondern die Verkettung erfolgt über die Konfiguartionsdatei. Es werden also Symbole eingeführt die innerhalb der Views verwendet werden, so dass ein umdefinieren eines Symbols Auswirkungen auf alle Seiten hat. Dieses ist interessant für Portalseiten. Durch Navigationsregeln wird festgelegt zu welcher Seite verwiesen werden soll, wenn eine bestimmte Aktion einen bestimmten Rückgabewert (Outcome) liefert.

<navigation-rule>
  <from-view-id>/tutadmin/member.jsp</from-view-id>
  <navigation-case>
    <from-outcome>ta_member_edit</from-outcome>
    <to-view-id>/tutadmin/member_edit.jsp</to-view-id>
  </navigation-case>
</navigation-rule>
Vgl. dazu Beispiel /Webroot/WEB-INF/tutadmin/faces-config.xml

Von der Sicht member.jsp ist die nächste anzuzeigende Seite member_edit.jsp, falls der Rückgabewert von member.jsp ta_member_edit ist. Eine spezielle Ausgangsaktion wurde hierbei nicht angegeben. Die Angabe könnte mir <from-action>...</from-action> erfolgen. Ein Link würde dann so aussehen: <h:commandlink action=“ta_member_edit“ value=“...“/>

Dynamik kommt ins Spiel, wenn nicht statisch das Symbol ta_member_edit verwendet wird, sondern an dieser Stelle eine Methode aufgerufen wird, die einen entsprechenden Navigationsstring zurückliefert. Wie schon erwähnt erfolgt aufgrund dieses Strings (also das Symbol) die Ermittlung der Folgeseite.

[ nach oben ]


Internationalisierung

In Zeiten globalen Wettbewerbs werden nahezu alle größeren Softwarelösungen international eingesetzt. Wünschenswert wäre es, wenn sich die Webseite an die jeweiligen Spracheigenschaften bzw. -wünsche des Benutzers anpassen würde. JSF nutzt hierzu die etablierten Resource Bundles aus Javas I18N- und L10N-APIS.

Drei Schritte zur Internationalisierung:

  1. Anlegen einer Resourcedateien pro Sprache.

    Vgl. dazu Beispiel /src/sf/resource/msg_de.properties, msg_en.properties

  2. Eintragen der Resourcedatei und der verfügbaren Sprachen in die Konfigurationsdatei.

    <application>
      <locale-config>
        <supported-locale>de</supported-locale>
        <supported-locale>en</supported-locale>
      </locale-config>
      <message-bundle>sf.resource.msg</message-bundle>
    </application>
    Vgl. dazu Beispiel /Webroot/WEB-INF/tutadmin/faces-config.xml

  3. Benutzen der Resourcedateien

    <f:view locale="#{LocaleControllerBean.currentLocale}"> Darstellen der aktuellen View mit der ausgewähtlen Sprache <f:loadBundle basename="sf.resource.msg" var="res" /> Bekanntmachen des Resourcebundles innerhalb dieser Datei unter der Variable res <h:outputText value="#{res.title}"/> Ausgabe des Titels aus der Resourcedatei mit dem Schlüssel title
    Vgl. dazu Beispiel /Webroot/tutadmin/overview.jsp

[ nach oben ]


Events & Listener

Während der Abarbeitung der verschiedenen Phasen werden von JSF bzw. von den Komponenten Ereignisse (Events) generiert und in Warteschlangen eingereiht. Am Ende der Phasen werden die entsprechenden Events abgearbeitet und die verknüpften Methoden (Listener) aufgerufen.

  1. ActionEvent und -Listener

    Ereignisse werden generiert in der Apply Request Values-Phase. Die Verarbeitung bzw. Übergabe an die Listener erfolgt erst in der Invoke Application-Phase. ActionListener können an CommandButtons und CommandLinks angehängt werden. Für Navigationszwecke wird das action-Attribut verwendet. Methodenaufrufe, z.B. das Setzen eines Zählers können mittels des actionListener-Attributs oder des <f:actionListener id=““/>-Tags erfolgen. Mit dem Tag können auch mehrere Listener eingehängt werden.

  2. ValueChangeEvent und -Listener

    Wenn sich ein Wert im Modell ändert (Phase: Update Model Values) werden die Ereignisse erstellt und anschließend an diese Phase von den Listenern verarbeitet. ValueChangeListener können an alle UIInput-Komponenten angehängt werden. Dazu existiert ähnlich wie bei den ActionListenern zum einen das valueChangeListener-Attribut und das <f:valueChangeListener id=““/>-Tags

  3. PhaseEvent und -Listener

    Wird ein PhaseListener erstellt, so wird dieser vor und nach einer oder allen Phasen des Lebenszyklus aufgerufen und seine Methode gestartet. Das Beobachten aller Phasen kann bei Debuging, Monitoring oder Logging interessant sein. Eine einzelne Phase zu üverwachen kann z.b. für das Überprüfen eines Logins sinnvoll sein (dazu wird dann vor der RestoreView-Phase eine Prüfung durchgeführt) Bei der Verwendung von PhaseListenern sowie von actionListener- bzw. valueChangeListener-Tags müssen diese entsprechend in der Konfiguaration eingetragen werden.
    Vgl. dazu Beispiel /Webroot/WEB-INF/tutadmin/faces-config.xml


[ nach oben ] ... [ zurück ] ... [ weiter ]

Valid XHTML 1.0 Strict