Vergleich des Scala Web-Frameworks Lift mit dem Java EE Programmiermodell
1. Bachelorthesis zur Erlangung des akademischen Grades
Bachelor of Science in Wirtschaftsinformatik
Vergleich des Scala
Web-Frameworks Lift mit dem
Java EE Programmiermodell
Bachelorthesis im Fachbereich Wirtschaftswissenschaften II
im Studiengang Wirtschaftsinformatik
der Hochschule für Technik und Wirtschaft Berlin
in Zusammenarbeit mit der adesso AG
vorgelegt von: Felix Müller
Matrikelnummer: 524063
eingereicht am: 9. Juni 2011
Erstbetreuer: Prof. Dr. Ingo Claßen
Zweitbetreuer: Prof. Dr. Harald Brandenburg
Betrieblicher Betreuer: Eberhard Wolff
9. Abkürzungsverzeichnis
AOP Aspektorientierte Programmierung
API Application Programming Interface
CRUD Create, Read, Update, Delete
DAO Data Access Object
DI Dependency Injection
DSL Domain Specific Language
HTML HyperText Markup Language
IDE Integrated Development Environment
JCP Java Community Process
JSON JavaScript Object Notation
JSR Java Specification Request
XHTML eXtensible HyperText Markup Language
XML Extensible Markup Language
VIII
10. 1 Einleitung
1.1 Motivation
Das Entwickeln von Anwendungen für das World Wide Web gewinnt stetig an
Bedeutung. Bereits heutzutage verdrängen Web-Anwendungen (z.B. Google Docs)
einzelne Desktop-Anwendungen. Auf mobilen Geräten sind Web-Anwendungen
bereits eine wirkliche Konkurrenz zu den nativen Applikationen. Daher ist es
interessant und wichtig, Frameworks, die das Entwickeln von Web-Anwendungen
erleichtern und verallgemeinern, auf die tatsächliche Einsatztauglichkeit zu prüfen
und zu testen.
Auf der Hauptseite des Lift Web-Frameworks wird Lift als das derzeit mächtigs-
te Framework für die Entwicklung von Web-Anwendungen bezeichnet.1 Mit der
Programmiersprache Scala setzt Lift zudem eine neue Sprache für die JVM ein
und zeigt damit gleichzeitig den Einsatz dieser neuen Sprache in einem größeren
Projekt. Aufgrunddessen wird das Lift Web-Framework in dieser Arbeit mit dem
etablierten Java EE Programmiermodell verglichen.
1.2 Zielsetzung der Arbeit
Ziel der Arbeit ist es, durch den Vergleich des Lift Web-Frameworks mit dem Java
EE Programmiermodell Antworten auf mehrere Fragestellungen zu erhalten, die
folgend zusammengefasst dargelegt werden.
1
siehe http://liftweb.net
1
11. 1.3 Abgrenzung zu anderen Arbeiten
Lift soll aus möglichst vielen Blickwinkeln betrachtet werden, damit zum Ende
der Arbeit die Vor- und Nachteile des Frameworks herausgearbeitet sind. Es gilt
außerdem zu untersuchen, was die besten Einsatzbereiche des Lift Frameworks
sind. Das Hauptziel ist dabei, Lift im Vergleich zum Java EE Programmiermodell
zu bewerten, da dieses einen der am häufigsten eingesetzten Standards in der
Web-Entwicklung darstellt. Außerdem setzt Lift auf die Java EE Plattform auf,
wie später noch erklärt wird, und tritt damit in mögliche Konkurrenz zum Java
EE Programmiermodell.
1.3 Abgrenzung zu anderen Arbeiten
Anders als in bisherigen Arbeiten wird Scala in dieser Arbeit nicht auf die Integration
mit bestehenden Java Anwendungen geprüft ([Kal10]) oder die Entwicklung einer
Web-Anwendung mit Scala und Lift ([TF11]) gezeigt. Im Mittelpunkt dieser Arbeit
steht der Vergleich von Scala und Lift als Technologiestack mit dem Java EE
Programmiermodell. Natürlich wurde dafür eine Beispielanwendung jeweils mit
Scala/Lift und Java EE entwickelt, was auch entsprechend ausführlich erläutert
wird. Ebenso wird an einigen Stellen kurz die Integration mit Java angesprochen.
Aber dies geschieht dann zwecks des Vergleichs und der Bewertung.
1.4 Aufbau der Arbeit
Zu Beginn der Arbeit werden die Grundlagen der verwendeten Konzepte und
eingesetzten Technologien erklärt. Dabei erfolgt eine Einführung in die Program-
miersprache Scala. Allerdings kann keine vollständige Sprachdefinition gegeben
werden. Alle Sprachmittel, die zum Verständnis der Arbeit nötig sind, werden
gezeigt und erläutert. Außerdem wird davon ausgegangen, dass der Leser dieser
Arbeit bereits mit der Sprache Java entwickelt, sodass eine Einführung in die
eingesetzten Java EE Techniken für das Verständnis ausreicht.
2
12. 1.4 Aufbau der Arbeit
Danach wird die Methodik der Evaluation beschrieben. Dabei werden die ver-
wendeten Methoden erläutert und die Evaluationskriterien vorgestellt. Da für
den Vergleich der beiden Technologiestacks jeweils eine Beispielanwendung ent-
wickelt wurde, folgt im Kapitel 4 die Darstellung der Entwicklung. Dabei wird
die Umsetzung der Prototypen anhand von Programmcodeauszügen erläutert. An-
schließend wird die Evaluation durchgeführt. Dafür werden die zuvor festgelegten
Evaluationskriterien verwendet.
Zum Abschluss wird das Ergebnis der Arbeit im Fazit zusammengefasst und ein
Ausblick gegeben, indem weitere untersuchenswerte Themen benannt werden, die
durch die Arbeit entstanden sind.
Die entwickelten Beispielanwendungen liegen dieser Arbeit auf einer CD bei, deren
Inhalt im Anhang kurz beschrieben wird.
3
13. 2 Grundlagen
2.1 Ajax
Die Begrifflichkeit Ajax ist heutzutage ein gängiges Schlagwort bei der Entwicklung
von Web-Anwendungen. Damit wird ein Konzept bezeichnet, dass unter Verwendung
gängiger Technologien die asynchrone Übertragung von Daten zwischen Server und
Client, meistens dem Browser, ermöglicht. Ajax steht dabei für „Asynchronous
JavaScript and XML“.1
Web-Anwendungen, in denen Ajax eingesetzt wird, verhalten sich oftmals ähnlich
einer Desktop-Anwendung. Die Webseiten reagieren auf Interaktionen des Nut-
zers flüssiger. Dies liegt daran, dass die Webseiten beim Einsatz von Ajax nicht
komplett neu geladen werden. Vielmehr werden sukzessive einzelne Seitenbereiche
nachgeladen. Damit dies funktioniert, wird auf Seiten des Clients eine Zwischen-
schicht eingebaut, die Ajax Engine. Diese Engine ist üblicherweise eine existierende
JavaScript-Bibliothek, die die asynchrone Datenübetragung steuert.
Eine Anfrage des Clients wird dabei nicht direkt an den Server gesendet, sondern
über eine Ajax Engine mittels XML, wie in Abbildung 2.1 zu sehen ist.2 Während
der Anfragebearbeitung auf dem Server kann der Client somit noch auf Nutzerin-
teraktionen reagieren. Durch die Ajax Engine muss die Server-Antwort nur die
notwendigen Daten zur Änderung der Webseite enthalten, die anschließend von der
Ajax Engine zur Seitenaktualisierung genutzt werden.3
1
Vgl. [Wik]
2
Neben XML ist auch die Verwendung von HTML oder JSON häufig anzutreffen.
3
siehe [MM10, S. 230 ff.]
4
14. 2.2 Java EE
Abb. 2.1: Prozessfluss des Ajax Modells von [Wik]
2.2 Java EE
Die Java Platform Enterprise Edition (Java EE) gilt umgangssprachlich als das
Java für Geschäftsanwendungen. Da in diesem Zusammenhang oft unklar ist, was
Java EE wirklich ist und wie es sich in das bestehende Java Ökosystem eingliedert,
erfolgt eine Einordnung von Java EE.
Oftmals wird nicht zwischen Java als Sprache und Java als Plattform unterschie-
den. Wobei mit Java SE, der Java Platform Standard Edition, durchaus diese
Unterscheidung vorliegt. Java SE definiert eine Plattform, die sich aus der Java
Standardbibliothek, der Java Virtual Machine und den Entwicklungstools (z.B. der
Java Compiler) zusammensetzt. Hinzu kommen viele Bibliotheken von Drittanbie-
tern. Die Java Standardbibliothek implementiert dabei die Kernfunktionalitäten
der Programmiersprache Java und liefert eine Fülle an bereits implementierten
Klassen. Darunter sind zum Beispiel Klassen für die Entwicklung grafischer Benut-
zeroberflächen, für die Kommunikation über das Netzwerk oder auch Klassen zum
5
15. 2.2 Java EE
Parsen von XML-Dokumenten.4
„Die Java Platform, Enterprise Edition (Java EE) ist ein Aufsatz für die Java SE
und integriert Pakete, die zur Entwicklung von Geschäftsanwendungen (Enterprise-
Applikationen genannt) nötig sind.“5 Diese Beschreibung fasst es gut zusammen.
Zusätzlich ist bei Java EE zwischen der Plattform und dem Programmiermodell zu
unterscheiden. Unter Java EE als Plattform werden in dieser Arbeit die Komponen-
ten der Laufzeitumgebung für Java Enterprise-Applikationen und die dafür nötigen
Technologien verstanden. So werden z.B. Anwendungen, die mit Java EE entwickelt
wurden, üblicherweise in einem Application Server oder Webcontainer installiert,
die als Laufzeitumgebung dienen. Java EE als Programmiermodell definiert Pro-
grammierschnittstellen (APIs) und Frameworks, mit deren Nutzung mehrschichtige,
lose gekoppelte und skalierbare Geschäftsanwendungen entwickelt werden.
Java EE besteht dabei aus einer Vielzahl an Spezifikationen bzw. Standards, die
durch den JCP erarbeitet werden. JCP steht für den Java Community Process
und bezeichnet einen Zusammenschluss von mehreren Firmen, Vereinigungen und
Einzelpersonen, die in einem vorgeschriebenen Verfahren Spezifikationen für die
Java Technologie erarbeiten. Diese Spezifikationen werden Java Specification Re-
quests (JSR) genannt. Dabei ist es üblich, dass durch eine Spezifikation lediglich
die abstrakte Programmierschnittstelle definiert wird und die konkreten Implemen-
tierungen dafür durch mehrere Anbieter zur Verfügung gestellt werden. Dadurch ist
der Entwickler nicht von einem Hersteller abhängig und kann die Implementierung
jederzeit austauschen.6
In den folgenden Abschnitten wird auf die in der Arbeit verwendeten Spezifikationen
des Java EE Programmiermodells eingegangen.
4
Vgl. [EJ10, Kapitel 2]
5
[Ull11, Kapitel 1.4.4]
6
siehe [Sta06, S. 22]
6
16. 2.2 Java EE
2.2.1 Java Persistence API
Der Standard für die Persistenzabbildung in Java EE wurde mit JPA im JSR 317
spezifiziert. Die Java Persistence API ist dabei zusammen mit EJB 3.0 im JSR 220
entstanden.7 Zwar ist JPA eine Java EE Spezifikation, es kann aber genauso in
Java SE Anwendungen eingesetzt werden.8
JPA definiert unter Nutzung eines objektrelationalen Mappers das Persistieren
von Java Objekten in einer relationalen Datenbank. Dabei wird das Data Mapper
Pattern umgesetzt. Das Data Mapper Pattern ist ein Entwurfsmuster zur Separie-
rung von Objekten und der Abbildung dieser in Datenbanktabellen. Die Objekte
haben im Data Mapper Pattern keine direkte Kenntniss von der Datenbank. Die
Abbildung in den Tabellen wird durch einen korrespondierenden Mapper geregelt.9
Die Grundkonzepte von JPA sind Persistent Entities, der Entity Manager, der
Persistence Context, die Persistence Unit und der JPA Provider.
Der zentrale Bestandteil von JPA sind die Persistent Entities.10 Damit werden
einfache Java-Objekte (Plain Old Java Objects, abgekürzt POJOs) bezeichnet, die
über die Entity-Annotation durch JPA persistierbar werden.11 Als POJO wird ein
Java Objekt bezeichnet, dass keine externen Abhängigkeiten zu anderen Schnitt-
stellen oder Frameworks hat und somit keiner Konvention, z.B. hinsichtlich der
Benennung von Klassen oder Methoden, unterworfen ist.12 Sämtliche Informationen
für die Abbildung in der relationalen Datenbank werden in der Persistent Entity
über Annotationen angegeben. Alternativ können diese Informationen in Form von
Deployment Deskriptoren beschrieben werden, was dem Vorgehen vor der Nutzung
von Annotationen entspricht. Ein Deployment Deskriptor ist ein XML Dokument,
7
EJB (Enterprise Java Beans) beschreibt ein Programmiermodell zur Entwicklung von Java
Komponenten, wobei dem Entwickler die Implementierung von infrastrukturellen Code, z.B.
zur Transaktionssteuerung oder Lastverteilung, abgenommen wird. (Vgl. [OI07, S. 32])
8
siehe [Wol10, S. 189]
9
Vgl. [Fow03, S. 165 ff.]
10
In der Arbeit werden für die Persistent Entities auch Synonyme wie JPA Fachklassen oder JPA
Entity verwendet.
11
Vgl. [OI07, S. 187 f.]
12
siehe [Fow]
7
17. 2.2 Java EE
das unteranderem Meta-Informationen für Persistent Entities enthalten kann.13
Das Speichern, Löschen, Laden und Suchen von Persistent Entities in einer Daten-
bank übernimmt ein so genannter Entity Manager. Dabei werden die Persistent
Entities von einem Entity Manager verwaltet. Mit dem Begriff Persistent Unit
werden dabei alle vom Entity Manager verwalteten Klassen bezeichnet. Im Ge-
gensatz dazu werden alle vom Manager verwalteten Entities, also die Objekte der
Klassen einer Persistent Unit, unter dem Begriff Persistence Context zusammenge-
fasst. Dabei unterliegen die Persistent Entities einem Lebenszyklus, der durch die
Verwendung des Entity Managers bestimmt wird, wie Abbildung 2.2 zeigt.14
Abb. 2.2: Lebenszyklus einer Persistent Entity aus [Obj]
Wurde eine Persistent Entity mit dem new-Operator instanziiert, befindet sie
sich im Zustand New. Zu dem Zeitpunkt ist die Entity nicht in der Datenbank
abgebildet. Durch die persist- oder merge-Operation des Entity Managers kann
die Persistent Entity dem Persistence Context hinzugefügt werden. Dann wird die
Entity vom Entity Manager verwaltet und ist im so genannten Managed Zustand.
Gleichzeitig wird die Persistent Entity dabei in der Datenbank abgebildet. Entweder
werden neue Datenbankeinträge angelegt oder bestehende aktualisiert. Je nach
Konfiguration kann das auch erst beim Commit einer Transaktion oder durch den
expliziten Aufruf der flush-Methode erfolgen. Dadurch dass die Persistent Entities
13
siehe [OI07, S. 189 f.]
14
Vgl. [OI07, S. 194 ff.]
8
18. 2.2 Java EE
im Zustand Managed sind, werden Änderungen der Attributswerte vom Entity
Manager registriert und mit der Datenbank synchronisiert. Mit der find-Methode
des Entity Managers können Persistent Entities aus der Datenbank geladen werden.
Diese sind dann ebenfalls im Managed Zustand. Sollen Entities in der Datenbank
gelöscht werden, genügt es, die remove-Methode vom Entity Manager aufzurufen.
Dafür müssen die Entities jedoch im Managed Zustand sein. Wird der Entity
Manager geschlossen, werden sämtliche Persistent Entities entkoppelt und gelangen
in den Detached Zustand.
Für die Abfrage von Persistent Entities aus der Datenbank wird in JPA eine
Abfragesprache definiert, die Java Persistence Query Language (JPQL). Die JPQL
ist unabhängig von der Persistenzschicht. Das bedeutet, die Abfragen werden nicht
in SQL formuliert, sondern auf Ebene der Objekte. Diese Abfragesprache ist jedoch
SQL-ähnlich bei der Formulierung von Abfragen. Die formulierten JPQL Abfragen
werden über den Entity Manager ausgeführt.15
Da JPA lediglich eine Programmierschnittstelle spezifiziert, muss für die Ver-
wendung eine Implementierung der Schnittstellen eingebunden werden. Solche
Implementierungen werden JPA Provider genannt. Für JPA in der Version 2.0 ist
das Projekt EclipseLink16 die Referenzimplementierung eines JPA Providers.17 In
dieser Arbeit wird jedoch Hibernate als JPA Provider eingesetzt.18
Konfiguriert wird ein JPA Provider über den Deployment Deskriptor in der Datei
persistence.xml. In der persistence.xml werden die Persistence Units definiert
und der Entity Manager konfiguriert. So wird dort z.B. eingestellt, zu welchem
Zeitpunkt Persistent Entities in die Datenbank geschrieben werden oder welche
Caching Methode verwendet wird, um die Performance der Persistenzschicht zu
verbessern.19
15
Vgl. [OI07, S. 300 ff.]
16
siehe http://www.eclipse.org/eclipselink
17
siehe dazu http://www.eclipse.org/org/press-release/20080317_Eclipselink.php
18
siehe http://www.hibernate.org
19
Vgl. [OI07, S. 200 ff.]
9
19. 2.2 Java EE
2.2.2 Bean Validation
Der JSR 303 (Bean Validation) definiert für Java einen Standard zur Validierung von
Objekten. Dabei werden Klassen und deren Attribute mit Annotationen versehen,
die die Informationen zur Validierung enthalten. Einige Annotationen werden durch
den Bean Validation Standard bereits vorgegeben. Mit der Size-Annotation kann
beispielsweise die minimale und maximale Größe von Zeichenketten, Arrays und
Listen festgelegt werden. Möglichkeiten zur Formulierung eigener Annotationen
sind ebenso gegeben.20
Dadurch dass die Validierungsregeln deklarativ mit Annotationen hinterlegt wer-
den, ist die Ausführung der Validierung entkoppelt. Diese kann somit an mehreren
Stellen in der Anwendung durchgeführt werden, z.B. im Webframework bei der
Eingabe von Daten in Formularen oder in der Persistenzschicht beim Speichern
von Anwendungsdaten. Der JPA Provider Hibernate bietet mit der Referenzim-
plementierung des JSR 303, Hibernate Validator, unteranderem die Möglichkeit,
die mit Bean-Validation-Annotationen hinterlegten Validierungsregeln im Entity
Manager einzusetzen und somit mit JPA zu vereinen.21
2.2.3 Dependency Injection for Java
Wenn Anwendungen in Java entwickelt werden, bestehen diese aus vielen Klassen,
die sich gegenseitig nutzen, um die erforderten Funktionalitäten umzusetzen. Die
Klassen haben dadurch häufig mehrere Abhängigkeiten zu anderen Klassen. Diese
müssen aufgelöst werden. Das geschieht, indem Objekte von abhängigen Klassen am
Ort ihrer Nutzung erzeugt werden oder bestenfalls bei der Erzeugung von Klassen
über den Konstruktor reingereicht werden. Das Problem bei diesem Vorgehen ist die
enge Kopplung der Klassen untereinander und die Unflexibilität bei Änderungen,
z.B. beim Austausch einzelner Klassen für Testzwecke. Soll die Implementierung
einer Klasse ausgetauscht werden, müssen überall im Programmcode Änderungen
erfolgen. Eine Lösung für dieses Problem ist Dependency Injection. Dabei werden
20
Vgl. [Wol10, S. 250 ff.]
21
siehe http://www.hibernate.org/subprojects/validator.html
10
20. 2.2 Java EE
abhängige Objekte zur Laufzeit injiziert. Das Objekt, dessen Abhängigkeiten
aufgelöst werden, ist passiv bei diesem Vorgang. Das hat den Vorteil, dass zum
einen große Objektnetze einfach erzeugt werden können, da die Objekte ihre
Abhängigkeiten injiziert bekommen, und zum anderen ist der Austausch von
Implementierungen darüber sehr leicht möglich.22
Mit dem JSR 330, Dependency Injection for Java, werden im Paket javax.inject
Annotationen zur Verfügung gestellt, die die Nutzung und Implementierung von
DI in Java standardisieren. Das Versehen von Attributen, Konstruktoren oder
Methoden mit der Inject-Annotation sorgt dafür, dass die abhängigen Objekte
an dieser Stelle injiziert werden. Um die Dependency Injection feiner zu steuern,
können die Qualifier- und Named-Annotationen verwendet werden.23 In der Arbeit
wird ausschließlich die Inject-Annotation genutzt.
Genau genommen zählt der JSR 330 nicht zu den Java EE Spezifikationen. Aller-
dings ermöglicht die Spezifikation das Bauen von lose gekoppelten Systemen in der
gesamten Java Technologie, wovon Java EE eine Teilmenge ist.
2.2.4 JavaServer Faces
JavaServer Faces (JSF) ist ein komponentenbasiertes Framework für die Entwicklung
von Web-Anwendungen.24 Die in der Arbeit verwendete Version 1.2 wird durch
den JSR 252 spezifiziert.25 Die Referenzimplementierung von JSF ist das Mojarra
Projekt, welches ebenfalls in dieser Arbeit eingesetzt wird.26
Das zugrundeliegende Entwurfsmuster von JSF ist das Model-View-Controller-
Muster (MVC). Durch das MVC-Entwurfsmuster sollen Anwendungen mit einer
grafischen Oberfläche besser strukturiert werden. Ziel ist es, die Bestandteile der
Anwendung wiederverwendbarer, erweiterbarer und wartbarer zu gestalten, indem
22
Vgl. [Wol10, S. 20]
23
Vgl. [Wol10, S. 81 ff.]
24
Vgl. [MM10, S. 6]
25
siehe http://www.jcp.org/en/jsr/detail?id=252
26
siehe http://javaserverfaces.java.net
11
21. 2.2 Java EE
die Daten (Model), die Darstellung (View) und das Verhalten (Controller) vonein-
ander getrennt implementiert werden.27 Der Einsatz des MVC-Entwurfsmusters in
JSF wird in Abbildung 2.3 gezeigt.
Abb. 2.3: MVC-Architektur von JSF aus [MM10]
Der Controller wird in JSF durch das Faces-Servlet implementiert. Dabei wird die
Servlet Technologie der Java EE Plattform verwendet. Servlets werden durch den
JSR 154 beschrieben.28 Für diese Arbeit genügt es zu wissen, dass Servlets den
ersten Versuch darstellten, dynamische Webseiten mit Java zu erzeugen. Servlet
Klassen können auf HTTP Anfragen reagieren und diese beantworten, indem sie
im Java Code z.B. über Streams HTML schreiben.29 In JSF wird die Servlet
Technologie nur intern verwendet. Das Faces-Servlet enthält die Steuerungslogik
einer JSF Anwendung. Dieses Servlet steuert aufgrund einer Anfrage des Clients
den Aufruf des benötigten Views und erzeugt die korrespondierenden Models.
JSF unterstützt bisher zwei verschiedene View-Technologien: Facelets und JSP
(JavaServer Pages). In dieser Arbeit wird Facelets verwendet. Der View wird bei
der Nutzung von Facelets durch XHTML-Dokumente implementiert, die spezifische
Tags aus der Facelets Tag Library enthalten. Eine Tag Library ist eine Sammlung
von XHTML-Tags, die über einen eigenen XML Namensraum (engl. Namespace)
27
Vgl. [Bos04, S. 45 ff.]
28
sieh http://www.jcp.org/en/jsr/detail?id=154
29
Vgl. [MM10, S. 2 ff.]
12
22. 2.2 Java EE
eindeutig identifizierbar sind. Außerdem kann die Darstellung bei Verwendung von
Facelets modular aufgebaut werden.30
Ein View bzw. eine Ansicht wird in JSF aus Komponenten zusammengesetzt. Kom-
ponenten sind eigenständige und wiederverwendbare Bausteine. Beim Erzeugen
eines Views wird in JSF ein Komponentenbaum erzeugt. Durch den Komponen-
tenbaum werden die einzelnen Komponenten einer Ansicht miteinander verknüpft.
Das Wurzelelement des Baums wird dabei durch ein Objekt der Klasse UIViewRoot
repräsentiert. Die Komponenten einer Ansicht werden als Kindelemente an das
Wurzelelement gehängt. Die Bearbeitung einer Anfrage, die sich in JSF in meh-
rere Phasen unterteilt, wie Abbildung 2.4 zeigt, beginnt immer mit einem Aufruf
einer Methode des UIViewRoot-Elements, das den Methodenaufruf rekursiv an die
Kindelemente weiterleitet. Über diesen Mechanismus kann jede Komponente einer
Ansicht auf eine Anfrage reagieren.31
Abb. 2.4: JSF Standard Request-Response-Zyklus aus [EJ10]
30
siehe [MM10, S. 157 f.]
31
Vgl. [MM10, S. 20 ff.]
13
23. 2.2 Java EE
Ein weiteres wichtiges Konzept stellen die Managed Beans dar, auch Backing Beans
genannt, über die das Model in JSF implementiert wird. Managed Beans sind
normale POJOs. Sie liefern die Werte für die im View definierten Komponenten.
Managed Beans können dabei verschiedene Gültigkeitsbereiche haben, die sich
in der Dauer der Lebenszeit eines Managed Bean Objekts unterscheiden. In JSF
1.2 ist dafür die Dauer der Requestbearbeitung, einer HTTP Session oder auch
die gesamte Dauer der Applikationsausführung möglich. Bevor ein normales Java
Objekt als Managed Bean gilt, muss ein Eintrag in der Konfiguration von JSF
erfolgen.32
Die Konfiguration von JSF wird in der faces-config.xml Datei formuliert. Außerdem
muss JSF in der web.xml Konfigurationsdatei eingerichtet werden. Damit eine
JSF Anwendung funktioniert, muss ein Servlet Container (auch Webcontainer
genannt) als Laufzeitumgebung genutzt werden.33 Ein solcher Servlet Container
nutzt die Informationen aus der web.xml Datei, um z.B. Servlets zu starten. Die
web.xml Datei ist ein Deployment Deskriptor für einen Servlet Container, eine
Bereitstellungsbeschreibung einer Anwendung. Das Faces-Servlet muss dort über
einen Eintrag hinterlegt werden.
§ Listing 2.1: Eintragung des Faces-Servlet in der web.xml ¤
< s e r v l e t>
<s e r v l e t −name>Faces S e r v l e t</ s e r v l e t −name>
<s e r v l e t −c l a s s>
j a v a x . f a c e s . webapp . F a c e s S e r v l e t
</ s e r v l e t −c l a s s>
<load−on−s t a r t u p>1</ load−on−s t a r t u p>
</ s e r v l e t>
¦ ¥
Eine JSF-Anwendung wird in Form einer WAR-Datei bereitgestellt. Mit dem
Web Application Archive (WAR) wird eine standardisierte Verzeichnisstruktur für
32
Das gilt für JSF 1.2. Im neueren JSF 2.0 Standard ist die Definition von Managed Beans über
Annotationen möglich.
33
Der Servlet Container war bereits in Abbildung 2.3 in Form der Servlet Engine dargestellt, die
ein Teil eines Servlet Containers ist.
14
24. 2.2 Java EE
Webanwendungen in Java EE definiert. So enthält beispielsweise das Verzeichnis
WEB-INF in einer WAR-Datei die Konfiguration und Seitenbeschreibungen ei-
ner Web-Anwendung. Als Archivierungsformat wird das bekannte JAR-Format
verwendet.
Unified Expression Language
Mit der Unified Expression Language werden die Komponenten der Ansicht und
die dahinterliegenden Managed Beans miteinander verbunden. Darüber können
im View Daten aus den Managed Beans gelesen und in diese geschrieben werden.
Weiterhin werden über Expression-Language-Ausdrücke (kurz EL-Ausdruck) Me-
thoden für die Ereignisbehandlung von Komponenten im View angegeben. Ein
EL-Ausdruck beginnt mit einer Raute und wird von geschweiften Klammern um-
schlossen. Zwischen den Klammern kann der Name einer Managed Bean stehen und
durch Punkte getrennt die Attribute der Managed Bean. Mit dem Ausdruck #{per-
son.vorname} wird beispielsweise auf das Attribut vorname der Managed Bean
person zugegriffen. Ebenfalls können einfache Operationen in einem EL-Ausdruck
untergebracht werden.34
Standard-Komponenten
JSF bietet eine Palette an Standard-Komponenten. Das sind bereits fertige JSF
Komponenten, die die Grundlage für die Entwicklung von JSF Anwendungen bilden.
Die Standard-Komponenten von JSF werden in den beiden Tag Libraries HTML-
Custom-Tag-Library und Core-Tag-Library implementiert und können durch das
Einbinden des jeweiligen XML Namespaces in einer View-Beschreibung genutzt
werden.35
Die HTML-Custom-Tag-Library im XML-Namensraum h stellt Komponenten zur
HTML-Ausgabe zur Verfügung. Die Core-Tag-Library mit dem XML-Namensraum
f vereint wiederum alle Basis-Komponenten, die für die Nutzung von JSF benötigt
34
Vgl. [MM10, S. 36 ff.]
35
Vgl. [MM10, S. 109]
15
25. 2.3 Programmiersprache Scala
werden. So wird beispielsweise mit dem f:view Tag der Core-Tag-Library der
Wurzelknoten eines Komponentenbaums in der Ansicht definiert.
Das Einbinden der Namensräume erfolgt bei der Deklaration des HTML-Tags in
der Seitenbeschreibung.
§ Listing 2.2: Einbinden der Namensräume der beiden Standard-Tag-Libraries ¤
<html xmlns=" h t t p : / /www. w3 . o r g /1999/ xhtml "
xmlns : h=" h t t p : / / j a v a . sun . com/ j s f / html "
xmlns : f=" h t t p : / / j a v a . sun . com/ j s f / c o r e ">
¦ ¥
RichFaces Komponentenbibliothek
Die RichFaces Bibliothek erweitert die JSF Standard-Komponenten um viele
Zusatzkomponenten. Außerdem implementiert RichFaces einige Komponenten mit
Unterstützung für Ajax, was in JSF 1.2 noch nicht vorhanden ist. Zusätzlich dazu
besteht die Möglichkeit, RichFaces Komponenten über einen Theming Mechanismus
durch eine Einstellung in der faces-config.xml im Aussehen anzupassen.
2.3 Programmiersprache Scala
An der École polytechnique fédérale de Lausanne (EPFL) in der Schweiz begann
2001 eine Projektgruppe unter Leitung von Martin Odersky mit dem Entwurf
einer neuen Sprache, die objektorientierte und funktionale Programmierparadigmen
vereinen sollte. Das Ergebnis dieser Arbeit wurde im Jahr 2003 mit der ersten
Version der Programmiersprache Scala veröffentlicht.36
Die Namensgebung soll ausdrücken, dass Scala (aus dem Englischen scalable,
skalierbar) an den Anforderungen des Entwicklers wächst.
Die Programmiersprache Scala setzt auf die Java SE Plattform auf. Scala Code
wird in Java Bytecode kompiliert und durch eine JVM ausgeführt. Außerdem kann
36
Vgl. [Bra11, Kapitel 1]
16
26. 2.3 Programmiersprache Scala
Scala interpretiert ausgeführt werden. Dazu liegt der Scala Laufzeitumgebung ein
interaktiver Interpreter bei, eine so genannte REPL.37
Im Gegensatz zu Java ist Scala vollständig objektorientiert. Alles ist ein Objekt,
auch primitive Datentypen. Dadurch dass Scala in Java-Bytecode kompiliert wird,
kann von Scala aus Java Code genutzt werden. Scala ist interoperabel mit Java. In
Scala steht also die gesamte Java Standardbibliothek zur Verfügung und sämtliche
andere Java Bibliotheken.
In den nächsten Abschnitten werden die in dieser Arbeit eingesetzten Sprachfeatures
von Scala erläutert.
2.3.1 Klassen und Objekte
Scala ist objektorientiert und unterstützt daher das Konzept der Klassen und
Objekte. Wie in Java wird das Schlüsselwort class zum Definieren einer Klasse
verwendet. Methoden bzw. Funktionen werden mit dem Schlüsselwort def eingeleitet.
Das folgende Listing zeigt eine einfach Scala Klasse, die ein typisches Hello-World-
Beispiel umsetzt.
§ Listing 2.3: Einfache Scala Klasse ¤
c l a s s HelloWorld ( val g r e e t i n g : S t r i n g ) {
def s a y H e l l o ( ) = p r i n t l n ( g r e e t i n g )
}
new HelloWorld ( " H a l l o ! " ) . s a y H e l l o ( ) // Ausgabe : H a l l o !
¦ ¥
Mit dem new-Operator wird wie in Java eine Instanz von HelloWorld erzeugt und
anschließend die Methode sayHello ausgeführt.
Anders als bei Java wird in Scala der Konstruktor direkt in Form einer Parame-
terliste hinter dem Klassennamen angegeben. Die HelloWorld Klasse hat einen
37
Die Read-Execute-Print-Loop beschreibt einen Interpreter, der einen Befehl einliest, ausführt,
das Ergebnis ausgibt und anschließend auf die nächste Befehlseingabe wartet.
17
27. 2.3 Programmiersprache Scala
Parameter vom Typ String, greeting. Die Typangabe erfolgt in Scala nach der An-
gabe des Bezeichners und mit einem Doppelpunkt getrennt. Mit dem Schlüsselwort
val wird greeting als unveränderbares Feld definiert. Das ist mit der Verwendung
von final in Java vergleichbar. Soll greeting veränderbar sein, wird das Schlüsselwort
var anstalle von val verwendet.
Die Sichtbarkeit von sayHello ist automatisch public. Anders als in Java haben
Methoden und Felder standardmäßig nicht die Sichtbarkeit friendly, sondern public.
Mit den Zugriffsmodifizierern private und protected kann die Sichtbarkeit wie in
Java eingestellt werden.
Die Methode sayHello hat nur eine Aufgabe: den mit greeting übergebenen Wert
auf dem Standardausgabestrom ausgeben. Dazu wird die Methode println des
Predef -Objektes aufgerufen. Das Predef -Objekt wird automatisch vom Compiler
in jede Scala-Datei importiert und stellt oft benötigte Funktionen zur Verfügung.38
Obwohl Scala statisch typisiert ist, muss der Rückgabetyp von sayHello nicht
angegeben werden, da Scala über einen Typinferenz-Mechanismus verfügt. Darüber
kann der Compiler feststellen, welchen Typ ein Wert im Aufrufkontext hat. In
diesem Fall, wird der Typ Unit hergeleitet, vergleichbar mit dem void Typ in Java.
Da println lediglich eine Zeichenkette auf dem Standardausgabestrom ausgibt, wird
der Typ Unit zurückgegeben.
Aufgrund der funktionalen Natur von Scala hat alles in Scala einen Wert. Anders
als in Java muss zum Zurückgeben eines Wertes nicht return verwendet werden, es
gilt der Wert des letzten Ausdrucks eines Programmblocks als Rückgabewert.
Neben der Typinferenz verfügt Scala ebenfalls über eine Semikoloninferenz. Semi-
kolons sind in Scala nicht notwendig, um das Ende eines Befehls zu signalisieren
und werden größtenteils weggelassen.
Ein Scala eigenes Sprachmittel sind Singleton Objekte.39 Die Idee des Singletons
entstammt einem Entwurfsmuster, bei dem von einer Klasse genau eine Instanz
38
Außerdem werden dort auch wichtige Typ-Synonyme und Implicit Conversions definiert.
39
Die Begriffe Objekt und Singleton werden in dieser Arbeit als Synonyme für den Begriff der
Singleton Objekte verwendet.
18
28. 2.3 Programmiersprache Scala
existiert und ein globaler Zugriff darauf.40 Scala verallgemeinert dieses Muster mit
der Nutzung des Schlüsselworts object und unterstützt es direkt in der Sprache.
In Singleton Objekten werden in Scala statische Werte und Methoden implemen-
tiert. Statische Methoden mit dem Schlüsselwort static wie in Java zu definieren,
ist nicht möglich. Hierbei trennt Scala zwischen nicht-statischen und statischen
Programmcode-Teilen. Sollen einer Klasse statische Felder oder Methoden hinzuge-
fügt werden, existiert in Scala dafür das Companion Objekt, welches im folgenden
Listing für das Hello-World-Beispiel implementiert ist. An der Implementierung
der HelloWorld-Klasse ändert sich dabei nichts.
§ Listing 2.4: Klasse mit dazugehörigem Companion Objekt ¤
c l a s s HelloWorld ( val g r e e t i n g : S t r i n g ) {
def s a y H e l l o ( ) = p r i n t l n ( g r e e t i n g )
}
object HelloWorld {
def apply ( ) = new HelloWorld ( " Standard Meldung " )
def sayHi ( ) = p r i n t l n ( " Hi ! " )
}
HelloWorld ( ) . s a y H e l l o ( ) // Ausgabe : Standard Meldung
HelloWorld . sayHi ( ) // Ausgabe : Hi !
¦ ¥
Ein Companion Objekt ist ein spezielles Singleton Objekt, das den gleichen Na-
men wie eine Klasse hat und sich in der selben Quellcode-Datei befindet. Im
Companion Objekt HelloWorld werden zwei Methoden definiert, apply und say-
Hi. Die sayHi Methode stellt eine normale statische Methode dar, die über den
Namen des Companion Objekts aufgerufen werden kann. Die abgebildete apply-
Methode ist eine Besonderheit bei der Nutzung von Companion Objekten. Mit
40
Vgl. [EG95, S. 127]
19
29. 2.3 Programmiersprache Scala
der apply-Methode existiert eine eingebaute Fabrik-Methode in Scala. In einer
apply-Methode wird eine Instanz der Klasse des Companion Objektes erzeugt und
zurückgegeben. Der Vorteil dabei ist, Scala erlaubt das Weglassen des Aufrufs der
apply-Methode. So kann HelloWorld().sayHello() geschrieben werden, was vom
Compiler zu HelloWorld.apply().sayHello() erweitert wird. Die apply-Methode kann
natürlich beliebige Parameter aufnehmen.
2.3.2 Traits
Ein neues Sprachmittel, dass mit Scala eingeführt wird, sind Traits, die die aus Java
bekannten Interfaces ersetzen. Ein Trait ist mit einem Java Interface vergleichbar.
Allerdings mit dem Unterschied, dass Traits neben der Deklaration von Methoden
auch Implementierungen beinhalten können. Ein Trait wird mit dem Schlüsselwort
trait definiert, wie im folgenden Listing zu sehen ist.
§ Listing 2.5: Beispiel der Nutzung von Traits ¤
t r a i t HasName {
val name = "Name von etwas "
}
t r a i t HasAge {
def c a l c u l a t e A g e : I n t
}
c l a s s Person extends HasName with HasAge {
def c a l c u l a t e A g e = 18
override val name = "Name e i n e r Person "
}
¦ ¥
20
30. 2.3 Programmiersprache Scala
Wie bei der Klasse Person zu sehen ist, können Klassen mehrere Traits implemen-
tieren. Der erste Trait wird immer mit extends hinzugefügt, alle folgenden mit
dem Schlüsselwort with. Scala unterstützt allerdings keine Mehrfachvererbung. Es
handelt sich dabei um einen so genannten Mixin-Mechnismus. Ein Mixin ist eine
wiederverwendbare Einheit, die zusammengehörige Funktionen bündelt.41 In diesem
Zusammenhang wird auch vom Einmixen eines Traits gesprochen. Mögliche Proble-
me, die bei Mehrfachvererbung auftreten, wie z.B. das Diamond Problem, werden
bei Traits mit Hilfe von Linearisierung umgangen. Dabei werden die eingemixten
Traits und Klassen der Vererbungshierarchie in der Reihenfolge des Einmixen in
Form einer Liste aufeinander gelegt, wodurch Methoden, die in unterschiedlichen
Typen mehrfach definiert sind, für den Compiler auflösbar bleiben.42
Im Listing 2.5 ist außerdem zu erkennen, dass in Scala mit override ein spezielles
Schlüsselwort für das Überschreiben von Methoden und Feldern existiert. Da
calculateAge noch keine Implementierung in HasAge hat, ist override bei dieser
Methode nicht notwendig. Es kann jedoch angegeben werden.
2.3.3 Funktionen
Da Scala neben den objektorientierten Programmierparadigmen ebenfalls funktio-
nale Paradigmen einschließt, sind Funktionen einer der wichtigsten Bestandteile
der Sprache. Funktionen sind in Scala so genannte First Class Values, auch First
Class Citizens genannt, was bedeutet, sie werden als Werte behandelt und können
als solche Variablen zugewiesen werden oder als Parameter anderen Funktionen
übergeben werden. Ebenso ist es möglich, dass Funktionen andere Funktionen als
Rückgabewert zurückgeben.43
Eine Funktion wird mit dem Schlüsselwort def definiert, was bereits in den voran-
gegangenen Abschnitten gezeigt wurde. Alternativ dazu besteht die Möglichkeit,
Funktionen über Funktionsliterale zu definieren Das folgende Listing zeigt die
41
Vgl. [Bra11, S. 84]
42
siehe [MO08, Kapitel 12.6]
43
Vgl. [Bra11, Kapitel 5.2]
21
31. 2.3 Programmiersprache Scala
Funktion dec, die über def angelegt wird und anschließend alternativ als Funkti-
onsliteral.
§ Listing 2.6: Beispiel einer einfachen Funktion ¤
def dec ( i : I n t ) = i − 1
// F u n k t i o n s l i t e r a l f ü r dec
( i : Int ) = i − 1
>
¦ ¥
Ein Funktionsliteral wird dabei mit einem Doppelpfeil angegeben.44 Links davon
steht die Parameterliste der Funktion und rechts davon die Befehle der Funktion.
Beide Schreibweisen erzeugen das gleiche Ergebnis. Der Vorteil des Funktionsliterals
ist, dass eine Funktion darüber sehr einfach als Wert zugewiesen werden kann,
was im Listing 2.7 zu sehen ist. Ebenfalls wird durch Funktionsliterale die Angabe
anonymer Funktionen erleichtert.
§ Listing 2.7: Beispiel der Zuweisung eines Funktionswertes ¤
val d e c F u n c t i o n = ( i : I n t ) = i − 1
>
p r i n t l n ( d e c F u n c t i o n ( 4 ) ) // Ausgabe : 3
¦ ¥
Funktionen höherer Ordnung
Mit dem Begriff der Funktionen höherer Ordnung werden Funktionen bezeichnet,
bei denen Parameter oder der Rückgabewert aus einer Funktion bestehen. Das ist
ein hilfreiches Mittel zur Abstraktion von allgemeinem Verhalten, wie das folgende
Listing zeigt.45
§ Listing 2.8: Anwendung einer Funktion höherer Ordnung ¤
def dec ( i : I n t ) = i − 1
def d e c L i s t ( l i s t : L i s t [ I n t ] ) = l i s t map dec
44
Der Doppelpfeil setzt sich aus einem Gleichheitszeichen und einem Pfeil zusammen.
45
siehe [Bra11, Kapitel 5.3]
22
32. 2.3 Programmiersprache Scala
p r i n t l n ( d e c L i s t ( L i s t ( 1 , 2 , 3 ) ) ) // Ausgabe : L i s t ( 0 , 1 , 2 )
¦ ¥
In der Funktion decList wird die Dekrementierungsfunktion dec auf alle Elemente
der übergebenen Liste angewendet. Dazu wird die Funktion höherer Ordnung
namens map verwendet. Die map Funktion gehört zur Collections API von Scala.
map wendet die übergebene Funktion dec auf alle Elemente der Liste an und gibt
die dadurch neu enstandene Liste als Ergebnis zurück. In der decList Funktion
kommen gleich mehrere Scala-Besonderheiten zum Einsatz, die den Aufruf von
map verkürzen. In der vollen Länge wird map hier wie folgt aufgerufen.
§ Listing 2.9: Vollständig ausgeschriebener Aufruf von map ¤
l i s t . map ( ( i : I n t ) = dec ( i ) )
>
¦ ¥
In dieser Form macht das Funktionsliteral, das map als Parameter übergeben
wird, deutlich, dass es sich um eine Funktion höherer Ordnung handelt. Durch die
Typinferenz kann Int in der Parameterliste des Funktionsliterals weggelassen werden.
Da dec ledigleich einen Parameter erwartet, kann die Angabe des Parameters i
ebenfalls entfallen. Der Scala Compiler setzt dann einen automatisch generierten
Platzhalter an der Stelle für i ein. Dieser Platzhalter hätte auch explizit mit einem
Unterstrich angegeben werden können.
§ Listing 2.10: Aufruf der map Funktion mit einem Platzhalter ¤
l i s t . map( dec (_) )
¦ ¥
Als weitere Möglichkeit der Vereinfachung wird abschließend die Infix-Operator-
Schreibweise46 für Funktionen verwendet, wodurch der in Listing 2.8 gezeigte Aufruf
der map Funktion zustandekommt. Da Operatoren in Scala auch nur Methoden
von Klassen sind, kann die Infix-Operator-Schreibweise bei Funktionen verwendet
werden.47
46
Dabei steht ein Operator zwischen seinen zwei Operanden.
47
Vgl. [MO08, Kapitel 5.3]
23
33. 2.3 Programmiersprache Scala
Beim Aufruf von decList in der println Methode wird das Companion Objekt von
List genutzt und intern die apply-Methode aufgerufen. Denn der Aufruf List(1, 2,
3) wird vom Compiler zu List.apply(1, 2, 3) erweitert.
Currying
Unter Currying wird das Abbilden einer Funktion mit mehreren Parametern, auf
mehrere Funktionen mit je einem Parameter verstanden. In Scala gilt dies ebenso
für Funktionen mit Parameterlisten. Dadurch können Funktionen mit mehreren
Parameterlisten definiert werden, wie das folgende Listing zeigt.48
§ Listing 2.11: Beispiel einer Funktion mit mehreren Parameterlisten ¤
def sub ( x : I n t ) ( y : I n t ) = x − y
p r i n t l n ( sub ( 2 ) ( 3 ) ) // Ausgabe : −1
¦ ¥
Die Auflösung einer solchen Funktion durch den Compiler kann wie folgt veran-
schaulicht werden.
§ Listing 2.12: Abbildung der sub Funktion auf mehrere Funktionen ¤
def sub ( x : I n t ) = ( y : I n t ) = x − y
>
¦ ¥
Die sub Funktion hat nur noch eine Parameterliste und in der Funktion selbst wird
ein Funktionsliteral mit der zweiten Parameterliste definiert. Der Aufruf dieser
Funktion gestaltet sich genauso, wie von der in Listing 2.11 gezeigten Funktion.
Partielle Funktionen
Bei den partiellen Funktionen muss zwischen den partiellen und den partiell ange-
wandten Funktionen unterschieden werden.
Eine partielle Funktion ist eine Funktion, die nicht für alle Werte ihres Definitions-
bereichs definiert ist.49 Anwendungen solcher Funktionen werden im nachfolgenden
48
siehe [Bra11, Kapitel 5.5]
49
Vgl. [Bra11, S. 124]
24
34. 2.3 Programmiersprache Scala
Kapitel über Pattern Matching gezeigt. Mit dem Trait PartialFunction existieren
außerdem Hilfsfunktionen, um beispielsweise zu bestimmen, ob eine Funktion für
einen bestimmten Wert ihres Definitionsbereichs definiert ist.50
Eine partiell angewandte Funktion hingegen ist eine Funktion, die auf einen Teil
ihrer Parameter bereits angewendet ist.51 Es ist häufig der Fall, dass partiell
angewandte Funktionen zusammen mit Currying eingesetzt werden, indem eine
Parameterliste bereits angewendet wird und eine weitere zur Aufnahme zusätzlicher
Parameter dient. Im folgenden Beispiel wird die sub Funktion aus dem Listing 2.11
partiell angewendet und einem Wert zugewiesen.
§ Listing 2.13: Beispiel einer partiell angewandten Funktion ¤
val subTwoWith = sub ( 2 ) _
p r i n t l n ( subTwoWith ( 3 ) ) // Ausgabe : −1
¦ ¥
Der Unterstrich dient hierbei als Platzhalter für die zweite Parameterliste. Die
Signatur der in subTwoWith gespeicherten Funktion lautet Int => Int. Es wird
dabei also eine neue Funktion mit nur einer Parameterliste erzeugt, die intern auf
die sub Funktion zugreift.
Die wirkliche Stärke dieses Sprachmittels wird deutlich, wenn anstatt des einfachen
Parameters mit dem Zahlenwert 3 eine Funktion verwendet wird, wie im folgenden
Listing.
§ Listing 2.14: Kontrollstruktur-ähnliche Konstrukte mit subTwoWith ¤
p r i n t l n ( subTwoWith {
val n i n e = 4 + 5
val s i x = 2 + 4
nine − s i x
})
¦ ¥
50
Die Funktion dafür lautet isDefinedAt.
51
Vgl. [MO08, S. 147]
25
35. 2.3 Programmiersprache Scala
Das Ergebnis dieses Aufrufs von subToWith ist das gleiche wie im vorangegangenen
Listing. Durch die Verwendung der geschweiften Klammern und der Formulierung
eines Funktionsblocks wirkt die Verwendung von subTwoWith wie eine Scala-eigene
Kontrollstruktur. Solche Konstrukte sind bei der Verwendung von Scala häufig
anzutreffen.
2.3.4 Pattern Matching
Pattern Matching kann für einen Java Entwickler als Erweiterung der Switch-
Case-Kontrollstruktur erklärt werden. Beim Pattern Matching wird ein Wert mit
Mustern verglichen. Die Besonderheit bei Scala ist, dass nicht nur Werte als Muster
angegeben werden können, sondern auch Typen.52 Eine einfache Anwendung von
Pattern Matching zeigt das folgende Beispiel.53
§ Listing 2.15: Bespiel von Pattern Matching in printInt ¤
def p r i n t I n t ( i : I n t ) = i match {
case 1 = p r i n t l n ( " Eins " )
>
case 2 = p r i n t l n ( " Zwei " )
>
case _ = p r i n t l n ( " e i n e a n d e r e Zahl " )
>
}
p r i n t I n t ( 2 ) // Ausgabe : Zwei
¦ ¥
Mit dem Schlüsselwort match wird ein Pattern-Matching-Ausdruck eingeleitet und
in diesem Fall auf den Parameter i angewendet. Mit case werden die einzelnen Mus-
ter definiert, gegen die i verglichen wird. Der Doppelpfeil leitet den Codeblock ein,
der ausgeführt wird, sollte das jeweilige Muster passt. Nachdem ein Muster gepasst
hat, kehrt die Ausführung aus dem Match-Ausdruck zurück. Der Rückgabewert
entspricht dem des ausgeführten Codeblocks.
Mit dem Unterstrich wird das Wildcard-Pattern definiert, das auf jeden Wert und
Typen passt. Wird das Wildcard-Pattern weggelassen, ist der Match-Ausdruck
52
siehe [Bra11, Kapitel 5.4]
53
in Anlehnung an [Bra11, S. 115]
26
36. 2.3 Programmiersprache Scala
mitunter nicht für alle Werte des Definitionsbereichs definiert. Ist ein solcher Match-
Ausdruck die einzige Anweisung einer Funktion, wird die Funktion zur partiellen
Funktion, wie das folgende Listing zeigt.54
§ Listing 2.16: Pattern Matching in Form einer partiellen Funktion ¤
def printType ( v a l u e : Any) = v a l u e match {
case i : I n t = p r i n t l n ( " I n t : " + i )
>
case d : Double = p r i n t l n ( " Double : " + d )
>
}
printType ( 2 ) // Ausgabe : I n t : 2
¦ ¥
Die printType Funktion verwendet für den Mustervergleich Typen. Außerdem
ist diese Funktion eine partielle Funktion, da sie nur für Int und Double Typen
definiert ist, obwohl der Definitionsbereich Any ist. Any ist der Basistyp des Scala-
Typsystems. Wird printType beispielsweise mit einer Zeichenkette aufgerufen, wird
zur Laufzeit ein MatchError erzeugt, da die Funktion für den String-Typ nicht
definiert ist.
2.3.5 Self-Type Annotationen
Self-Type-Annotationen ermöglichen es, den Typ des this Wertes explizit zu dekla-
rieren. Dadurch können Funktionalitäten einer Klasse modular verteilt in mehreren
Traits implementiert werden oder aber ein Trait kann Abhängigkeiten festlegen,
die seine Funktionen benötigen und durch das Einmixen in die richtigen Klassen
aufgelöst werden.55 Ein Beispiel für den Einsatz einer Self-Type Annotation wird
im folgenden Listing gezeigt.56
54
in Anlehnung an [Bra11, S. 116]
55
siehe [Bra11, S. 160 ff.]
56
entnommen aus [Bra11, S. 161]
27
37. 2.3 Programmiersprache Scala
§ Listing 2.17: Beispiel einer Self-Type Annotation ¤
trait Noise {
def makeNoise : Unit
}
t r a i t Food {
def e a t : Unit
}
t r a i t Animal {
t h i s : N o i s e with Food =>
def run = {
makeNoise
eat
makeNoise
}
}
¦ ¥
Eine Self-Type-Annotation wird zu Beginn einer Typ-Definition formuliert. Sie
besteht dabei aus dem Schlüsselwort this,57 der Typangabe, die gewohnt mit einem
Doppelpunkt vom Bezeichner getrennt wird und einem Doppelpfeil. Dadurch
dass der Animal Trait die Self-Type Annotation Noise with Food hat, kann er
nur in Klassen eingemixt werden, die diese beiden Traits ebenfalls einmixen und
implementieren. Animal kann außerdem auf die Methoden (und Felder) von Noise
und Food zugreifen.
57
Alternativ kann auch self verwendet werden. Da this jedoch deutlicher aussagt, dass der Typ
des this Wertes überschrieben wird, ist diese Variante zu bevorzugen.
28
38. 2.3 Programmiersprache Scala
2.3.6 Implicit Conversions
Mit Implicit Conversions können in Scala Typumwandlungen in Form von Methoden
definiert werden. Wird eine Methode mit dem vorangestellten Schlüsselwort implicit
definiert, gilt sie als Implicit Conversion.
Erwartet der Compiler ein Objekt eines bestimmten Typs, findet jedoch ein Objekt
eines inkompatiblen Typen vor, sucht er nach einer Implicit Conversion. Findet
der Compiler eine passende Umwandlung, führt er die Methode aus. Durch dieses
Sprachmittel können in Scala bestehende Klassen um zusätzliche Funktionen
erweitert werden. Das wird auch als Pimp-my-Library-Pattern bezeichnet.58 Wie
einfach die Nutzung von Implicit Conversions ist, zeigt das nachfolgende Listing.
§ Listing 2.18: Beispiel einer Implicit Conversion ¤
c l a s s F a n c y S t r i n g ( val s t r : S t r i n g ) {
def d o F a n c y S t u f f = s t r + " f a n c y "
}
object F a n c y S t r i n g {
i m p l i c i t def s t r i n g T o F a n c y S t r i n g ( s t r : S t r i n g )
: F a n c y S t r i n g = new F a n c y S t r i n g ( s t r )
}
import F a n c y S t r i n g ._
p r i n t l n ( " H a l l o " . d o F a n c y S t u f f ) // Ausgabe : H a l l o f a n c y
¦ ¥
Die Klasse String der Java Standardbibliothek wird hierbei durch FancyString
und die im Companion Objekt implementiere Typumwandlung um die Methode
doFancyStuff erweitert. Obwohl diese Methode nicht in der String Klasse definiert
ist, kann sie dadurch an einem String Objekt aufgerufen werden. Vor dem Aufruf
von doFancyStuff wird der Compiler das String-Objekt „Hallo“ dafür mit Hilfe
58
siehe http://www.artima.com/weblogs/viewpost.jsp?thread=179766
29
39. 2.4 Lift Web-Framework
von stringToFancyString in ein Objekt des Typs FancyString umwandeln. Damit
die Implicit Conversion vom Compiler gefunden wird, importiert die Anweisung
import FancyString._ die Felder und Methoden von FancyString.
2.4 Lift Web-Framework
Im Jahr 2007 startete die Entwicklung von Lift. Das Lift Framework ist ein in
Scala entwickeltes Framework zur Entwicklung von Web-Anwendungen. Es ist
ein so genanntes Full-Stack Web-Framework. Das bedeutet, neben Funktionen
zur Erzeugung von Webseiten werden z.B. auch Funktionalitäten zum Speichern
der Anwendungsdaten in einer Datenbank angeboten oder für die Validierung
der Nutzereingaben. Ohne die Nutzung anderer Bibliotheken kann mit Lift eine
komplette Web-Anwendung entwickelt werden.
Lift baut auf bewährte Konzepte anderer Web-Frameworks auf und versucht diese
zu vereinen. So wird beispielsweise der Ansatz der Convention over Configuration
befolgt, der vom Web-Framework Ruby on Rails59 inspiriert wurde. Mit Convention
over Configuration soll der Aufwand und die Komplexität der Konfiguration und
Nutzung des Frameworks gesenkt werden, indem sinnvolle Vorgaben und Annahmen
vom Framework über die Nutzung getroffen werden, wie z.B. eine vorgegebene
Verzeichnisstuktur.
Lift ist allerdings kein MVC-Framework wie JSF.60 In Lift wird ein selbstdefiniertes
Entwurfsmuster verwendet. Die Lift-Entwickler verfolgen den View-First-Ansatz
für den Entwurf von Web-Anwendungen, wobei ein als View-ViewModel-Model
beschreibbares Entwurfsmuster eingesetzt wird, das in Abbildung 2.5 zu sehen ist.61
Der View-First-Ansatz besagt, dass in der Beschreibung der Seitendarstellung
ausschließlich valide Standard XHTML-Tags verwendet werden und wurde durch
59
siehe http://rubyonrails.org
60
Wobei MVC mit Lift sehr wohl umsetzbar ist. Das hat der Hauptentwickler von Lift, David
Pollak, exemplarisch gezeigt. (siehe dazu [Pol, Kapitel 12 und 13])
61
Vgl. [Per11, S. 7 f.]
30
40. 2.4 Lift Web-Framework
Abb. 2.5: Kontrollfluss im View-First-Ansatz von Lift aus [Per11]
das Java Web-Framework Wicket62 inspiriert. In Lift wird ein View daher in Form
von XML-Dokumenten beschrieben, die bestenfalls ausschließlich Standard XHTML
Tags enthalten.
Snippets sind das wichtigste Konzept von Lift. Als Snippet wird eine Funktion
bezeichnet, die XML-Knoten transformiert.63 Snippets sind das ViewModel im
View-First-Ansatz und sorgen für die Generierung von dynamischen Seiteninhalten.
Dabei verbinden sie den View mit dem Model, das die Fachentitäten der Anwendung
darstellt. Dabei kann ein View durchaus mehrere Snippets nutzen. Im Gegensatz
zum View können Snippets zustandsbehaftet sein. Auch wenn das Snippet in der
Abbildung 2.5 wie ein Controller gemäß dem MVC-Muster aussieht, ist es das nicht.
Ein Snippet übernimmt nicht die Steuerung des Kontrollflusses.
Außerdem baut das Lift Framework auf der Java EE Plattform auf. Über die Servlet
Technologie integriert sich Lift in Java EE Webcontainer, was unteranderem in
Abbildung 2.6 dargestellt wird. Wie eine JSF-Anwendung werden Lift-Anwendungen
in Form von WAR-Dateien bereitgestellt.
62
siehe http://wicket.apache.org
63
In der Arbeit werden teilweise auch die Klassen als Snippet bezeichnet, die eine solche Funktion
enthalten.
31
41. 2.4 Lift Web-Framework
Abb. 2.6: Architektur von Lift aus [DCB11]
Lift implementiert mehrere Persistenzlösungen. Die Standard-Persistenzlösung
nennt sich Mapper. Die Mapper Bibliothek implementiert unter Nutzung des Active
Record Patterns die Abbildung von Objekten in einer relationalen Datenbank.
Beim Active Record Pattern befindet sich die Logik für den Datenbankzugriff im
Domänenobjekt. Außerdem repräsentiert ein Domänenobjekt bei Active Record
einen Datenbankeintrag.64 Gleichzeitig ist Mapper, wie es der Name bereits andeutet,
ein objektrelationaler Mapper. Die Doppeldeutigkeit von Mapper als Umsetzung des
Active Record Patterns und objektrelationaler Mapper ist auf die Art und Weise der
Implementierung zurückzuführen. Lift Mapper Funktionalitäten werden mit Traits
in die Domänenobjekte eingemixt. Dadurch ist die Logik für den Datenbankzugriff
zwar in den Domänenobjekten, aber implementiert wird sie separiert in den Mapper
64
Vgl. [Fow03, S. 160 f.]
32
42. 2.4 Lift Web-Framework
Traits. Intern nutzt Mapper die Java SE Datenbankschnittstelle JDBC (Java
Database Connectivity).65
Zusätzlich bietet Lift eine Abstraktionsschicht für Ajax und JavaScript, wodurch
JavaScript-Befehle und Ajax Aufrufe in reinem Scala-Code formuliert werden
können. Dabei wird intern die JavaScript-Bibliothek JQuery66 eingesetzt. Alternativ
kann YUI67 verwendet werden.
65
Vgl. [Per11, S. 13]
66
siehe http://jquery.com
67
siehe http://developer.yahoo.com/yui
33
43. 3 Methodik der Evaluation
3.1 Beschreibung der Evaluation
Für die Evaluation der beiden Technologiestacks werden folgende zwei Methoden
eingesetzt:
• Literaturstudium
• Umsetzung einer prototypischen Beispielanwendung
Durch das Literaturstudium wird der Blick in die Interna der jeweiligen Frameworks
ermöglicht. Dieser wäre ohne ein solches Studium nicht möglich. Die alleinige Im-
plementierung einer prototypischen Anwendung führt zwar zur Auseinandersetzung
mit der API der jeweiligen Technologie. Jedoch sind dadurch nicht zwangsläufig
die internen Mechanismen geklärt. Mögliche Stärken und Schwächen, die sich nicht
durch die Nutzung der API zeigen, würden somit auch nicht in die Evaluation
einfließen.
Die Ergebnisse aus dem Literaturstudium werden beim Vergleich der beiden imple-
mentierten Lösungen eingebracht.
Die wichtigste Grundlage für die Evaluation ist die Entwicklung einer prototypischen
Beispielanwendung. Für jeden Technologiestack wird ein Prototyp für die im
Abschnitt 4.1.2 formulierten Anwendungsfälle entwickelt. Der Fokus liegt dabei auf
dem Einsatz der jeweiligen Frameworkspezifika und nicht auf der bestmöglichen
Umsetzung der Anforderungen. Die implementierten Prototypen sind nicht für den
produktiven Einsatz vorgesehen.
34
44. 3.2 Evaluationskriterien
3.2 Evaluationskriterien
Die Evaluation wird anhand von Evaluationskriterien durchgeführt, um das Ergebnis
möglichst objektiv und für den Leser transparent zu gestalten.
Die Auswahl der Evaluationskriterien für diese Arbeit orientiert sich an gängigen
Standards und Erkenntnissen der Softwareentwicklung sowie den Erfahrungswerten
des Autors. Dabei spielen ebenso einfache Faktoren wie die reine Funktionalität
eine Rolle als auch Faktoren, die die Struktur und den Aufbau des Programmcodes
betreffen.
3.2.1 Allgemeines
Beim Vergleich von Technologien und Frameworks gibt es allgemeingültige Kriterien
wie z.B. die Integration in die Entwicklungswerkzeuge, die bei jedem Vergleich
anwendbar sind.
Dokumentation
Beschäftigt sich ein Entwickler mit Technologien, so wird eine gute Dokumentation
erwartet. Die Dokumentation sollte dabei eine Spezifikation der API enthalten,
als auch beschreibende Anleitungen zu Anwendungsfällen. Ebenso zählt zur Doku-
mentation das Vorhandensein von Büchern zur Einführung und Vertiefung in die
Technologie.
Im Verlauf der Entwicklung der Prototypen hat sich gezeigt, dass es sehr wohl Unter-
schiede in der Qualität und Quantität der Dokumentationsmaterialien gibt. Daher
wird bei diesem Kriterium die Dokumentation der Technologiestacks beurteilt.
Lernkurve
Eignen sich Softwareentwickler Kenntnisse über neue Sprachen und Frameworks
an, gibt es eine so genannte Lernkurve. Unter der Lernkurve wird der nötige
35
45. 3.2 Evaluationskriterien
Lernaufwand verstanden, der zum Erreichen eines bestimmten Ergebnisses nötig
ist.
Der Autor hat Java EE im November 2010 kennengelernt. Für diese Arbeit hat er
Scala und Lift gelernt. Daher sind die Ergebnisse zur Lernkurve reine Erfahrungs-
werte, die einen Eindruck vermitteln sollen, in welchem Verhältnis die Lernaufwände
für die beiden Technologiestacks stehen.
Toolchain
Die Toolchain bezeichnet umgangssprachlich die Werkzeugkiste eines jeden Soft-
wareentwicklers. Damit sind die Programme gemeint, die der Entwickler für seine
Arbeiten einsetzt. Im heutigen Entwicklungsgeschäft ist es wichtig, dass sich die
eingesetzen Techniken reibungslos in die Toolchain integrieren lassen. Dies steigert
die Produktivität und ist somit von großer Bedeutung.
Es gilt bei der Toolchain zu untersuchen, in welchem Maße sich die jeweiligen
Frameworks in die eingesetzten Programme integrieren lassen und welche Vor- und
Nachteile durch diese Integration entstehen.
3.2.2 Funktionalität
Bei der Entwicklung von Web-Anwendungen treten oft ähnliche Anforderungen
an die verwendeten Frameworks und Technologien auf. Daher werden die beiden
entwickelten Lösungen auf die Unterstützung der im folgenden Abschnitt beschrie-
benen Funktionalitäten untersucht und bewertet. Die Auswahl dieser Kriterien
wurde maßgeblich durch [Wan08] beeinflusst.
Persistenzmechanismen
Heutzutage kommt keine Web-Anwendung ohne einen entsprechenden Persistenz-
mechanismus aus. Die Daten der Anwendung und des Nutzers müssen für die Dauer
einer Sitzung und über die Laufzeit der Anwendung hinweg gespeichert werden.
36
46. 3.2 Evaluationskriterien
Es wird untersucht, welche Persistenztechnologien die Frameworks unterstützen
und gezeigt, wie der so genannte objektrelationale Mismatch gelöst wird. Aufgrund
der Verschiedenheit Daten objektorientiert und relational abzubilden, gibt es bei
der Persistierung eines objektorientierten Domänenmodells in eine releationale
Datenbank eine Unverträglichkeit; einen Mismatch, den es zu lösen gilt.1 Weiterhin
wird die Umsetzung in den jeweiligen Lösungen aufgrund der Integration in das
bestehende Domänenmodell und der Funktionalität bewertet.
Darstellungskonzept
Jede Anwendung stellt die Daten für den Nutzer auf dem Bildschirm in irgend-
einer Form dar. Frameworks bieten unterschiedliche Konzepte, wie diese Ansicht
aufzubauen und mit dem Rest der Anwendung zu verbinden ist.
Hier gilt es zu untersuchen, wie naht- und reibungslos sich die unterschiedlichen Dar-
stellungskonzepte mit den anderen Anwendungsteilen verbinden lassen. Ebenfalls
wird aufgezeigt, welche Vor- und Nachteile die unterschiedlichen Ansätze bieten.
Ajax-Unterstützung
In den letzten Jahren hat sich Ajax als Technologie für hochgradig interaktive
Anwendungen durchgesetzt. Dabei werden Inhalte vom Server asynchron in Form
von XML durch den Client nachgeladen. Dies geschieht für den Nutzer transparent
im Hintergrund. Ein Neuladen der Seite ist nicht erforderlich.
Die eingesetzten Frameworks werden daraufhin untersucht und bewertet, inwiefern
Ajax unterstützt wird und welche Qualität die Ajax-Integration aufweist.
Validierung
Die Validierung von Nutzereingaben ist in jeder Anwendung wichtig. Die Eingaben
müssen auf bewusst falsche Angaben und fachliche Inkonsistenzen geprüft werden.
1
Vgl. [Röd10, Kapitel 1]
37
47. 3.2 Evaluationskriterien
Für die Validierung wird bewertet, wie sich die formulierten Validierungsregeln
in das Domänenmodell einpflegen lassen, welche Möglichkeiten zur Validierung
durch das Framework bereits angeboten werden und wie sich die Validierung in die
anderen Frameworkteile integriert.
Internationalisierung
Web-Anwendungen werden nicht ausschließlich in der Muttersprache des Kunden
ausgeliefert. Da diese Anwendungen in den meisten Fällen von Nutzern unterschied-
licher Sprachen genutzt werden, sind die Beschriftungen und Texte so zu gestalten,
dass sie dynamisch ladbar sind. Abhängig von der Spracheinstellung des Nutzers
wird die Anwendung in einer anderen Sprache dargestellt.
Es gilt zu untersuchen, welche Mechanismen die Technologiestacks für diese Form
der Internationalisierung von Anwendungen zur Verfügung stellen und wie sich
diese in die Anwendung integrieren.
3.2.3 Architektur
Laut [Bal01] kann Softwarearchitektur als „eine strukturierte oder hierarchische
Anordnung der Systemkomponenten sowie Beschreibung ihrer Beziehungen“ defi-
niert werden. Dem zugrundeliegend werden die beiden Technologiestacks aufgrund
architekturspezifischer Funktionalitäten bewertet. Dabei wird bewertet, welche
Funktionen existieren, um Enterprise-Anwendungen strukturiert und lose gekoppelt
zu entwickeln.
Lose Kopplung
Bei der Komplexität heutiger Anwendungen ist es von großer Bedeutung, Objekt-
netze flexibel aufzubauen. Dabei steht die lose Kopplung einzelner Komponenten
und einfache Konfiguration dieser im Vordergrund. Unteranderem soll dadurch die
Testbarkeit eines Systems verbessert werden.
38
48. 3.2 Evaluationskriterien
Es gilt zu untersuchen, welche Möglichkeiten und Funktionalitäten die Frameworks
für die lose Kopplung eines Anwendungssystems anbieten.
Cross Cutting Concerns
In Softwaresystemen gibt es so genannte Querschnittsbelange (engl. Cross Cutting
Concerns), die mit herkömmlicher objektorientierter Programmierung ohne Verlet-
zung des DRY-Prinzips nicht umsetzbar sind.2 Querschnittsbelange werden auch
orthogonale Belange genannt. Dies sind z.B. Funktionalitäten wie Tracing,3 Trans-
aktionssteuerung oder Exception Handling. Solche Funktionen sind in irgendeiner
Form in jeder Anwendungsschicht vorhanden. Sie befinden sich orthogonal zu den
Anwendungsschichten. So finden sich z.B. in vielen Methoden eines Anwendungs-
systems Programmzeilen, die für das Tracing verantwortlich sind. Dadurch wird
Code mit gleicher Funktionalität regelmäßig in den einzelnen Methoden dupliziert.
Dies widerspricht dem DRY-Prinzip. Dieses besagt „Don’t Repeat Yourself“ und
ist ein Grundprinzip guter Softwareentwicklunng.
Es wird untersucht, ob es Möglichkeiten gibt, diese Querschnittsbelange separat
vom Anwendungscode zu formulieren. Wenn dies nicht möglich ist, wird diskutiert,
welche anderen Arten der Implementierung existieren und wie gehaltvoll diese sind.
2
Vgl. [Wol10, S. 100 f.]
3
Tracing bezeichnet das detaillierte Loggen von Methoden. In den allermeisten Fällen wird dabei
der Methodeneintritt mit den übergebenen Parameter sowie der Austritt geloggt.
39
49. 4 Entwicklung der
Beispielanwendung
4.1 Entwurf der Anwendung
Für den Vergleich des Scala/Lift Technologiestacks mit dem Java EE Programmier-
modell wird eine prototypische Raumverwaltungssoftware entwickelt. Der Nutzer
ist mit dieser Software in der Lage, Gebäude mit Räumen zu verwalten. Dabei gilt
die Vorgabe, dass für die Verwaltung der Gebäude und Räume so wenige Seiten
wie möglich aufzurufen sind. Bestenfalls existiert eine Seite pro Anwendungsfall.
4.1.1 Domänenmodell
Das Domänenmodell einer Anwendung beschreibt die fachlichen Entitäten einer
Problemlösung und die Beziehungen dieser untereinander. Bei der zu entwickeln-
den Raumverwaltungsanwendung besteht das Domänenmodell aus vier Entitäten:
Gebäuden, Räumen, Ausstattungen der Räume und Ausstattungsmerkmalen. Die
Abbildung 4.1 zeigt das Domänenmodell der Beispielanwendung. Zusätzlich existie-
ren Regeln für die zulässigen Werte einiger Attribute.
Sämtliche Bezeichnungen dürfen nicht leer sein. Das heißt, die Zeichenketten
müssen mindestens ein Zeichen enthalten und Null ist ebenfalls nicht als Wert
zulässig. Des Weiteren sind sämtliche Zahlen als positive ganze Zahlen abzubilden,
Primärschlüssel ausgeschlossen.
40
50. 4.1 Entwurf der Anwendung
Abb. 4.1: Domänenmodell der Beispielanwendung
41
51. 4.1 Entwurf der Anwendung
4.1.2 Anwendungsfälle
Ein Anwendungsfall beschreibt ein mögliches Szenario für die Nutzung eines An-
wendungssystems. Die Beispielanwendung behandelt drei Anwendungsfälle: die
Verwaltung von Gebäuden, die Suche nach Räumen und das Verwalten von Aus-
stattungsmerkmalen.
Gebäude verwalten
Der Hauptanwendungsfall ist das Verwalten von Gebäuden. Dabei soll der Nutzer
neue Gebäude anlegen, Veränderungen an Gebäudedaten speichern und existierende
Gebäude löschen können. Dies gilt natürlich ebenfalls für die Räume der Gebäude
und Ausstattungen der Räume.
Räume suchen
Der Nutzer soll die Möglichkeit haben, nach Räumen mit einem bestimmten
Ausstattungsmerkmal zu suchen. Dabei kann der Benutzer die Wahl treffen zwischen
allen vorhandenen Ausstattungsmerkmalen.
Ausstattungsmerkmale verwalten
Ein Raum kann mehrere Raumaustattungen besitzen. Eine Raumaustattung ist
dabei die Zuordnung eines Ausstattungsmerkmals mit einer Menge zu einem Raum.
Diese Ausstattungsmerkmale soll der Nutzer ebenfalls verwalten können. Dabei
gilt die Vorgabe, dass Ausstattungsmerkmale nur gelöscht werden dürfen, wenn
diese bei keinem Raum in einer Raumausstattung verwendet werden. Andernfalls
würden bestehende Daten fehlerhaft. Das Hinzufügen und Verändern bestehender
Ausstattungsmerkmale soll ebenso möglich sein.
42
52. 4.2 Umsetzung mit Java EE
4.2 Umsetzung mit Java EE
Die Java EE Implementierung der Beispielanwendung setzt JSF 1.2 mit Facelets
als View-Technologie ein. Für die Persistenzschicht wird JPA 2.0 mit Hibernate als
JPA Provider verwendet.
4.2.1 Domänenmodell
Das Domänenmodell wird im Paket de.htw.berlin.jroomyweb.domain entwickelt.
Dabei wird für jede fachliche Entität eine Klasse implementiert. Ein Gebäude wird
also durch die Klasse Gebaeude repräsentiert, wie das folgende Listing zeigt.
§ Listing 4.1: JPA Grundgerüst der Java Gebäude Fachklasse ¤
@Entity
public c l a s s Gebaeude implements S e r i a l i z a b l e {
@Id
@GeneratedValue
private Long i d ;
protected Gebaeude ( ) {
// f ü r JPA
}
public Long g e t I d ( ) {
return i d ;
}
public void s e t I d ( Long i d ) {
this . id = id ;
}
}
¦ ¥
43
53. 4.2 Umsetzung mit Java EE
Da die Objekte der Domänenschicht mittels JPA in einer relationalen Datenbank
persistiert werden, hat jede Fachklasse die Entity-Annotation. Diese Annotation
kennzeichnet für JPA eine Klasse, die auf eine Datenbank abzubilden ist. Weiterhin
fällt auf, dass Gebaeude das Interface Serializable implementiert. Das Serializable
Interface kennzeichnet Klassen, die serialisierbar sind. Damit ist das Abbilden eines
Objektes der Klasse auf einen Bytestrom oder anderes Übetragungsformat (z.B.
XML) gemeint. Für die reine Persistierung über JPA ist das nicht notwendig. Jedoch
ist es eine Best-Practice, im Java EE Kontext die JPA Fachklassen serialisierbar zu
machen. Sollte die Anwendung in einem verteilten System eingesetzt werden, kann
es vorkommen, dass Objekte der Domänenschicht zwischen verschiedenen Servern
hin- und hergeschickt werden. Dafür müssen die Objekte serialisiert werden. Das
Speichern von Domänenobjekten in der HTTP Session eines Nutzers kann ebenfalls
das Serialisieren für die Zwischenspeicherung auf der Festplatte des Application
Servers zur Folge haben.
Alle Domänenobjekte werden mit einer eindeutigen Identifikationsnummer gespei-
chert. Diese entspricht dem Primärschlüssel in der Datenbank. Daher hat die
Gebaeude Fachklasse ein id Attribut. Dieses ist mit einer Id-Annotation versehen,
die den Primärschlüssel der Objekte für die Abbildung in der Datenbank kennzeich-
net. Außerdem wird mit der GeneratedValue-Annotation festgelegt, dass der Wert
von id automatisch durch JPA generiert wird. In den meisten Konfigurationen be-
wirkt dies das Anlegen einer automatisch hochzählenden Datenbankspalte.1 Damit
ein JPA Provider auf das id Attribut von Gebaeude zugreifen kann, muss ein so
genanntes Getter-Setter-Methodenpaar implementiert werden. Diese Vorgabe der
Getter-Setter-Zugriffsmethoden gilt für alle Attribute, die durch JPA persistiert
werden.
Die Verwendung von JPA in der Gebaeude Klasse verlangt des Weiteren die Imple-
mentierung eines Konstruktors mit einer leeren Parameterliste. Das ist notwendig,
da ein JPA Provider Objekte von Gebaeude über die Java Reflections API erzeugt,
indem dieser leere Konstruktor aufgerufen wird, und die Gebaeude Objekte z.B.
anschließend über die Setter-Methoden mit den Daten aus der Datenbank befüllt.
1
Oftmals wird dafür der Terminus AUTO_INCREMENT verwenden.
44
54. 4.2 Umsetzung mit Java EE
Da die Gebaeude Klasse einen Konstruktor mit einer nicht leeren Parameterliste hat,
der den Default-Konstruktor von Java überdeckt, musste dieser leere Konstruktor
zusätzlich fomuliert werden.2 Um zu verhindern, dass ein Gebaeude Objekt uner-
laubter Wise im Programmcode über diesen leeren Konstruktor erzeugt wird, hat
er die Sichtbarkeit protected und ist damit nur im Paket der Fachklassen sichtbar.
Alle Attribute von Gebaeude werden durch die Nutzung von JPA automatisch in
der Datenbank abgebildet.3 So auch die vier String-Attribute der Gebäude Entität
aus dem Domänenmodell, wie das folgende Listing zeigt. Nicht abgebildet, sind die
obligatorischen Getter- und Setter-Methoden.
§ Listing 4.2: String Attribute der Gebäude Klasse ¤
@NotEmpty ( message=
" Der Name e i n e s Gebäudes d a r f n i c h t l e e r s e i n . " )
private S t r i n g b e z e i c h n u n g ;
private S t r i n g s t r a s s e ;
private S t r i n g p l z ;
private S t r i n g o r t ;
¦ ¥
Das Domänenmodell verlangt, dass sämtliche Bezeichnungen nicht leer sein dürfen.
Daher wurde das Attribut bezeichnung mit NotEmpty annotiert. Die NotEm-
pty-Annotation ist eine Erweiterung des Bean Validation Standards und steht
durch die Nutzung der Hibernate Validator Bibliothek zur Verfügung. Durch die
Annotation wird festgelegt, dass bezeichnung weder den Wert Null noch eine Zei-
chenkette einer Länge kleiner 1 aufnehmen kann. Mit dem message Attribut der
NotEmpty-Annotation wird die Fehlernachricht angegeben, die bei Verletzung der
Validierungsregel ausgegeben wird.
Die Fehlernachrichten von Bean-Validation-Annotationen sind zusätzlich über
Ressourcendateien lokalisierbar. Dazu muss mindestens eine ValidationMessa-
ges.properties Datei im Klassenpfad abgelegt werden, die in Schlüssel-Wert-Paaren
2
Der Konstruktor mit der nicht leeren Parameterliste ist in dem Listing aus Gründen der
Übersicht nicht dargestellt ist.
3
Sollen einzelne Attribute nicht persistiert werden, sind diese mit @Transient zu annotieren.
45
55. 4.2 Umsetzung mit Java EE
Variablen und ihren Wert enthält. Mit dem Ausdruck {Variablenbezeichner} kann
ein Wert im message Attribut einer Bean-Validation-Annotation geladen werden.
Über Länder- und Sprachkürzel als Suffix des Dateinamen können einzelne Valida-
tionMessages_xx_XX.properties Dateien für bestimmte Sprachen definiert werden.
Das wurde im Prototypen jedoch nicht implementiert.
Die 1-n Beziehung zwischen Gebäuden und Räumen wird in der Gebaeude Klasse
durch ein Set implementiert. Ein Raum wird in der Java EE Lösung durch die
gleichnamige Klasse Raum repräsentiert.
§ Listing 4.3: Implementierung der Beziehung zwischen Räumen und Gebäuden ¤
@OneToMany( c a s c a d e=CascadeType . ALL, mappedBy=" gebaeude " )
private Set<Raum> raeume = new HashSet<Raum>() ;
¦ ¥
Durch das Annotieren des raeume Sets mit OneToMany wird die Art der Beziehung
definiert. Die zwei Attribute, cascade und mappedBy, konfigurieren diese Beziehung.
Über das cascade Attribut der OneToMany-Annotation werden kaskadierende
Operationen eingestellt. Das heißt, ist das umgebene Gebaeude Objekt durch
eine JPA Operation, wie z.B. das Speichern oder Löschen in der Datenbank,
betroffen, wird diese Operation auf alle Raum Objekte in raeume angewendet.
In diesem Fall werden durch die Angabe von CascadeType.ALL alle Operationen
kaskadierend auf das Set raeume angewendet. Das zweite Attribut, mappedBy,
betrifft die Abbildung der Beziehung in der Datenbank. Darüber wird definiert, dass
die Beziehung in der Datenbank durch die Spalte des gebaeude Attributs der Raum
Klasse abgebildet wird. Dies entspricht der Speicherung eines Fremdschlüssels in der
Raum Tabelle auf die ID Spalte der Gebäude Tabelle. Damit diese Konfiguration
über mappedBy funktioniert, wird das gebaeude Attribut der Raum Klasse mit
ManyToOne annotiert.
§ Listing 4.4: gebaeude Attribut in der Java Raum Fachklasse ¤
@ManyToOne( o p t i o n a l=f a l s e )
private Gebaeude gebaeude ;
¦ ¥
46
56. 4.2 Umsetzung mit Java EE
Das Setzen des optional Attributs auf den Wert false, bewirkt das Generieren eines
NotNull Constraints für die gebaeude Spalte in der Datenbank. Ein Raum kann
demnach nicht ohne ein dazugehöriges Gebäude existieren.
Neben der Beziehung zu einem Gebäude hat ein Raum mehrere Attribute, wie im
folgenden Listing exemplarisch anhand der Fensteranzahl gezeigt wird.
§ Listing 4.5: Anzahl der Fenster eines Raumes ¤
@Min( v a l u e =1, message=
" Die Anzahl d e r F e n s t e r kann n i c h t k l e i n e r 0 s e i n . " )
private int a n z a h l F e n s t e r ;
¦ ¥
In der Beschreibung des Domänenmodells wurde festgelegt, dass sämtliche Zahlen
als positive Ganzzahlen abzubilden sind. Dies wird durch die Validierungsregel der
Min-Annotation umgesetzt. Durch das value Attribut wird der kleinste zulässige
Wert von anzahlFenster festgelegt. Die Min-Annotation ist eine Annotation des
Bean Validation Standards.
Wie bereits die 1-n Beziehung zwischen Gebäuden und Räumen wird auch die
Beziehung zwischen einem Raum und mehreren Raumausstattungen über ein
Set implementiert. Eine Raumausstattung wird durch die Klasse Ausstattung
implementiert.
§ Listing 4.6: Raumausstattungen in der Raum Klasse ¤
@OneToMany( c a s c a d e=CascadeType . ALL,
f e t c h=FetchType .EAGER, mappedBy=" raum " )
private Set<Ausstattung > a u s s t a t t u n g e n =
new HashSet<Ausstattung >() ;
¦ ¥
Das fetch Attribut legt in diesem Fall fest, dass alle Raumausstattungen beim
Laden eines Raumes aus der Datenbank sofort mitgeladen werden. Andernfalls
geschieht dies verzögert (engl. lazy), was bei der Umsetzung des Prototypen zu
Ladefehlern geführt hat.
Die Implementierungen der Klassen Ausstattung und Merkmal, die das Ausstattungs-
merkmal des Domänenmodells repräsentiert, verwenden keine neuen Annotation
47