Verweise

JSF 2 Download

JSF Buch online
JSF 2.1 Doku von Mojarra
Java EE 6 Doku von Oracle
Expression Language

Projekt Lombok

Startseite

Web Anwendungen mit JSF 2

Dieses Tutorial soll Anfängern helfen, sich im Rahmen einer Neuentwicklung für oder gegen ein Framework zu entscheiden. Es zeigt nur die grundlegenden Aspekte von JSF. Der Leser sollte bereits wenigstens eine Anwendung auf Basis der Servlet API (ohne Framework) geschrieben haben.

Wenn Sie das Funktionsprinzip von JSF verstanden haben, werden Sie mit Hilfe der oben rechts aufgelisteten Links weiter arbeiten können.

Was ist JSF?

JSF ist ein Grundgerüst für die schnelle Entwicklung von Web Anwendungen in Java. Es trennt die Darstellungsschicht von der Programmlogik, jedoch etwas anders als beim Modell 2 (MVC Pattern).

Das Java Programm enthält Beans (einfache Klassen), welche Zugriff auf Daten durch get- und set- Methoden bereit stellen. Und sie enthalten Aktions-Methoden, die z.B beim Klick auf einen Button ausgeführt werden. JSF lädt diese Beans bei Bedarf automatisch in den Speicher.

Zwischen diesen Beans und dem Web Browser steht die JSF Template Engine. Sie erzeugt Webseiten auf der Grundlage von Vorlagen-Dateien, die mit Daten aus den Beans befüllt werden.

    Sie haben sich mit dem Namen #{user.name} angemeldet.

Ihre Java Klassen enthalten kein HTML und die Templates enthalten kein Java-Code. Auf diese Weise lässt sich die Arbeit des Java Programmierers sauber von der des HTML Programmierers trennen. Für den Datenaustausch zwischen Bean und Webseite validiert und konvertiert JSF die Werte automatisch (z.B. von String nach Integer und zurück).

Facelets

Facelets sind Templates, also Vorlagen für Webseiten, typischerweise mit der Dateiendung *.xhtml. Facelets enthalten neben HTML auch Ausdrücke aus der Expression Language und JSF Tags. Expression Language Ausdrücke sehen so aus:
    #{user.name}
    #{geld.eingang - geld.ausgang}
    #{auto.farbe != rot}

Zum Aufbau von Bedingten Abschnitten, Schleifen und anderen Kontrollstrukturen verwendet man JSF Tags. Beispiele:

	<ui:fragment rendered="#{user.alter > 12}">
	    Du bist alt genug
	</ui:fragment>
	
	<ui:repeat var="auto" value="#{meinBean.alleAutos}">
	    Das Auto #{auto.name} ist #{auto.farbe}.
	</ui:repeat>

Weiterhin gibt es Tags zum Erzeugen von HTML Dokumenten. Zum Beispiel erzeugt das h:selectBooleanCheckbox Tag eine Check-Box, die an eine booleansche Variable gebunden ist. Die Standard Tags sind für HTML-Dokumente vorgesehen. Weitere Dateiformate sind theoretisch möglich, aber meines Wissens nach nicht umgesetzt worden.

Wie wirkt sich JSF auf die Performance aus?

JSF bietet ungefähr gleich gute Performance, wie andere Web Frameworks, also mittelmäßige. Einerseits wird die Softwareentwicklung vereinfacht, was andererseits signifikant Rechenleistung kostet. Doch Rechenleistung ist meist billiger, als Personalkosten.

Für Anwendungen, die Zig-Megabyte große Dokumente in kürzester Zeit erzeugen müssen, würde ich jedenfalls Servlets ohne irgendwelche Frameworks bevorzugen. Doch das kommt nicht oft vor.

Was brauche ich mindestens?

Um eine JSF 2 Anwendung zu entwickeln, brauchen Sie mindestens Die jeweils aktuellste Version finden Sie auf der Webseite des Mojarra Project.

Laden Sie sich die Datei JSFTest.zip herunter. Das ist ein Netbeans Projekt, es kann aber auch in allen gängigen IDE's importiert werden. Die folgenden Erklärungen beziehen sich auf dieses Beispiel-Projekt.

Konfiguration

Servlets

Jede Web Anwendung hat als erste Konfigurationsdatei die WEB-INF/web.xml. JSF ist ein Aufsatz auf die Servlet API, folglich ist ein Servlet zu konfiurieren:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">

    <context-param>
        <description>Development or Production</description>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value> 
    </context-param>

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
</web-app>

Mit dem Kontext-Parameter definiert man den Projekt-Status. In der Entwicklungsphase erzeugt JSF auf Kosten der Performance detailliertere Fehlermeldungen. Der Wert kann mit Application.getProjectStage() abgefragt werden.

Die Beispielapplikation bindet das JSF Servlet an das URL-Muster *.jsf. HTTP Requests mit dieser Endung werden durch JSF beantwortet. Sie müssen das Servlet entweder wie in diesem Beispiel an irgendeine Dateiendung binden, oder an einen Pfad, z.B. facelets/*.

Faces-Config

Die Konfiguration des JSF Frameworks befindet sich in der Datei WEB-INF/faces-config.xml. Ab Version 2.0 kann JSF aber weitgehend durch die viel bequemeren Annotationen konfiguriert werden. Das heisst, sie brauchen erstmal keine faces-config.xml Datei.

Facelets

Zuerst möchte ich Ihnen zeigen, wie ein JSF Template (Facelet) auszusehen hat. Man kann diese Templates bereits nutzen, ohne eine einzige Zeile Java Programmcode schreiben zu müssen. Erstellen Sie eine Datei index.xhtml (in dem Order, wo auch WEB-INF und META-INF liegen) mit folgendem Inhalt:

index.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:body>
        Hallo.
        <br/>
        <br/>
        Die Zahl ist: #{param['zahl']}
        <br/>
        Deim Browser ist ein: #{header['User-Agent']}
        <br/>
        Das Cookie "JSESSIONID" hat den Wert: #{cookie['JSESSIONID'].value}
        <br/>
        Der Username in der Session lautet: #{sessionScope['userName']}
        <br/>
        Deine Session-ID ist: #{session.id}
        <br/>
        Drei mal Sieben ist: #{3*7}
        <br/>
        <br/>
        <a href="index.jsf?zahl=12">klick mich</a>
    </h:body>
</html>

Bildschirmfoto vom ersten Template

Obwohl die Datei über die URL http://localhost:8080/JSFTest/index.jsf aufgerufen wird, muss die Template-Datei die Endung xhtml haben. Die Endung "jsf" aus der URL entspricht der Konfiguration in der web.xml. Die Endung der Template-Datei ist vom Framework vorgegeben.

Das obige Beispiel zeigt einige Spezial-Variablen, die vom JSF Framework gesetzt werden: Request Parameter, Request Header, Cookie-Werte und die HTTP Session.

Alle Map basierten Objekte kann man mit der Syntax name['key'] ansprechen. Listen und Arrays spricht man im Prinzip genau so an: name[index]. Wenn Sie dieses Beispiel ausprobieren, werden Sie keinen Usernamen sehen, weil dieses Session-Attribut nicht gesetzt ist, also null ist.

Das obige Beispiel mit der Session-ID ruft vom HttpSession Objekt die Methode getId() auf und beim Cookie ruft es die Methode getValue() auf.

Für booleansche Werte gibt es noch die is-Methode. Zum Beispiel würde user.erwachsen die Methode getErwachsen() oder isErwachsen() vom user-Objekt aufrufen.

Dieses Prinzip lässt sich intuitiv auf beliebig viele Unter-Objekte anwenden. Mit user.daten.adresse.PLZ rufen Sie von User-Objekt die Methode getDaten() auf, von dem Daten-Objekt wird die Methode getAdresse() aufgerufen und von der Adresse wird die Methode getPLZ() aufgerufen.

Listen und Beans

Im nächsten Beispiel zeige ich Ihnen, wie Sie mit einem Template Daten aus einem Java Bean anzeigen. In diesem Fall soll es eine Liste mit Namen sein. In allen folgenden Java Codes habe ich der Übersicht wegen die import Statements weg gelassen.

Namen.java:

@ManagedBean
@ApplicationScoped
public class Namen {

    private ArrayList<String> meineNamen=new ArrayList<String>();
    
    public Namen() {
        meineNamen.add("Silke");
        meineNamen.add("Marvin");
        meineNamen.add("Pumuckl");
    }

    public int getAnzahl() {
        return meineNamen.size();
    }

    public List<String> getListe() {
        return meineNamen;
    }
}

Die Annotation @ManagedBean bewirkt, dass das Bean automatisch bei Bedarf instantiiert wird und der Template Engine bekannt gemacht wird. Es ist egal, in welchem Package die Klasse liegt. Bei Managed-Beans zählt nur der einfache Name.

Die Annotation @ApplicationScoped legt fest, daß dieses Bean genau einmal geladen wird und dann solange im Speicher bleibt, wie die Applikation läuft. Die Variante @RequestScoped würde hingegen bewirken, daß für jeden HTTP Request eine neue eigene Instanz des Beans erzeugt würde. Ein @SessionScoped Bean wird in der HTTP-Session abgelegt und ggf. wieder verwendet. Für interaktive Webseiten gibt es den @ViewScoped, welcher solange existiert, wie der Benutzer die aktuelle Seite benutzt.

Erstellen Sie dazu passend das Template liste.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets">
    <h:body>

        Die Liste enthält #{namen.anzahl} Elemente.
        <br/>
        <br/>

        <ui:repeat var="einName" value="#{namen.liste}">
           #{einName}<br/>
        </ui:repeat>
        <br/>
        <br/>

        <h:outputText value="Die Liste ist leer"
            rendered="#{namen.anzahl==0}"/>

        <ui:fragment rendered="#{namen.liste.size()!=0}">
            Die Liste ist nicht leer
        </ui:fragment>

    </h:body>
</html>

Bildschirmfoto, Liste anzeigen

Das Tag ui:repeat wird verwendet, um die Elemente von Listen anzuzeigen. Alternativ gibt es das Tag ui:dataTable speziell zum Erzeugen von Tabellen.

Das Tag h:outputText ist nützlich, um die Ausgabe an eine Bedingung anzuknüpfen. Alternativ können Sie auch ui:fragment verwenden. Wegen einem Bug markieren die meissten Entwicklungsumgebungen das rendered Attribut vom ui:fragment Tag als fehlerhaft. Ignorieren Sie die Meldung einfach. Dieses Attribut ist definitiv gültig, da es spezifiziert ist und auch tatsächlich funktioniert.

Beachten Sie, wie ich die Länge der Liste abgefragt habe. Im ersten Fall wird mit namen.anzahl die Methode Namen.getAnzahl() aufgerufen. Weniger Tipparbeit hat man, wenn man die Liste direkt befragt, wie das Beispiel darunter zeigt. Da die Methode der Liste aber nicht getSize() heisst, sondern einfach nur size() muss man hier die Klammern mitschreiben.

Wenn Sie im Internet nach Methoden für bedingte Ausgaben suchen, werden Sie ganz sicher auf die Tags c:if und c:choose stossen. Verwenden Sie diese Tags nicht! Das sind nämlich JSTL Tags für JSP. Wenn Sie in einem Template JSTL und JSF Tags vermischt einsetzen, kommt es früher oder später zu unerwarteten Fehlfunktionen. Ich finde es von Oracle besonders gemein, dass sie die JSTL und JSP Tags in einem gemeinsamen Dokument zusammengefasst haben, ohne davor zu warnen.

Schauen Sie sich die Beschreibung der Expression Language an, um herauszufinden welche Möglichkeiten sie für die Formulierung von Ausdrücken bietet.

Formulare

Bei Formularen kümmert sich JSF um die Validierung, Typ-Konvertierung und den Austausch der Eingabefelder zwischen dem HTML Formular und der Bean-Klasse. Bei Klicks auf Aktions-Buttons werden Methoden einer Controller-Klasse aufgerufen.

Man kann für Daten-Bean und Aktions-Controller separate Klassen definieren. Oft ist es jedoch einfacher, beides miteinander zu kombinieren, wie in diesem Beispiel:

@ManagedBean
@SessionScoped
public class PersonFormular implements Serializable {

    private static final long serialVersionUID=1L;

    private String name=null;
    private Integer einkommen=null;

    public void setName(String name) {
        this.name=name;
    }

    public void setEinkommen(Integer einkommen) {
        this.einkommen=einkommen;
    }

    public String getName() {
        return name;
    }

    public Integer getEinkommen() {
        return einkommen;
    }

    public String speichern() {        
        if (einkommen==101) {
            FacesContext.getCurrentInstance().addMessage("das_einkommen",
                    new FacesMessage("101 ist ausnahmsweise nicht erlaubt"));
            return null;
        }
        else {
            System.out.println("Speichere "+name+", "+einkommen);
            return "gespeichert";
        }
    }

    public String abbrechen() {
        return "abgebrochen";
    }
}

Dieser Controller hat zwei private Felder, welche die Werte aus dem Formular aufnehmen sollen. Dazu passend hat er Setter und Getter Methoden, wie man es von einem Bean erwartet.

Die Methode speichern() soll aufgerufen werden, wenn der Benutzer auf den Sende-Button des Formulars klickt. Die Methode abbrechen() soll aufgerufen werden, wenn der Benutzer auf den Abbrechen-Button klickt.

Zu diesem Formular gehören drei Templates, die in einem eigenen Unterverzeichnis liegen, so daß man erkennen kann, welche Templates zusammen gehören. person/person.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>JSF Test Formular</title>
    </h:head>
    <h:body>

        <h:messages/>
        <h:form prependId="false">

            Name:
            <h:inputText id="der_name" 
                value="#{personFormular.name}" 
                required="true" 
                requiredMessage="Du musst einen Namen eingeben" />
            <h:message for="der_name"/>
            <br/>

            Einkommen:
            <h:inputText id="das_einkommen" 
                value="#{personFormular.einkommen}" 
                validatorMessage="Die Zahl muss im Bereich 100-100000 liegen" 
                converterMessage="Das ist keine Zahl" >
                <f:validateLongRange  minimum="100" maximum="100000"/>
            </h:inputText>
            <h:message for="das_einkommen"/>
            <br/>

            <h:commandButton 
                value="Speichern" 
                action="#{personFormular.speichern}"/>
            <h:commandButton 
                value="Abbrechen" 
                action="#{personFormular.abbrechen}"/>

        </h:form>
    </h:body>
</html>

person/gespeichert.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>JSF Test Startseite</title>
    </h:head>
    <h:body>
        Deine Eingabe wurde gespeichert.
        <br/>
        <a href="person.jsf">Weiter</a>
    </h:body>
</html>

person/abgebrochen.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:body>
        Du hast die Eingabe abgebrochen.
        <br/>
        <a href="../index.jsf">Weiter</a>
    </h:body>
</html>

Das Eingabeformular sieht im Web-browser so aus:

Bildschirmfoto, Formular

Schauen Sie sich an, was für einen HTML Quelltext die Template Engine erzeugt hat (Ansicht/Seitenquelltext anzeigen).

Das h:inputText Tag erzeugt ein einfaches Formularfeld, aber auch Java Code zur Validierung der Eingabe. Das Attribute required="true" legt fest, daß in dem Namens-Feld eine Eingabe erforderlich ist. Das Attribute requiredMessage legt den Meldungstext fest, der im Fehlerfall erscheinen soll.

Das Eingabefeld für das Einkommen darf der Benutzer aber leer lassen. Dafür prüfen wir hier, ob die Zahl zwischen 100 und 100000 liegt. Die converterMessage wird angezeigt, wenn die Eingabe nicht in einen Integer umgewandelt werden kann. Wie Sie sehen, findet die Typumwandlung ganz automatsich statt. JSF wandelt alle einfachen Datentypen automatisch um, also Integer Long, Float, usw.

Das Tag h:messages zeigt alle Fehlermeldungen als Liste an. Wer die Fehlermeldungen lieber neben den Eingabefeldern anzeigen möchte, muß jedem Eingabefeld eine ID geben und das Tag h:message zusammen mit der ID benutzen.

Schauen Sie sich die speichern() Methode an. Hier wird eine weitere Variante der Validierung vorgeführt. In diesem Fall wird eine Fehlermeldung erzeugt, wenn das Einkommen=101 ist. Diese Art der Validierung bietet sich besonders bei entweder/oder Feldern an, wo der Benutzer wenigstens eins von beiden Felder ausfüllen muss.

Es gibt noch eine dritte Möglichkeit, Eingaben zu validieren. Sie können mit dem Attribut validator beim h:inputText eine eigene Klassen-Methode angeben, die zur Validierung verwendet werden soll. Auf sehr ähnliche Art können Sie mit dem Attribut converter eine eigene Methode zur Typ-Konvertierung anbieten.

Die Aktions-Methoden speichern() und abbrechen() geben immer einen String zurück. Dieser String bestimmt, welches Template als Nächstes angezeigt werden soll. Der besondere Wert null bewirkt, daß das aktuelle Formular erneut angezeigt wird.

Beachten Sie, daß ich die Controller Klasse als @SessionScoped gekennzeichnet habe. Wenn Sie das Formular schon einmal aufgefüllt haben und es später erneut aufrufen, sehen Sie ihre alten Eingaben wieder.

Wenn Sie die Attribute validatorMessage und converterMessage weg lassen, verwendet JSF Meldungstexte aus jsf-impl.jar:/com/sun/faces/resources/*.properties.

Internationalisierung

Bei der Internationalisierung geht es darum, einzelne Textabschnitte in mehreren Sprachen bereit zu stellen. Diese Text-Stücke legen Sie in einem Satz Properties-Dateien ab, welches in der faces-config.xml registriert wird.

WEB-INF/classes/texte.properties:

welcome=Herzlich Willkommen
and=und
goodbye=auf Wiedersehen
prefer_number=Bevorzugst Du die Zahl {0} oder {1}?

WEB-INF/classes/texte_en.properties:

welcome=Welcome
and=and
goodbye=goodbye
prefer_number=Do you prefer the number {0} or {1}?

WEB-INF/faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.0"
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

    <application>
        <resource-bundle>
            <base-name>texte</base-name>
            <var>messages</var>
        </resource-bundle>
    </application>

</faces-config>

hello.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">

    <f:view locale="#{facesContext.externalContext.requestLocale}">
        <h:head>
            <title>JSF Test Internationalisierung</title>
        </h:head>

        <h:body>

            #{messages.welcome}
            #{messages['and']}
            #{messages.goodbye}

            <br/><br/>

            <h:outputFormat value="#{messages.prefer_number}">
                <f:param value="7"/>
                <f:param value="13"/>
            </h:outputFormat>

            <br/><br/>
            2000+1/3=
            <h:outputText value="#{2000+1/3}">
                <f:convertNumber maxFractionDigits="4"/>
            </h:outputText>

            <br/><br/>
            <h:form>
                <h:inputText value="#{sessionScope['zahl']}">
                    <f:converter converterId="integerConverter"/>
                </h:inputText>
                <h:commandButton type="submit" value="Senden"/>
            </h:form>

        </h:body>
    </f:view>
</html>

Auf dem Bildschirm sieht es so aus, wenn ich in meinen Web-Browser die bevorzugte Sprache auf Englisch stelle:

Bildschirmfoto, Internationalisierung

Das Tag f:view ist neu. Über dessen locale Attribut bestimmen Sie die Darstellungssprache. Anstatt einen festen Wert (wie z.B. "de") anzugeben, benutzen wir die Sprache, die der aufrufende Benutzer in seinem Web-Browser eingestellt hat. Für englische Benutzer wird die englische Properties Datei verwendet und für deutsche Benutzer wird die deutsche Datei verwendet.

Ohne das f:view Tag würde die Applikation sich stattdessen an der Sprache des Servers orientieren. Wenn der Server z.B. ein deutsches Windows ausführt, würden die Webseiten auf deutsch erscheinen, auch wenn der Benutzer ein Engländer ist.

Bei der Meldung für "and" bzw. "und" musste eine andere Syntax gewählt werden, weil "and" kein gültiger Java Methoden-Name ist. Bei der gewählten alternativen Syntax wird "and" aber als String gewertet, nicht als Methoden-Name.

Darunter sehen Sie, wie Meldungen aus Properties Dateien mit Variablen angereichtert werden. In diesem Fall wird {0} durch die "7" ersetzt und {1} wird durch die "13" ersetzt.

Darunter sehen Sie, wie man Zahlen formatiert. Der f:numberConverter unterstützt viele weitere Parameter, mit denen Sie die Formatierung beeinflussen können. Für Datum und Uhrzeit verwenden Sie f:convertDateTime. Die beiden Konverter berücksichtigen die Landes-spezifische Schreibweise des Benutzers, sofern Sie entweder ein f:view Tag wie oben gezeigt drumherum bauen oder den Paramater locale verwenden.

Wie man eigene Konverter schreibt, zeigt das letzte Beispiel mit dem Formular-Feld. Hier können Sie eine Zahl in hexadezimaler schreibweise eingeben, die dann als Integer in der Session gespeichert wird und als natürliche Zahl formatiert ausgegeben wird. Aus "0xFF" wird nach Klick auf den Senden-Button eine "255". Der Konverter wird dabei zweimal aufgerufen. Beim Senden des Formulars wird die getAsObject(...) Methode aufgerufen, und beim Laden der Seite wird getAsString(...) aufgerufen.

IntegerConverter.java:

@FacesConverter("integerConverter")
public class IntegerConverter implements Converter {

    public Object getAsObject(FacesContext facesContext, 
      UIComponent uiComponent, String param) {
        return Integer.decode(param);
    }

    public String getAsString(FacesContext facesContext, 
      UIComponent uiComponent, Object obj) {
        return String.valueOf(obj);
    }
}

Die Annotation @FacesCoverter benötigt als Parameter einen Namen, entsprechen dem converterId Parameter im Template. Die Konvertierung von hexadezimaler Schreibweise zu Integer übernimmt hier die Methode Integer.decode().

Properties Dateien

Beachten Sie folgende Regel, für das Laden von Properties Dateien: Diese Regel führt unter Umständen zu einem unerwarteten Verhalten. Ein Beispiel: In diesem Fall sieht der engliche Benutzer unerwartet deutsche Texte. Denn es gibt keine Datei mit der Endung _en, doch weil es eine Datei in der Sprache des Servers gibt (nämlich _de) wird diese Ersatzweise herangezogen. Aus genau dem gleichen Grund würde ein schwedischer Benutzer ebenfalls deutsche Seiten sehen, obwohl er warscheinlich mit englischem Text viel mehr anfangen kann.

Sie können dieses merkwürdige Verhalten umgehen, indem sie entweder sicherstellen, dass die Sprache der Standard-Datei der Sprache des Server entspricht (wie im obigen Beispiel, dort sind beide deutsch) oder indem Sie eine leere Properties Datei mit Sprach-Endung anlegen, die der Sprache der Standard-Datei entspricht. Also zum Beispiel eine texte.properties in englisch, dazu eine leere texte_en.properties, sowie beliebig viele Übersetzungen in anderen Sprachen.

JSF enthält bereits Properties Dateien für die Meldungen der eingebauten Validatoren (siehe vorheriges Kapitel). Es gibt viele Möglichkeiten, diese Meldungen durch eigene zu ersetzen. Ich bevorzuge folgende Methode: Kopieren Sie die Dateien jsf-impl.jar:/com/sun/faces/resources/*.properties in ihr Source-Verzeichnis (also nach src/java/com/sun/faces/resources) und editieren Sie sie dort. Der Classloader sucht dort nämlich zuerst und wird daher ihre modifizierten Dateien bevorzugt laden.

Falls Sie einmal in Java Code ein solches Property verwenden müssen, tun sie das so:

public static String getWording(String key, Object... args) {
    FacesContext ctx=FacesContext.getCurrentInstance();
    ResourceBundle bundle=ctx.getApplication().getResourceBundle(ctx, "messages");
    String message=bundle.getString(key);
    return MessageFormat.format(message, args);
}

String result=getWording("prefer_number",7,13);
Oder binden Sie das ResourceBundle via DependencyInjection ein, siehe nächstes Kapitel.

Dependency Injection

Sie haben gelernt, daß JSF die Managed-Beans automatisch bei Bedarf instantiiert und der Template Engine (also den Facelets) verfügbar macht. Mittels Dependency Injection können sie solche Beans auch anderen Klassen zur Verfügung stellen. Die folgende Klasse demonstriert dies.

Injection.java:

@ManagedBean
@RequestScoped
public class Injection {

    @ManagedProperty(value="#{namen}")
    private Namen namen;
    
    public void setNamen(Namen namen) {
        this.namen=namen;
    }
    
    public Namen getNamen() {
        return namen;
    }

    public String getSecondName() {
        return namen.getListe().get(1);
    }
}

Die Annotation @ManagedProperty bewirkt, daß beim Erzeugen des Objektes "Injection" auch eine Instanz vom Objekt "Namen" erzeugt wird, und dieses dann über den Setter setNamen() zugewiesen wird. Wenn Sie @ManagedProperty benutzen, muss immer auch ein passender Setter vorhanden sein.

injection.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html">
    <h:body>

        Der zweite Name ist #{injection.secondName}.
        <br/><br/>
        Das Objekt injection ist #{injection.toString()}.
        <br/><br/>
        Das Objekt namen ist #{injection.namen.toString()}.
        
    </h:body>
</html>

Dieses Facelet ruft die Methode Injection.getSecondName() auf, welche den zweiten Namen aus der bereits bekannten Namens-Liste liefert. Spannender sind die beiden folgenden Zeilen, welche die Hash-Codes der beteiligten Objekte anzeigen. Wenn Sie diese Webseite mehrmals laden (F5 drücken), dann sehen Sie, dass das Objekt "injection" bei jedem Request immer wieder neu erzeugt wird, während das Objekt "namen" immer das Gleiche bleibt. Dies liegt an den unterschiedlich festgelegten Scopes.

Die Webseite sieht so aus:

Bildschirmfoto, Dependency Injection

Serialisierung

Beans, die SessionScoped oder ViewScoped sind, müssen serialisierbar sein, weil sie in der Session des Benutzers gespeichert werden. Der Server (z.B. Tomcat) lagert die Session in Dateien aus, wenn die Applikation neu gestartet wird. Und im Cluster werden Sessions eventuell zwischen den Nodes synchronisiert. Um Performance und Speicherbedarf zu optimieren, sollte man vermeiden, große Mengen von Daten in die Session zu schreiben.

Eine Klasse ist dann serialisierbar, wenn sie mit "implements Serializable" deklariert wurde. Alle Felder der Klasse müssen ebenfalls entweder serialisierbar sein, oder als "transient" gekennzeichnet sein, sonst schlägt die Serialisisierung zur Laufzeit fehl. Die folgenden beiden Fehlerszenarien sind typisch:

  1. Wenn eine SessionScoped oder ViewScoped Klasse nicht serialisierbar ist, geht der Status der Klasse bei Neustart der Applikation verloren. Als Entwickler sollten Sie daher testen, ob die Applikation (nicht der ganze Server) korrekt gestoppt und wieder gestartet werden kann. Die entsprechende Warnmeldung von Tomcat lautet: "Cannot serialize session attribute ...". Wenn die Applikation ohne Warnmeldung neu gestartet werden kann und dabei den Status der Beans nicht vergisst, dann wird die Applikation warscheinlich auch im Cluster funktionieren.
  2. Nach dem Zurücklesen serialisierter Objekte sind alle transienten Felder null oder 0. Sie können die Methode readResolve() überschreiben, um transiente Felder nach dem Deserialisieren zu initialisieren.
Die Klasse TransientTest und die Webseite transient.jsf aus dem Test-Projekt demonstrieren dies.

TransientTest.java:

@ManagedBean
@SessionScoped
public class TransientTest implements Serializable {
    private static final long serialVersionUID = 1L;
    
    Date date=new Date();    
    transient Integer a=1;
    transient Integer b;
    transient Integer c;
    
    public TransientTest() {
        b=2;
        c=3;
    }
    
    private Object readResolve() {
        c=4;
        return this;
    }
    
    public Date getDate() {
        return date;
    }
    
    public Integer getA() {
        return a;
    }
    
    public Integer getB() {
        return b;
    }
    
    public Integer getC() {
        return c;
    }
    
}

Rufen Sie die Webseite auf:

Bildschirmfoto, Transient Test

Re-Starten sie dann den Server und laden Sie die Webseite neu, ohne den Browser zwischenzeitlich zu schließen:

Bildschirmfoto, Transient Test

Am unveränderten Datum erkennen Sie, dass das managed Bean den Neustart des Servers überlebt hat. Das Datum ist das einzige serialisierbare Feld der Klasse. Die Werte der anderen drei Felder (a, b und c) sind verloren gegangen. C ist allerdings beim Wiederherstellen des Objektes mit einem neuen Wert initalisiert worden.

Rich Faces

Eine beliebte Erweiterung zu JSF ist die Rich-Faces Tag Library. RichFaces kombiniert mehrere HTML Elemente mit Javascript, um komplexe Gestaltungs-Elemente bereit zu stellen. Zum Beispiel eine Tabelle mit verschiebbaren Spalten, ein Dialogfenster zur Eingabe eines Datums.

Ich habe inzwischen einige Jahre mit RichFaces gearbeitet und kann leider nur davon abraten, es zu verwenden.

Die Umstellung einer größeren Webanwendung von Richfaces 3.3 nach 4 führte zu massiven Mehraufwand, weil viele zuvor stabile Funktionen plötzlich nicht mehr richtig funktionierten. Darüber hinaus wurden einige Funktionen (die gar nicht mehr funktionierten) einfach ersatzlos gestrichen: Beispielsweise das Sortieren und Filtern in Tabellen, das Drop-Down Menü, der Dialog zur Auswahl einer Farbe. Tabellen mit variabler Anzahl von Spalten sind nicht mehr möglich. Selbst 4 Jahre nach Veröffentlichung der Version 4.0 ist RichFaces immer noch voller Fehler und von den verlorenen Features wurde lediglich das Menü wiederbelebt.

Einfach die ältere stabile Version 3.3 zu nutzen, kommt kaum in Frage, weil sie seit 2009 nicht mehr gepflegt wird und nur auf veralteten Webservern läuft, die ebenfalls seit vielen Jahren nicht mehr gepflegt werden.

Eigentlich verspricht RichFaces dem Entwickler, sich nicht mit Javascript beschäftigen zu müssen. Angeblich genügen Anfänger-Kenntnisse in HTML, um mit RichFaces voll Gas zu geben. Aber das war und ist lediglich ein schöner Traum. In der Wirklichkeit muss man sich sogar sehr gut mit HTML, CSS, Javascript und jQuery auskennen, nämlich um die Probleme von RichFaces zu begreifen und dann Lösungen entwickeln zu können. Die ungenügende Dokumentation erschwert dies noch.

Kurz gesagt: RichFaces bringt mehr Probleme, als es nützt.

Nachwort

Ich hoffe, daß Sie nun einen groben Eindruck davon haben, wie man JSF einsetzen kann. Auf dieser Webseite habe ich längst nicht alle Funktionen von JSF beschrieben, darum schauen Sie sich bei Gelegenheit die weitergehenden Informationen an, auf die der Kasten oben rechts verweist.

Achten Sie bei der Suche nach Dokumentation auf die richtige Version und verwechseln Sie nicht JSP mit JSF. Denn sowohl das alte JSF 1.2 als auch JSP werden immer noch gerne verwendet und dominieren das Angebot frei verfügbarer Dokumentation. Lassen Sie sich nicht dazu verleiten, die Tags der JSTL Library in Facelets zu verwenden, da dies zu sehr schwer greifbaren Fehlfunktionen führen kann.

Testen Sie die Serialisierung gründlich, um sicherzustellen, dass die Applikation in Produktion nach einem Server-Ausfall oder gewolltem Neustart funktioniert.

Versuchen Sie nicht, die Kommunikation zwischen Web-Browser und managed Bean am Framework vorbei zu implementieren. Denn dies führt zu sporadischen Fehlfunktionen, z.B. dass einzelne Benutzereingaben nicht gespeichert werden oder Aktions-Methoden nach Klick auf Buttons nicht aufgerufen werden.

Und schauen Sie sich mal das Projekt Lombok an. Es erspart ihnen einige Tipparbeit und macht die Quelltexte der Beans übersichtlicher.