Java Server Faces
Willemers Informatik-Ecke
Java Server Pages (JSP) REST

Java Server Faces (JSF) ist eine Jakarta/Java Enterprise-Technologie für das User-Interface. Java Server Faces findet seinen Einsatz bei der Erstellung von dynamischen Webseiten und ist dort vergleichbar mit den Java Server Pages (JSP).

Im Gegensatz zu Java Server Pages bettet JSF keinen Java-Code in den HTML-Code ein. Stattdessen definiert sie Oberflächenelemente im HTML-Code (Facelet), die sich auf Elemente in einem Datenobjekt befinden, das als Backing Bean bezeichnet wird.

Die Benutzeraktivitäten auf der Oberfläche verändern das zugrundeliegende Objekt und umgekehrt. Dazu verbindet das Facelet die Oberflächenkontrollelemente direkt mit den Attributen der Backing Bean. Ebenso kann ein Facelet durch Buttons oder Links die Methoden der Backing Bean aufrufen. Für diese Zugriffe und Aufrufe definiert Java Server Faces eigene Tags.

Beispiel: Addieren mit JSF

Das erste Beispiel ist eine JSF-Seite, die zwei Zahlen addieren soll. Als Entwicklungsumgebung wird Eclipse mit Glassfish vorausgesetzt.

Backing Bean

Die Backing Bean, die die Basis für das Facelet darstellt, ist im Grunde eine einfache Klasse mit Attributen für jedes Oberflächenelement und Methoden für jedes Event, das allerdings als Backing Bean angemeldet wird. Die Erzeugung geschieht unter Eclipse also wie bei jeder anderen Klasse auch:

CalcBackBean wird durch die Annotation @Named als Backing Bean gekennzeichnet. @ManagedBean ist inzwischen veraltet.

Mit dem Scope wird die Gültigkeit der Bean beschrieben. Der Import muss aus javax.enterprise.context stammen. javax.faces.beans ist deprecated und führt ab Glassfish 5 zu einem Deploy-Fehler.

Die Bean muss Serializable implementieren und eine unique serialVersionUID haben.
import java.io.Serializable;


import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped

public class CalcBackBean implements Serializable {
    private static final long serialVersionUID = 199543L;
    private int a;
    private int b;
    private int summe=12;
    public void setA(int a) {
        this.a = a;
    }
    public int getA() {
        return a;
    }
    public void setB(int b) {
        this.b = b;
    }
    public int getB() {
        return b;
    }
    public int getSumme() {
        return summe;
    }
    
    public void calc() {
        summe = a + b;
    }
}
Die Attribute a und b sind die Summanden. Sie müssen private sein. Für jedes Feld, das ausgegeben werden soll, muss ein Getter existieren. Alle Elemente, für die es ein Eingabefeld gibt, benötigen einen Getter und einen Setter.

Für jeden Button der Oberfläche wird eine Methode (hier calc) definiert. Sie hat keinen Parameter und führt hier die Addition der beiden Variablen aus.

Die Methode kann einen String als Rückgabewert zurückgeben, in dem die Seite angegeben wird, zu der der Client navigieren soll, nachdem die Aktion ausgeführt wurde.

    public String calc() {
        summe = a + b;
        return "index.xhtml";
    }
In unserem Beispiel wäre das aber weniger sinnvoll, da der Benutzer dann das Ergebnis der Addition nicht sehen könnte.

Backing Bean in faces-config.xml anmelden

In der JSF-Konfigurationsdatei werden die ManagedBeans erfasst. Eclipse stellt für deren Pflege einige Dialoge zur Verfügung. Diese werden bei Doppelklick auf die Datei faces-config.xml geöffnet, die sich im Projekt unter dem Verzeichnis WebContent/WEB-INF befindet.

In der Datei faces-config.xml müssen die Backing Bean angemeldet werden, damit sie von den Facelets verwendet werden können. In ihrem Ausgangszustand sieht die Datei folgendermaßen aus:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
           http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
    version="2.3">

</faces-config>

Unter dem Editor gibt es mehrere Tabs. Um eine Backing Bean anzumelden, ist es am einfachsten, folgende Schritte durchzuführen.

Letztlich entsteht eine XML-Datei faces-config.xml, die unter /WebContent/WEB-INF zu finden ist. Gegebenenfalls können noch direkte Änderungen unter der Lasche Source durchgeführt werden.

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                        http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
    version="2.3">
    <managed-bean>
        <managed-bean-name>calcBackBean</managed-bean-name>
        <managed-bean-class>CalcBackBean</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>

</faces-config>

Anlegen eines Facelets

Facelets werden in XHTML kodiert. XHTML besteht auf einer saubereren Verwendung der Tags als HTML. Dort wird das fehlende Schließen eines Tags lässig ignoriert. Für das Facelet wird also eine XHTML-Datei angelegt. Die Datei befindet sich im Projektordner unter WebContent/WEB-INF.

Die Dateien enden zwar auf .xhtml, werden aber in einem Link mit der Endung .jsf aufgerufen.

Arbeiten mit dem Web-Editor

Sie können unter Eclipse einen Web-Editor verwenden, der es ermöglicht, die Elemente per Drag & Drop zu setzen.

In der Palette befinden sich alle möglichen Elemente, die per Drag und Drop in den Bildschirm gezogen werden können. Für den Anfang befinden sich die wichtigsten Elemente unter JSF-HTML.

Anpassen des Additions-Facelets im Editor

Das Additions-Facelet können Sie mit folgenden Elementen schnell per Drag und Drop zusammenklicken: Durch diese Aktionen wird die XHMTL-Datei ergänzt. Im normalen Editor können Sie sie noch so anpassen, wie in dem folgenden Listing:
<?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://xmlns.jcp.org/jsf/html">
<h:head>
    <title><h:outputText value="Wir addieren" /></title>
</h:head>
<h:body>
    <h:form>
        <h:panelGrid columns="2">
            <h:outputText value="A = " />
            <h:inputText value="#{calcBackBean.a}">
            </h:inputText>
            <h:outputText value="B = " />
            <h:inputText value="#{calcBackBean.b}">
            </h:inputText>
            <h:outputText value="Summe = " />
            <h:outputText value="#{calcBackBean.summe}" />
            <h:outputText value="Los geht's" />
            <h:commandButton value="OK" action="#{calcBackBean.calc}" />
        </h:panelGrid>
    </h:form>
</h:body>
</html>
Es ist zu erkennen, dass die Elemente der Backing Bean per #{bean.element} erreicht werden.

Starten der JSF-Seite

Sie können die XHTML-Datei mit der rechten Maustaste anklicken und mit Run As | Run on Server starten. Funktioniert das nicht, steht die XHTML-Datei eventuell nicht unter WebContent im Projekt.

Obwohl die Datei im Projekt als index.xhtml abgelegt ist, wird sie über den Browser als index.jsf angesprochen.

Spezielle JSF-Tags

Attribute benutzerdefinierter Tags werden in der Property der UI-Komponente deklariert. Um die Tags, die mit h: beginnen aufzulösen, muss die Definition xmlns:h="http://xmlns.jcp.org/jsf/html" in das html-Tag eingebunden sein. Mit <h: beginnende Tags bilden HTML-Elemente nach.

Der Zugriff von Facelets auf ihre Backing Bean

JSF leiten den Zugriff auf Elemente durch ein Doppelkreuz (Lattenzaun, Raute) ein und umschließen sie mit geschweiften Klammern.

Nach dem Starten des Facelets werden die Kontrollelemente sofort mit den Werten aus der Backing Bean vorbelegt. Ist das nicht der Fall, ist die Backing Bean vermutlich noch nicht in der faces-config.xml angemeldet.

Datenobjekt als Eingabe und Liste

Normalerweise werden Backing-Beans nicht als Taschenrechner eingesetzt, sondern repräsentieren ein Datenobjekt. Typischerweise werden JSF für die Interaktion mit einem Objekt erstellt. Dabei ergeben sich zwei Dialoge: Der Übersicht halber werden Liste und Dialog im Beispiel in einem Facelet zusammengefasst.

Projekt

Zur Realisierung wird ein Projekt benötigt.

Backing Bean

Wohnung

Eine Wohnung hat folgende Attribute: Die Klasse wird erzeugt, indem das Projekt rechts angeklickt wird und New Class aus dem Menü gewählt wird.
import java.io.Serializable;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named
@ApplicationScoped
public class Wohnung implements Serializable {
    private static final long serialVersionUID = 3020832L;
    private String bez;
    private int qm;
    private double miete;
}
Um für die privaten Attribute die passenden Getter und Setter zu machen, kann Eclipse helfen.

WohnungsListe

Für die Wohnungsliste wird eine statische ArrayList in der Klasse Wohnung angelegt. Für den Zugriff aus dem Facelet wird eine nicht-statische get-Methode benötigt.
private static ArrayList<Wohnung> liste = new ArrayList<>();
    
public ArrayList<Wohnung> getListe() {
    return liste;
}

Eintrag in faces-config.xml

In der faces-config.xml wird die Wohnung eingetragen. Unter der Lasche Source müsste sie zum Schluss so aussehen. Im Zweifelsfall nacheditieren.
<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
              http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
    version="2.3">
    <managed-bean>
        <managed-bean-name>wohnung</managed-bean-name>
        <managed-bean-class>Wohnung</managed-bean-class>
        <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>
</faces-config>

Facelet für die Wohnung

Zunächst wird ein normales Formular für die Eingabe erstellt.
<h:form>
    <h:panelGrid border="1" columns="2">
        <h:outputText value="Name: "></h:outputText>
        <h:inputText value="#{wohnung.bez}"></h:inputText>
        <h:outputText value="qm: "></h:outputText>
        <h:inputText value="#{wohnung.qm}"></h:inputText>
        <h:outputText value="Miete: "></h:outputText>
        <h:inputText value="#{wohnung.miete}"></h:inputText>
        <h:outputText value="Übernehmen "></h:outputText>
        <h:commandButton value="OK" action="#{wohnung.insert}"/>
    </h:panelGrid>
</h:form>
Der Button OK ruft die Methode insert auf.

Die Methode insert erzeugt ein neues Objekt und kopiert \ident{this} hinein. Das ist erforderlich, weil ansonsten alle Referenzen in der Liste auf dasselbe Objekt verweisen. Die Liste wird dann sehr langweilig.

public void insert() {
    Wohnung w = new Wohnung();
    w.setBez(bez); w.setQm(qm); w.setMiete(miete);
    liste.add(w);
}
Über oder unter das Eingabe-Formular wird ein DataTable gesetzt.
<h:dataTable value="#{wohnung.liste}" var="wng">
    <h:column>
        <f:facet name="header">Name</f:facet>
        #{wng.bez}
    </h:column>
    <h:column>
        <f:facet name="header">qm</f:facet>
        #{wng.qm}
    </h:column>
    <h:column>
        <f:facet name="header">Miete</f:facet>
        #{wng.miete}
    </h:column>
    <h:column>
        <f:facet name="header">Aktion</f:facet>
        <h:commandButton value="Löschen" action="#{wng.delete}"/>
    </h:column>
</h:dataTable>
Wichtig ist die Einfügung der xmlns-Zeilen im HTML-Tag.
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
Der Button in der Tabelle ruft in action die Methode delete über das Element der entsprechenden Zeile.

Also muss die Methode nur das aktuelle Objekt aus der Liste entfernen.

public void delete() {
    liste.remove(this);
}

Einbinden von CSS

.table {
    border-collapse:collapse;
}
.header{
    background:none repeat scroll 0 0 #EEEEEE;
    border-bottom:1px solid #BBBBBB;
}

.odd-row{
    background:none repeat scroll 0 0 #FFFFFFF;
    border-top:1px solid #BBBBBB;
}

.even-row{
    background:none repeat scroll 0 0 #DDDDDD;
    border-top:1px solid #BBBBBB;
}
Im Header wird die CSS-Datei in die JSF eingebunden.
<h:head>
    <h:outputStylesheet library="css" name="table.css"  />
</h:head>
Die CSS-Classes werden in der h:dataTable-Tag angesprochen.
<h:dataTable value="#{wohnung.liste}" var="wng"
    styleClass="table"
    headerClass="header"
    rowClasses="odd-row,even-row"
>

Und hier das Ergebnis:

Kombination von Backing Beans: Lieferung und Kunde

In manchen Fällen haben Klassen Fremdschlüssel. So geht eine Lieferung an einen Kunden. Es gelten also die Klassen Lieferung und Kunde.

Im ersten Schritt wird der Kunde als einfacher String in der Klasse Lieferung hinterlegt.

import java.io.Serializable;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named
@ApplicationScoped
public class Lieferung implements Serializable {
    private static final long serialVersionUID = 2134L;
    private String artikelnr;
    private double menge;
    private String kunde;
   // Es folgen die Getter und Setter und die Methode für den Button

Hier müssen die benötigten Getter und Setter definiert werden. Außerdem muss die Backing Bean in der faces-config.xml angemeldet werden, wie oben beschrieben.

Das Facelet greift auf die Attribute der Backing-Bean zu:

<h:head>
    <title><h:outputText value="Lieferung:" /></title>
</h:head>
<h:body>
    <h:form>
        <h:panelGrid columns="2">
            <h:outputText value="Artikel " />
            <h:inputText value="#{lieferung.artikelnr}">
            </h:inputText>
            <h:outputText value="Menge: " />
            <h:inputText value="#{lieferung.menge}">
            </h:inputText>
            <h:outputText value="Kunde: " />
            <h:inputText value="#{lieferung.kunde}" />
            <h:outputText value="Speichern" />
            <h:commandButton value="OK" action="#{lieferung.speichern}" />
        </h:panelGrid>
    </h:form>
</h:body>

Bean-Injection

Im nächsten Schritt soll der Kunde allerdings nicht als einfacher String repräsentiert werden, sondern über eine eigene Bean Kunde. Dazu wird Kunde in die Bean Lieferung injected.
import java.io.Serializable;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@ApplicationScoped
public class Lieferung implements Serializable {
    private static final long serialVersionUID = 2134L;
    private String artikelnr;
    private double menge;
    @Inject
    private Kunde kunde;

    public Kunde getKunde() {
        return kunde;
    }
    public void setKunde(Kunde kunde) {
        this.kunde = kunde;
    }
    // Der Rest wie gehabt...

Die Bean Kunde sieht der Bean Lieferung sehr ähnlich.

import java.io.Serializable;

import javax.enterprise.context.Dependent;
import javax.inject.Named;

@Named
@Dependent
public class Kunde implements Serializable {
    private static final long serialVersionUID = 1L;
    private long id;
    private String name;
    private String ort;
    private String telefon;
    // Auch hier Getter und Setter...
Die Annotation @Dependent sorgt dafür, dass der Scope der Bean, in die injected wird, übernommen wird. Die injected Bean sollte keinen weiteren Scope haben als die einbindende Bean.

Im Formular des Facelets wird nun über lieferung auf kunde und von dort auf dessen Attribut name referenziert.

<h:outputText value="Kunde: " />
<h:inputText value="#{lieferung.kunde.name}" />

Zugriffe auf die HTTP-Umgebung

URL-Parameter auslesen

Über den Link können Werte auch bei GET-Kommandos übergeben werden. Beispiel:
<a href="index.xhtml?wert=42">Link mich!</a>
Eine JSF liest diesen Parameter über die Map param aus:
Die Antwort ist  #{param['wert']}

Sitzungsinformationen

Templating

JSF-Seiten sollten über die Anwendung hinweg gleichmäßig aussehen. Das unterstützt JSF durch Templating. Ein Template enthält Platzhalter für andere ein JSF-Dokumente. Jenes JSF-Dokument wiederum bezieht sich auf das Template, in das es eingebettet werden will.

JSF und Eclipse

Für das Nachvollziehen der JSF-Beispiele benötigen Sie ein Eclipse für JEE mit Glassfish. Tomcat wird an dieser Stelle nicht empfohlen, da Tomcat Nachinstallationen benötigt, um Java Server Faces zu realisieren.

Manche Deploy-Fehler sind folgendermaßen zu beseitigen.

JEE-Literatur

Alexander Salvanos Professionell entwickeln mit Java EE 8

Marcus Schießer, Martin Schmollinger: Workshop Java EE 7

Etwas abstrakter: Dirk Weil: Java EE 7: Enterprise-Anwendungsentwicklung leicht gemacht

Das Buch enthält über 100 Seiten über JEE: Arnold Willemer: Java Alles-in-einem-Band für Dummies

Links