Java Server Faces
Willemers Informatik-Ecke
Java Server Faces (JSF) ist eine Java Enterprise-Technologie für das User-Interface. Java Server Faces verwendet Facelets, die auf der Basis von XHTML aufsetzen. Ein Facelet arbeitet immer im Zweiklang mit einer Backing Bean zusammen, die den Controller-Teil der Model-View-Controller-Architektur übernimmt.

Im Gegensatz zu Java Server Pages bettet JSF kein Java ein, sondern greift auf die Attribute seiner Backing Bean zu, die direkt durch die Eingabefelder eingegeben und ebenso direkt ausgegeben werden können. Ein manuelles Umsetzen wie bei Java Server Pages (JSP) ist also nicht erforderlich. 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.

JSF mit Eclipse erzeugen

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.

Zahlenspiele mit JSF

Das erste Beispiel ist eine JSF-Seite, die zwei Zahlen addieren soll.

Backing Bean

Bevor Sie eine JSF-Datei anlegen, sollten Sie eine Backing Bean erzeugen, da das Faclet auf dieser basiert.

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 erfüllen 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 String calc() {
        summe = a + b;
        return "/index.xhtml";
    }
}
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.

Darüber hinaus wird eine Methode (hier calc) benötigt, die über den Button ausgelöst wird. Sie hat keinen Parameter, liefert aber als String die Seite zurück, zu der navigiert werden soll, nachdem die Aktion ausgeführt wurde.

faces-config.xml

In der JSF-Konfigurationsdatei werden die ManagedBeans erfasst. Eclipse stellt einige Dialoge dazu zur Verfügung. Letztlich entsteht eine XML-Datei faces-config.xml, die unter /WebContent/WEB-INF zu finden ist:
<?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>lieferung</managed-bean-name>
        <managed-bean-class>Lieferung</managed-bean-class>
        <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>

</faces-config>

Anlegen eines Facelets

In dem Projekt wird nun das Facelet als XHTML-Datei angelegt. Sie können unter Eclipse einen Web-Editor verwenden, der es ermöglicht, die Elemente per Drag & Drop zu setzen. Es entsteht der Rahmen einer XHMTL-Datei index.xhtml. Sie wird so angepasst, 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>
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.

Backing Beans als Datenobjekte

Normalerweise werden Backing-Beans nicht als Taschenrechner eingesetzt, sondern repräsentieren ein Datenobjekt. Als Beispiel dient eine Lieferung, die per Facelet erfasst werden soll:
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

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}" />

Eine Tabelle mit JSF

Neben einer Eingabemaske ist die häufigste Anwendung die Darstellung einer Tabelle. Hier wird eine einfache Kundentabellen-Bean gestrickt:
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class KundenTabelleBean implements Serializable {

    private static final long serialVersionUID = 4487L;
    private Kunde[] kundenliste = new Kunde[] {
            
            new Kunde("Hugo", "Hamburg", "112"),
            new Kunde("Erna", "Gintoft", "110")
    };
    
    public Kunde[] getKundenliste() {
        return kundenliste;
    }
}
Der passende Kunde entspricht dem obigen Beispiel. Allerdings benötigt er noch einen Konstruktor für die Stringvariablen name, ort und telefon, damit die Beispieltabelle einfach mit Daten gefüllt werden kann. Und da ein parametrisierter Konstruktor den Standardkonstruktor verdeckt, muss der noch zusätzlich eingebaut werden:
public class Kunde implements Serializable {
    // ...
    public Kunde() {}
    public Kunde(String n, String o, String t) {
        this.name=n; this.ort=o; this.telefon=t;
    }
Nun benötigen wir ein Facelet.
<?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"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      >
    <h:body>        
        <h1>JSF Kundenliste</h1>
            <h:dataTable value="#{kundenTabelleBean.kundenliste}" var="kd">
                <h:column>
                    <f:facet name="header">Name</f:facet>
                    #{kd.name}
                </h:column>
                <h:column>
                    <f:facet name="header">Ort</f:facet>
                    #{kd.ort}
                </h:column>
                <h:column>
                    <f:facet name="header">Tel.</f:facet>
                    #{kd.telefon}
                </h:column>
            </h:dataTable>
    </h:body>        
</html>

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.

JEE-Literatur

Alexander Salvanos Professionell entwickeln mit Java EE 7

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