Java EE 7 - Enterprise-Anwendungsentwicklung leicht gemacht

JSF – Überblick und Einsatzzweck
Kommentare

Der überwiegende Teil von Enterprise-Anwendungen ist mit einem grafischen Benutzerinterface ausgestattet, mit dem Anwendungsdaten visualisiert, Eingaben durch den Benutzer gemacht und Aktionen ausgelöst werden können. Häufig wird zu diesem Zweck ein Webbrowser verwendet, d. h. die Präsentation ist bspw. HTML-basiert und die Kommunikation zwischen Browser und Anwendung geschieht mittels HTTP.

Der Vorteil dieser Konstellation liegt u. a. darin, dass der Anwender keinerlei Softwareinstallation über die Bereitstellung eines Webbrowsers hinaus durchführen muss. Im Gegenzug muss die Anwendung die Aufbereitung des zur Präsentation genutzten HTML-Texts übernehmen und die vom Browser ausgelösten HTTP-Requests verarbeiten.

Technisch lässt sich diese Anforderung mit Webanwendungen und den darin enthaltenen Servlets realisieren. Der nächste Abschnitt geht auf diese Anwendungsbasis im Überblick ein. Der Aufbau einer kompletten Anwendung ausschließlich mit Servlets wäre allerdings sehr aufwändig. Hier wird eher eine Anwendungsschicht mit einem höheren Abstraktionsgrad gebraucht, die in geeigneter Weise die Präsentation in HTML mit Daten und Methoden von Java-Objekten verbindet und eine Verknüpfung der Views der Anwendung untereinander ermöglicht. Der Java-EE-Standard bietet dazu JavaServer Faces – kurz JSF – an. Anwendungen lassen sich damit aus HTML-ähnlichen Seitenbeschreibungen aufbauen, die Daten und Logik von Java-Objekten verwenden und referenzieren.

Die Basis: Java-Webanwendungen

Grundlegender Aufbau

Webanwendungen bestehen zunächst aus einer Menge von Dokumenten, die im Browser zur Anzeige gebracht werden sollen. Es können HTML-Dokumente sein oder auch Grafiken, PDF-Dateien etc. Sie befinden sich im Startverzeichnis der Anwendung oder in passend benannten Unterverzeichnissen.

Der statische Teil der Webanwendung wird ergänzt um kompilierte Java-Klassen und Ressource-Dateien. Sie finden im Verzeichnis WEB-INF/classes Platz oder im Fall von Bibliotheken in WEB-INF/lib. Schließlich wird die Anwendung mit einem Deployment Descriptor namens web.xml und ggf. weiteren Parameterdateien im Verzeichnis WEB-INF konfiguriert.

Abb. 5.1: Aufbau einer Webanwendung

Ein Einstiegsbeispiel zeigt Abb. 5.1: Im willkürlich benannten Startverzeichnis helloWar und dem Unterverzeichnis images befinden sich einige statische Dokumente. WEB-INF enthält den Deployment Descriptor, eine kompilierte Klasse sowie eine Bibliothek.

Webanwendungen werden zum Deployment auf einem geeigneten Server mit jar gepackt und erhalten dabei die Dateiendung .war. Das Deployment-Verfahren ist abhängig vom Server. In vielen Fällen reicht es, eine Kopie der war-Datei in ein dafür bestimmtes Verzeichnis zu speichern.

Der Name der Deployment-Datei ohne ihre Endung bestimmt den sog. Web Context, der Teil der URL zur Adressierung der Anwendungsteile wird. Würde obiges Beispiel als Datei helloWar.war auf einem lokalen GlassFish- oder JBoss-Server deployt, müsste man im Browser die Adresse http://localhost:8080/helloWar/hello.html nutzen, um die entsprechende Webseite angezeigt zu bekommen.

Servlets

Die gezeigte Struktur wird allerdings erst dann zu einer richtigen Anwendung, wenn sie dynamische Anteile enthält, d. h. Java-Klassen, deren Methoden während der Request-Verarbeitung aufgerufen werden, sodass zu diesem Zeitpunkt dynamischer Inhalt erzeugt und im Browser angezeigt werden kann. Diese Aufgabe übernehmen Servlets. Sie werden im Folgenden skizziert, allerdings ohne auf Details einzugehen. Diese können in der Servlet-Spezifikation nachgelesen werden.

Unter Servlets versteht man Klassen, die i. d. R. von HttpServlet abgeleitet werden. Ihre Methode doGet wird zur Verarbeitung eines HTTP-GET-Requests aufgerufen. Ähnliche Methoden sind auch für die anderen HTTP-Verben (POST etc.) vorgesehen. Die beiden Parameter vom Typ HttpServletRequest und HttpServletResponse stellen Ein- und Ausgabe des Servlets dar: Die jeweilige Methode verarbeitet die Request-Parameter und füllt das Response-Objekt u. a. mit dem Text, den der Browser schließlich anzeigen soll (Listing 5.1).

public class HelloServlet extends HttpServlet
{
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    resp.setContentType("text/html");   // Response ist HTML
    PrintWriter out = resp.getWriter();
    out.println("<html>");              // Anzeigetext ausgeben
    out.println("<body>");
    out.println("  Hallo, Welt!");
    out.println("</body>");
    out.println("</html>");
    out.close();
  }
}

Eines fehlt allerdings noch: Die Klasse muss als Servlet registriert und mit einem URL verknüpft werden. Dies ist mithilfe der Annotation @WebServlet möglich (Listing 5.2) oder alternativ durch Einträge im Descriptor web.xml (Listing 5.3).

@WebServlet(urlPatterns = "/helloServlet")
public class HelloServlet extends HttpServlet
{
  …

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
  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_3_1.xsd">
  <servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>de.gedoplan.….HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
    <url-pattern>/helloServlet</url-pattern>
  </servlet-mapping>
</web-app>

Ein Servlet kann wie im Listing gezeigt mit einem exakten Pfad in der Webanwendung verknüpft werden: Das URL-Pattern /helloServlet bewirkt, dass das Servlet in unserer Beispielanwendung unter der URL http://localhost:8080/helloWar/helloServlet aufrufbar ist. Alternativ sind Verknüpfungen mit Verzeichnissen (/somePath/*) oder Endungen (*.xhtml) möglich. Einem Servlet können mehrere URL-Patterns zugeordnet werden.

JavaServer Pages

Es stellte sich in der Vergangenheit schnell heraus, dass Servlets zwar ein mächtiges Werkzeug für die dynamische Request-Verarbeitung sind, die Gestaltung von Webseiten damit aber recht mühsam ist. Schließlich muss der gesamte HTML-Text mithilfe von Ausgabeanweisungen im Servlet erstellt werden.

Abhilfe verspricht die Umkehrung der Vorgehensweise: Statt im Java-Code HTML-Text auszugeben, gestaltet man eine HTML-Seite mit speziellen Tags, die Java-Code enthalten. Der Java-EE-Standard bietet dazu JavaServer Pages – kurz JSP – an. Sie werden in Webanwendungen wie statische Dokumente integriert, allerdings mit der Dateiendung .jsp statt .html. Zur Laufzeit der Anwendung werden JSPs spätestens bei der ersten Benutzung in äquivalente Servlets übersetzt.

<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <body>
    <h2>"Hallo, Welt!"</h2>
    <br/>
    Dies ist eine JSP. Es ist <%= new java.util.Date() %>
  </body>
</html>


Wird das in Listing 5.4 gezeigte Beispiel als hello.jsp in unsere Beispielanwendung integriert, so führt der URL http://localhost:8080/helloWar/hello.jsp zur Anzeige einer Seite im Browser, in der im Text der Zeitpunkt des Requests eingeflochten ist. Dies geschieht durch den im Tag <%= new java.util.Date() %> enthaltenen Java-Code. JSP kennt diverse weitere Tags zur Integration von Deklarationen und Anweisungen, die für die weitere Betrachtung aber unerheblich sind. Bei Interesse finden Sie die Details in der JSP-Spezifikation.

JSPs verfolgen zwar durch die beschriebene Umkehr der Vorgehensweise den richtigen Ansatz, bescheren dem Entwickler aber neue Probleme: Zum einen wird der in JSPs enthaltene Java-Code zur Laufzeit der Anwendung kompiliert, Übersetzungsfehler treten somit erst dann auf. Zudem beziehen sich die Fehlermeldungen nicht direkt auf das JSP-Dokument, sondern auf einen vom Server temporär daraus erstellten Java-Quelltext. Zum anderen ist die Gefahr sehr groß, durch die Mischung von HTML- und Java-Code unübersichtliche Programme zu schreiben. Viele reale Projekte sahen sich dadurch am Ende mit praktisch unwartbarem Code konfrontiert.

Eine Lösung dieser Problematik besteht darin, Java-Code nicht mehr direkt in die Webdokumente zu integrieren, sondern ihn in Bibliotheken auszulagern und mit speziellen Tags zu referenzieren. Somit entsteht auf der Seite der Webdokumente eine erweiterte, HTML-ähnliche Sprache, was sich mithilfe von XML und Namespaces gut und ohne konzeptionellen Bruch realisieren lässt. Die andere Seite des referenzierten Java-Codes ist nun in normalen Bibliotheken enthalten, die mit herkömmlichen Mitteln im normalen Build-System erstellt werden können. Eine Ausprägung dieser Vorgehensweise sind die weiter unten beschriebenen Facelets, die in JavaServer Faces als präferierte View-Beschreibung eingesetzt werden.

Aufmacherbild: Enterprise road sign with fountain pen pillar von Shutterstock / Urheberrecht: JJ Studio

[header = Seite 2: JSF im Überblick]

JSF im Überblick

Model View Controller

Model View Controller oder auch kurz MVC ist ein Architekturmuster für die Softwareentwicklung. Es trennt drei Aspekte der Software und weist ihnen klare Gestaltungsarten zu:

  • Das Model enthält die bearbeiteten Daten. Die Geschäftslogik ist hier mit enthalten oder wird von hier aufgerufen.
  • Die View dient der Darstellung der Daten. Sie übernimmt auch die Interaktion mit dem Benutzer, also insbesondere die Annahme von Benutzeraktionen wie Eingaben und Kommandos, ist aber nicht für die eigentliche Verarbeitung zuständig.
  • Der Controller verwaltet Views und Models und verknüpft dabei insbesondere die von den Views gelieferten Benutzerkommandos mit den Daten und der Logik der Models.

Das Ziel von MVC ist eine höhere Flexibilität für Änderungen und Erweiterungen und eine größere Wiederverwendbarkeit der einzelnen Komponenten. Das Konzept wurde am Ende der 1970er Jahre zunächst für Benutzeroberflächen in Smalltalk beschrieben (Abb. 5.2).

Abb. 5.2: Model View Controller

Im Fall von JavaServer Faces kommt MVC in der folgenden Ausprägung zum Einsatz: Der Controller wird durch das Faces Servlet implementiert. Die Views sind Facelets, d. h. XHTML-Dokumente, die einige besondere Tag-Bibliotheken verwenden. In JSF können auch andere View-Technologien zum Einsatz kommen, worauf aber hier nicht weiter eingegangen werden soll. Die Models schließlich werden als POJOs (Plain Old Java Objects) bereitgestellt, d. h. als einfache Java-Objekte.

Alle Anwendungs-Requests werden vom Faces Servlet behandelt. Aufgrund des Status der Anwendung wird entschieden, welche Beans und Views verwendet werden sollen. Die Antwort wird schließlich von einer View erzeugt und im Browser angezeigt (Abb. 5.3).


Abb. 5.3: MVC in JSF

Facelets

Die Sprache zur Seitenbeschreibung ist in der JSF-Spezifikation nicht festgelegt. Jede Implementierung muss aber zumindest Facelets und JSP unterstützen. Seit JSF 2.0 werden  Facelets präferiert. Das sind XHTML-Dokumente entsprechend der Definition des W3C. Sie können um Tags aus weiteren Namensräumen ergänzt werden, um die spezielle Funktionalität von Facelets zu nutzen:

<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:h="http://xmlns.jcp.org/jsf/html">
<head>
  <title>First Facelet</title>
</head>
<body>
  <h:outputText value="Hello World!" />
  <br/>
  <h:outputText value="Ihr Browser: #{header['User-Agent']}" />
</body>
</html>

Ein Einstiegsbeispiel für ein Facelet zeigt Listing 5.5. Es benutzt das Tag <h:outputText> aus der HTML-Bibliothek zur Ausgabe von Text. Der Ausdruck #{…} stammt aus der JSF Expression Language – kurz JSF-EL – und wird zur Request-Zeit ausgewertet. Die weiteren Abschnitte dieses Kapitels gehen genauer auf die verfügbaren Tags und die JSF-EL ein.

Request-Verarbeitung

Wie oben dargestellt, werden Anwendungs-Requests vom zentralen Faces Servlet angenommen. Die Verarbeitung geschieht dann im sog. Request Processing Lifecycle. Dieser Lebenszyklus umfasst sechs Phasen von der Initialisierung der Verarbeitung über die Annahme von Request-Parametern bis schließlich zum Rendern der Antwort (Abb. 5.4).

Abb. 5.4: Request Processing Lifecyvcle

An die Phasen schließt sich meist eine Event-Verarbeitung an. Hierin kann man mit verschiedenen Event Listenern auf Zustandswechsel o. ä. reagieren.

Beim Auftreten von Verarbeitungsfehlern (Validierungsfehler etc.) wird die Request-Verarbeitung direkt mit der letzten Phase zur Anzeige der View abgeschlossen. Dies kann in der Event-Verarbeitung auch programmatisch ausgelöst werden.

Während der Event-Verarbeitung kann zudem auf das Rendern einer Antwort komplett verzichtet werden (in der Abbildung nicht dargestellt). Dies ist bspw. der Fall, wenn binäre Daten als Antwort an den Client gesendet werden, z. B. Bilder oder PDF-Dokumente.

In der Phase 1 (Restore View) wird die interne Darstellung der aktuellen View hergestellt. Darunter versteht man eine baumartige Objektstruktur, die den ineinander verschachtelten Komponenten der View entspricht. Wird eine View zum ersten Mal benutzt, existiert dieser interne Komponentenbaum noch nicht. Dann wird er entsprechend der Struktur der Seite aufgebaut. Die restlichen Verarbeitungsschritte bis auf die Rendering-Phase entfallen dann. Bei Folgeaufrufen wird der Komponentenbaum aus dem gespeicherten Status heraus wieder hergestellt und der komplette Lebenszyklus durchlaufen.

Der Komponentenbaum einer View wird zwischen zwei Requests gespeichert. Die Ablage geschieht i. d. R. serverseitig in der Session, kann aber auch clientseitig mithilfe von versteckten Feldern in den Views geschehen.

Einige Komponenten lassen die Eingabe von Werten zu (Texte, Listenauswahl, …). Die Eingabewerte werden im Request als Parameter mitgeliefert. Die Phase 2 (Apply Request Values) übernimmt die Eingabewerte als sog. Submitted Values in die zugehörigen Objekte des Komponentenbaums. Die alten Werte der Komponenten werden nicht verändert, um einen späteren Vergleich noch zu ermöglichen.

In der Phase 3 (Process Validations) werden die übermittelten Werte in den gewünschten Zieldatentyp konvertiert und auf Gültigkeit geprüft. Einige Komponenten haben implizite Konverter und Validatoren, weitere können bei Bedarf registriert werden. Verlaufen Konvertierung und Validierung positiv, werden die Eingabewerte endgültig in den Komponenten abgespeichert. Ergibt sich dabei eine Werteänderung, wird ein entsprechender Event ausgelöst. Bei Konvertierungs- oder Validierungsfehlern werden die Phasen 4 und 5 übersprungen und die Request-Bearbeitung wird mit der letzten Phase fortgesetzt.

In den bisherigen Phasen waren nur die Objekte des Komponentenbaums beteiligt. In der Phase 4 (Update Model Values) werden nun die Werte von dort in die assoziierten Model-Objekte kopiert. Durch die bisherige Verarbeitung ist sichergestellt, dass die Werte valide sind.

Einige Komponenten haben einen natürlichen Aktionscharakter, z. B. Buttons oder Links. Ihnen werden normalerweise Methoden zugeordnet, die die entsprechende Anwendungslogik enthalten. Sie werden in der Phase 5 (Invoke Application) aufgerufen, wenn der Request von einem solchen Aktionselement ausgelöst wurde. Anschließend findet die Navigation in der Anwendung statt, d. h. die Festlegung der als Nächstes anzuzeigenden View.

Als Abschluss der Request-Bearbeitung wird in der Phase 6 (Render Response) der anzuzeigende Inhalt erzeugt. In dieser Phase wird zudem der Komponentenbaum für weitere Anfragen der gleichen View abgespeichert.

Der dargestellt Lebenszyklus kann über die Attribute der beteiligten Komponenten noch partiell modifiziert werden. Darauf gehen die nachfolgenden Abschnitte 5.18 (Immediate-Komponenten) und 5.19 (A) noch ein.


Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -