Dies ist eine überarbeitete/erneuerte Version von Schnellstart: PrimeFaces 8 mit JSF 2.3. Damals ging es noch um Java EE 8.

1. Einleitung

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

Screenshot der ToDo-Anwendung

Den vollständigen Code findest du auf GitHub: https://github.com/MatthiasPischka/jakarta-ee-beispiele/tree/main/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>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>10.0.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>14.0.6</version>
            <classifier>jakarta</classifier>
        </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.

  • jakarta.jakartaee-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 JF-Framework. Achtung: Wenn wir Jakarta verwenden, müssen wir hier den Classifier jakarta angeben, um PrimeFaces mit jakarta-Package-Struktur heranzuziehen.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <context-param>
        <param-name>jakarta.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>jakarta.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>
</web-app>

Hierbei handelt es sich um eine kurz gehaltene, für Jakarta Faces benötigte, web.xml. Auf PrimeFaces bezogen ist hier nichts zu finden. Per Standard wird das Theme saga verwendet. Hier könntest du aber auch explizit ein anderes THEME angeben. Eine Übersicht aller Themes findest du hier.

faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="4.0" xmlns="https://jakarta.ee/xml/ns/jakartaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                                  https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd">
</faces-config>

Die für Jakarta Faces 4.0 vorgesehene (leere) faces-config.xml. Diese kann eigentlich weggelassen werden, wenn stattdessen eine Bean zur Konfiguration mit @FacesConfig versehen wird. Allerdings kann es aus meiner Sicht nicht schaden die faces-config.xml an gewohnter Stelle vorzufinden.

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd">
</beans>

Über die beans.xml geben wir an, dass wir in der Laufzeitumgebung von einer CDI 4.0 Implementierung ausgehen. Per Standard werden nur entsprechend annotierte Klassen zu CDI-Beans. Wenn es anders gewünscht wäre, müssten wir das Verhalten mit einer expliziten Angabe von bean-discovery-mode="all" anpassen.

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 implements Serializable {

    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 Objects.equals(id, toDo.id);
    }

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

View

<!DOCTYPE html>
<html lang="de"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.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 selectionBox="true" 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 beginnen jeweils mit <p:. Bitte achte auch auf die Verwendung der neuen Jakarta Namespaces (hier z.B. xmlns:h="jakarta.faces.html")

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 auswählbar 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 jeweils ein CommandButton verwendet, der 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 Jakarta EE Anwendungen. Dieser lässt sich optimal 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.


0 Kommentare

Schreibe einen Kommentar

Avatar-Platzhalter

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