1. Einleitung

PrimeFaces ist mitunter das meistgenutzte Komponenten-Framework für JavaServer Faces Anwendungen. Ich zeige euch in diesem Artikel, wie ihr in wenigen Minuten eine JSF-Anwendung mit Maven erstellt, PrimeFaces einbindet und anschließend per Docker ausführt. Als Beispiel erzeugen wir exemplarisch eine ToDo-Liste, inklusive der Möglichkeit ToDo’s hinzuzufügen und zu entfernen.

Screenshot der ToDo-Anwendung

Den vollständigen Code findest du auf GitHub: https://github.com/MatthiasPischka/java-ee-8-beispiele/tree/master/primefaces-schnellstart

2. Vorbereitung

Bevor wir uns um die eigentliche Implementierung kümmern können, sind einige Konfigurationen vorzunehmen.

pom.xml

 <project ...>
  ... 
  <artifactId>primefaces-schnellstart</artifactId>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>8.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>8.0</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>${project.artifactId}</finalName>
  </build>
</project>

Über die pom.xml machen wir das Projekt zu einem Maven-Projekt und holen uns die benötigten Abhängigkeiten rein. Ich gebe gerne über finalName den endgültigen Namen ohne Versionsangabe an, sodass dieser sich nicht im Laufe der Zeit ändert.

  • javaee-api: Diese Abhängigkeit wird für das Kompilieren benötigt, sie wird allerdings nicht in das war-Archiv eingepackt, da der verwendete Server die Implementierung mitbringt.
  • primefaces: Das zu verwendende JSF-Framework.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_4_0.xsd"
         version="4.0">
  <context-param>
    <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>*.xhtml</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.xhtml</welcome-file>
  </welcome-file-list>
  <!-- Zur Änderung des Trenners in IDs (von ':' zu '-') -->
  <context-param>
    <param-name>javax.faces.SEPARATOR_CHAR</param-name>
    <param-value>-</param-value>
  </context-param>

  <!-- PrimeFaces -->
  <context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>nova-light</param-value>
  </context-param>
</web-app>

Hierbei handelt es sich um eine kurz gehaltene, für JSF benötigte, web.xml. Auf PrimeFaces bezogen ist für uns der letzte Eintrag (primefaces.THEME) relevant. Über diesen Parameter lässt sich eines der mitgelieferten Themes auswählen. Eine Liste möglicher Werte findest du hier.

faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.3" 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">

</faces-config>

Ab JSF 2.3 kann die (leere) faces-config.xml eigentlich weggelassen werden, wenn stattdessen eine Bean zur Konfiguration angelegt wird (siehe weiter unten). ABER es gab in frühen Versionen von JSF 2.3.x einen Bug (siehe https://github.com/javaserverfaces/mojarra/issues/4270), sodass ich diese bisher mitziehe. So ist sichergestellt, dass es immer funktioniert.

@FacesConfig(version = FacesConfig.Version.JSF_2_3)
public class ConfigurationBean {
}

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_2_0.xsd"
       bean-discovery-mode="all">
</beans>

Über die beans.xml geben wir an, dass wir in der Laufzeitumgebung von einer CDI 2.0 Implementierung ausgehen und noch viel wichtiger, dass alle Klassen als CDI-Beans behandelt werden sollen, unabhängig von ihrer Annotation (bean-discovery-mode="all").

3. Implementierung

Model

Zur technischen Darstellung der ToDo’s legen wir eine simple Klasse an, die lediglich den Titel enthält. Des Weiteren fügen wir eine ID mit Hilfe von java.util.UUID hinzu, um die Instanzen auseinanderhalten zu können.

public class ToDo {

  private final String id;
  private final String titel;

  public ToDo(String titel) {
    this.id = UUID.randomUUID().toString();
    this.titel = titel;
  }

  public String getId() {
    return id;
  }

  public String getTitel() {
    return titel;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    ToDo toDo = (ToDo) o;
    return id.equals(toDo.id);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
}

View

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui">

<h:head>
  <title>PrimeFaces Schnellstart</title>
</h:head>

<h:body>
  <h1>ToDo Verwaltung</h1>

  <h:form id="form_index">
    <h2>Anlegen</h2>
    <h:panelGrid columns="2">
      <p:outputLabel for="todo" value="ToDo:"/>
      <p:inputText id="todo" value="#{toDoVerwaltungController.toDoNeu}"/>
    </h:panelGrid>
    <p:commandButton value="Anlegen" action="#{toDoVerwaltungController.anlegen}" update="@form"/>

    <h2>Abhaken</h2>
    <p:dataTable id="todos"
                 var="todo"
                 value="#{toDoVerwaltungController.toDos}"
                 selection="#{toDoVerwaltungController.toDosErledigt}"
                 rowKey="#{todo.id}"
                 emptyMessage="Keine ToDos vorhanden">
      <p:column selectionMode="multiple" style="width:16px;text-align:center"/>
      <p:column headerText="Titel">
        <h:outputText value="#{todo.titel}"/>
      </p:column>
    </p:dataTable>
    <p>
      <p:commandButton value="Löschen" action="#{toDoVerwaltungController.loeschen}" update="@form"/>
    </p>
  </h:form>

</h:body>
</html>

Der vorangegangene Code-Abschnitt zeigt die gesamte index.xhtml. Das ist alles, was wir benötigen, um die oben gezeigte Darstellung der Anwendung zu erreichen. Die PrimeFaces-spezifischen Zeilen sind hervorgehoben.

Im ersten Abschnitt wird ein Eingabefeld, welches an die Instanzvariable toDoNeu des ToDoVerwaltungControllers gebunden wird, gerendert. Es erhält zusätzlich ein Label.

Im zweiten Abschnitt verwenden wir eine Tabelle von PrimeFaces, um zum einen die angelegten ToDo’s anzuzeigen und zum anderen diese markierbar zu machen. Dazu bietet die PrimeFaces-Tabelle die Möglichkeit eine Collection anzubinden (selection), die die markierten ToDo’s vorhält.

In beiden Fällen wird ein CommandButton verwendet, der jeweils zum Aufruf einer Controller-Methode dient und im Anschluss das gesamte Formular (update="@form") neu rendert.

Controller

@Named
@ViewScoped
public class ToDoVerwaltungController implements Serializable {

  private List<ToDo> toDos;
  private List<ToDo> toDosErledigt;
  private String toDoNeu;

  @PostConstruct
  private void init() {
    this.toDos = new ArrayList<>();
    this.toDos.add(new ToDo("Testen"));
    this.toDos.add(new ToDo("Dokumentieren"));
  }

  public void anlegen() {
    this.toDos.add(new ToDo(this.toDoNeu));
    this.toDoNeu = "";
  }

  public void loeschen() {
    this.toDosErledigt.forEach(this.toDos::remove);
  }

  // Getter und Setter
}

Der Controller enthält die Steuerung für die zuvor gezeigte View. Dieser ist recht simpel gehalten. Die drei Instanzvariablen haben wir bereits bei der View gesehen:

  • toDos: Liste der aktuell angelegten ToDo’s, die in der DataTable von PrimeFaces dargestellt werden.
  • toDosErledigt: Enthält die in der Tabelle selektierten ToDo’s.
  • toDoNeu: Ist an das Eingabefeld zur Anlage eines neuen ToDo’s gebunden.

Neben den hier nicht gezeigten Getter und Setter besteht der Controller aus den drei folgenden Methoden:

  • init: Wird über @PostConstruct beim initialen Aufruf der View ausgeführt und legt Beispieldaten an, sodass bereits ToDo’s vorhanden sind.
  • anlegen: Entnimmt der Variable toDoNeu die Zeichenkette und erzeugt damit ein neues ToDo.
  • loeschen: Löscht alle ToDo’s aus toDos, die zuvor auch in toDosErledigt abgelegt wurden.

4. Ausführen mit Docker

Ich nutze den WildFly Application Server zur Ausführung von Java EE Anwendungen. Dieser lässt sich sehr gut in einem Docker Container verpacken und mit dem war-File der Anwendung versorgen. Hierzu enthält das vollständige Beispiel auf GitHub einen Ordner Docker. Dieser enthält das benötigte Dockerfile sowie Shell-Skripte zum Bauen der Anwendung per Maven, Bauen des Images inklusive war-File per Docker und Starten des Docker-Containers.

Eine genaue Anleitung ist im Hauptverzeichnis der Java EE Beispiele zu finden: https://github.com/MatthiasPischka/java-ee-8-beispiele


4 Kommentare

Lutz Spieker · 4. März 2021 um 10:49

Steige gerade neu in das Thema ein. Sehr schoene, kurze und informative Anleitung. Hat mir sehr geholfen.

    Matthias Pischka · 26. August 2021 um 22:00

    Vielen Dank für den allerersten Kommentar und das Feedback, dass die Anleitung hilfreich ist.

Kurt aus Dahlewitz · 20. August 2021 um 16:13

Habe 2 Tage nach einer Anleitung gesucht und bestimmt 10 ausprobiert.
Gott sei Dank habe ich deine Seite auch gefunden.
Und dir natürlich auch ein: „Danke schön“

    Matthias Pischka · 26. August 2021 um 22:01

    Ich danke dir für deinen wertvollen Kommentar. Das beflügelt mich noch mehr zu diesen Themen zu Schreiben.

Schreibe einen Kommentar

Avatar-Platzhalter

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert