Praktikum aus Softwareentwicklung 2 - SSW

Werbung
Praktikum aus
Softwareentwicklung 2
Version: 2011-09-15_11:53
Markus Loeberbauer
2010, 2011
Praktikum aus Softwareentwicklung 2
Inhalt
Inhalt
Inhalt ............................................................................................................................................................ 1
Java ............................................................................................................................................................... 2
Subversion ................................................................................................................................................. 8
Graphische Oberflächen .................................................................................................................... 11
Dateien ...................................................................................................................................................... 26
Reflection ................................................................................................................................................. 27
Threads...................................................................................................................................................... 31
Streams ..................................................................................................................................................... 41
Serialisierung .......................................................................................................................................... 46
Netzwerkprogrammierung................................................................................................................ 48
Datenbanken .......................................................................................................................................... 49
Remoting .................................................................................................................................................. 59
XML............................................................................................................................................................. 67
Java Servlet.............................................................................................................................................. 80
Dynamischer Kontext in JavaServer Pages ................................................................................ 89
JSP Custom Tags................................................................................................................................ 104
Applets ................................................................................................................................................... 112
Java Service .......................................................................................................................................... 115
Sicherheit............................................................................................................................................... 118
Java Native Interface (JNI) ............................................................................................................. 124
Eclipse-Tastenkürzel ......................................................................................................................... 130
Markus Loeberbauer 2010, 2011
1
Praktikum aus Softwareentwicklung 2
Java
Java
Sun hat 1995 die Programmiersprache Java vorgestellt. Java ist eine statisch stark
typisierte, objektorientierte Sprache mit dem Ziel betriebssystemunabhängige
Programme zu ermöglichen. Java ist von der Syntax her an C++ angelehnt, aber
von Umfang her reduziert und dadurch vereinfacht. Wesentliche Eigenschaften
von Java sind: die automatische Speicherbereinigung, Verzicht auf
Zeigerarithmetik und die mächtige Klassenbibliothek.
Mit Java 5.0 (auch Java 1.5) wurde die Sprache um einige interessante
Eigenschaften erweitert, zB: Autoboxing, Enumerationen, eine erweiterte forSchleife und variable Argumentlisten.
Autoboxing
In Java gilt grundsätzlich "everything is an object", außer es handelt sich um einen
primitiven Datentyp. Primitive Datentypen müssen daher vor der Zuweisung an
eine Variable vom Typ Object in Wrapper-Objekte gekapselt werden (Boxing). Das
ist nötig wenn man primitive Daten in eine Sammlung wie zB ArrayList einfügen
will. Seit Version 5.0 macht der Java-Compiler dieses Boxing automatisch
(Autoboxing).
Abbildung 1 zeigt wie man primitive Datentypen boxen und unboxen kann.
Achtung: Autoboxing benutzt die erste gezeigte Möglichkeit (valueOf). Dabei
werden Objekte wiederverwendet, d.h., geboxte Objekte können referenzgleich
sein, per default sind das die Objekte im Bereich zwischen -128 und 127. Der
Bereich kann sich aber ändern, die Objektwiederverwendung ist nur eine
Optimierung, auf die Referenzgleichheit darf man sich also auf keinen Fall
verlassen.
Markus Loeberbauer 2010, 2011
2
Praktikum aus Softwareentwicklung 2
Java
int i = 42;
int x;
Object obj;
// Manual (Un)Boxing Possibility 1
obj = Integer.valueOf(i);
x = ((Integer) obj).intValue();
// Manual (Un)Boxing Possibility 2
obj = new Integer(i);
x = ((Integer) obj).intValue();
// Auto(un)boxing, equivalent with Possiblity 1
obj = i;
x = (Integer) obj;
Abbildung 1) Vergleich manuelles Boxing und Autoboxing
Enumerationen
Enumerationen sind Typen mit festgelegten Werten, zB eine Enumeration für
Sortierreihenfolgen könnte SortOrder heißen und die Werte ASCENDING und
DESCENDING haben. Vor Version 5.0 hat man in Java dafür gerne int-Konstanten
verwendet. Das hat aber den Nachteil, dass Enumerationswerte nicht typsicher
sind. Wollte man typsichere Enumeration hat man mit final-Klassen und
Konstanten dieser Klassen gearbeitet. Seit Java 5.0 gibt es expliziten Support für
typsichere Konstanten. Abbildung 2 zeigt die unterschiedlichen Möglichkeiten wie
man Enumerationen anlegen kann.
Markus Loeberbauer 2010, 2011
3
Praktikum aus Softwareentwicklung 2
Java
// Before Java 5.0 NOT type safe
public class SortOrder {
public static final int ASCENDING = 1;
public static final int DESCENDING = 2;
}
// Before Java 5.0 type safe
public final class SortOrder {
public static final SortOrder ASCENDING = new SortOrder();
public static final SortOrder DESCENDING = new SortOrder();
private SortOrder() { /* avoid public object creation */ }
}
// In Java 5.0 (type safe)
public enum SortOrder {
ASCENDING, DESCENDING
}
Abbildung 2) Enumerationen in Java
Java Enumeration sind Klassen und ihre Werte sind Objekte dieser Klassen. Damit
kann der Programmierer auch Methoden und Konstruktoren zu Enumerationen
hinzufügen, siehe Abbildung 3. Damit bei Enumerationen nur die vorgesehen
Werte existieren können und jeder dieser Werte nur einmal existiert sind die
Konstruktoren immer privat und die Enumerations-Klasse ist final (d.h., von einer
Enumerations-Klasse kann man nicht erben).
Markus Loeberbauer 2010, 2011
4
Praktikum aus Softwareentwicklung 2
Java
public enum SortOrder {
ASCENDING(1), DESCENDING(2);
private final int legacyValue;
SortOrder(int legacyValue) {
this.legacyValue = legacyValue;
}
public int getLegacyValue() {
return legacyValue;
}
}
Abbildung 3) Funktionen in Java-Enumerationen
Erweiterte for-Schleife
Mit der erweiterten for-Schleife (oder for-each Schleife) kann man über alle Werte
eines Arrays (siehe Abbildung 4) oder einer Sammlung (siehe Abbildung 5) laufen.
int[] numbers = { 5, 7, 23, 42 };
// classic for loop
for (int index = 0; index < numbers.length; ++index) {
int number = numbers[index];
System.out.println(number);
}
// extended for loop
for (int number : numbers) {
System.out.println(number);
}
Abbildung 4) Erweiterte for-Schleife, angewandt auf ein Array
Markus Loeberbauer 2010, 2011
5
Praktikum aus Softwareentwicklung 2
Java
List names = new ArrayList();
names.add("Susi");
names.add("Karl");
// classic collection iteration
Iterator iter = names.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
System.out.println(name);
}
// extended for loop
for (int name : names) {
System.out.println(name);
}
Abbildung 5) Erweiterte for-Schleife, angewandt auf eine Sammlung
Variable Argumentlisten
Es gibt Methoden, die funktionieren mit einem oder mit mehreren Parametern.
Vor Java 5.0 musste man dazu mit einem Array oder einer Klasse arbeiten.
Obwohl das innerhalb der Methode bequem ist, ist es für den Rufer der Methode
aufwändig. Aus diesem Grund kann man seit Java 5.0 eine Methode mit einer
variablen Anzahl von Parametern (vararg-Parameter) definieren. Die Parameter
sind innerhalb der Methode als Array verfügbar. Der Rufer kann die Methode
aufrufen als hätte sie die gewünschte Parameteranzahl. Hat die Methode neben
dem vararg-Parameter auch andere Parameter, dann müssen diese davor definiert
sein (Anmerkung: Es darf nur einen vararg-Parameter geben). Abbildung 6 zeigt
die Verwendung einer variablen Argumentliste.
Markus Loeberbauer 2010, 2011
6
Praktikum aus Softwareentwicklung 2
Java
// classic method with variable parameters
public int sum(int[] values) {
int res = 0;
for (int index = 0; index < values.length; ++index) {
res += values[index];
}
return res;
}
// classic method call
int x = sum(new int[] {1, 2, 3, 4, 5});
// method with variable argument list
public int sum(int... values) {
// same method body as above
}
// method call to method with variable argument list
int x = sum(1, 2, 3, 4, 5);
Abbildung 6) Methoden mit variabler Argumentliste
Markus Loeberbauer 2010, 2011
7
Praktikum aus Softwareentwicklung 2
Subversion
Subversion
Zur Übungsabgabe benutzen wir die Versionsverwaltung Subversion. Die
Zugangsdaten haben Sie auf Ihre Kusss-Email-Adresse bekommen. Ihr SubversionRepository liegt auf dem Server ssw.jku.at, der Pfad setzt sich aus dem Semester,
dem Lehrveranstaltungs-Kürzel und der Matrikel-Nummer zusammen. Für die
Lehrveranstaltung Praktikum aus Softwareentwicklung 2 ist das Kürzel PSW2. Das
Semester besteht aus der Jahreszahl und einem „S“ für Sommer- oder einem „W“
für Winter-Semester. Der Matrikel-Nummer muss ein „k“ vorangestellt werden.
Beispiel: Für das Praktikum aus Softwareentwicklung 2, im Sommersemester 2011
und dem Studenten mit der Matrikel-Nummer 1234567 lautet die Subversion-Url:
svn://www.ssw.uni-linz.ac.at/2011S/PSW2/k1234567/
Voraussetzungen
Die Voraussetzung für die Übungsabgabe mit Subversion ist ein Subversion-Client.
Die folgenden Clients haben wir schon am Institut verwendet:
a. Subversion-Client für die Kommandozeile: http://subversion.apache.org/
b. Subversion-Eclipse-Plugin: http://subclipse.tigris.org/
c. NetBeans unterstützt Subversion
d. IntelliJ IDEA unterstützt Subversion
e. Subversion-Windows-Explorer-Plugin: http://tortoisesvn.tigris.org/
f. Subversion-Applikationen für Mac OS X:
http://versionsapp.com/
http://www.zennaware.com/
Was ist Subversion?
Ein Subversion-Repository kann man sich als Dateisystem vorstellen. Ein
Dateisystem mit Vergangenheit, Änderungen in Dateien können jederzeit
nachvollzogen und alte Versionen von Dateien wiederhergestellt werden.
Markus Loeberbauer 2010, 2011
8
Praktikum aus Softwareentwicklung 2
Subversion
Subversion ist ein zentrales Versionsverwaltungssystem, d.h. die versionierten
Daten liegen auf einem Server. Wenn man mit den Daten arbeiten will holt man
sich eine Arbeitskopie auf den Rechner (checkout). Ist man fertig schreibt man die
Änderungen auf den Server zurück (commit). Als alternative zu zentralen
Versionsverwaltungen gibt es dezentrale Systeme wie zB Mercurial (hg) oder git.
Bei dezentralen Systemen kopiert man das ganze Repository auf seinen Rechner
um damit zu arbeiten.
Vorteile von dezentralen Versionsverwaltungen: Unabhängigkeit von der
Netzverbindung, jede Kopie ist ein Backup, häufig besserer Branch-/MergeSupport. Eignet sich gut für die Verteilte Softwareentwicklung, wie zB bei OpenSource-Projekten.
Vorteile von zentralen Versionsverwaltungen: Einfacher in der Handhabung. Eignet
sich gut für Softwareentwicklung mit wenigen Entwicklern; und Übungsabgaben☺ .
Wichtige Befehle von Subversion
help
Befehlsübersicht und Hilfe zu Befehlen, zB: svn help checkout
import
Importieren eines Pfads in ein Repository, zB: svn import
svn://server.tld/<repoPfad> oder svn import <lokalerPfad>
svn://server.tld/<repoPfad>
add
Datei oder Verzeichnis unter Versionskontrolle stellen, zB: svn add
<lokalerPfad>
move,
Verschieben oder umbenennen von Dateien und Verzeichnissen, zB:
mv
svn mv Main.java Test.java
checkout, Auschecken einer Arbeitskopie, zB: svn co svn://server.tld/<repoPfad>
co
oder svn co svn://server.tld/<repoPfad> <lokalerPfad>
commit,
Einchecken der lokalen Änderungen in das Repository, zB: svn ci
Markus Loeberbauer 2010, 2011
9
Praktikum aus Softwareentwicklung 2
Subversion
ci
update,
Aktualisieren der Arbeitskopie, zB: svn up
up
status, st
Anzeigen der Änderungen in der Arbeitskopie, zB: svn st
delete,
Löschen einer Datei aus der Versionskontrolle, Gegenstück zu add, zB:
del, rm
svn del Main.java
diff
Anzeigen der Änderungen in Dateien
list, ls
Auflisten der Dateien unter Versionskontrolle, zB: svn ls
svn://server.tld/<repoPfad>
log
Änderungsübersicht
revert
Lokale Änderungen rückgängig machen, zB: svn revert Main.java
Markus Loeberbauer 2010, 2011
10
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Graphische Oberflächen
Graphische Oberflächen (GUI) sind teil jeder Desktopanwendung. In Java kann
man eine GUI mit AWT oder Swing erstellen. AWT ist die Grundlage der
graphischen Oberflächen in Java, es bildet die Verbindung zwischen dem
Betriebssystem und der Java VM. Wir werden uns in diesem Kapitel die
Komponente zur Baumdarstellung in der Java Klassenbibliothek ansehen.
Aufbau von Oberflächen in Java
In Java werden die GUI-Komponenten hierarchisch angeordnet, außen ein Fenster,
im Fenster ein Menü und eine Komponente. Diese Komponente kann ein
Container sein und damit weite Komponenten enthalten.
Ein frei wählbarer Layout-Manager (Component.setLayout(LayoutManager)) legt
fest wie die Komponenten in einem Container angeordnet werden. Komponenten
werden immer relativ zu ihrem Container angeordnet. Die X-Koordinate ist die
Entfernung vom linken Rand des Containers zum linken Rand der Komponente.
Die Y-Koordinate ist die Entfernung vom oberen Rand des Containers zum oberen
Rand der Komponente.
Abstract Window Toolkit
Die Klassen des Abstract Window Toolkit (AWT) liegen im Paket java.awt. Das
AWT ist die Grundlage der Graphischen Oberflächen in Java, es abstrahiert die
GUI-Komponenten des Betriebssystems. Für jede unterstützte
Betriebssystemkomponente in AWT gibt es eine Peer-Klasse, die die Verbindung
zu Java herstellt. Java-Programme mit AWT Oberflächen sehen wie nativ
entwickelte Programme aus. Die Nachteile von AWT sind: es werden
Betriebssystem-Ressourcen verwendet (zB: Window Handles), und es werden nur
Komponenten unterstützt die auf allen Plattformen verfügbar sind. Denn eine Java
Anwendung muss unverändert auf allen Plattformen laufen für die eine Java-
Markus Loeberbauer 2010, 2011
11
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Laufzeit-Umgebung existiert. Die Basisklasse aller AWT-Komponenten ist
java.awt.Component.
Swing
Die Klassen von Swing liegen im Paket javax.swing. Klassennamen von
Graphischen Komponenten in Swing beginnen mit einem „J“, wie zum Beispiel:
JTree, JList, JButton. Swing Komponenten zeichnen in Java, nur Basiskomponenten
wie Fenster nutzen AWT und damit Betriebssystem-Ressourcen. Komponenten wie
Buttons oder Listen werden ausschließlich in Java gezeichnet, daher bezeichnen
wir Swing als leichtgewichtig. Die Vorteile dieses Vorgehens sind: es können
beliebige Komponenten gestaltet werden, das Aussehen der Komponenten kann
beliebig gestaltet werden (Pluggable Look-and-Feel). Der Nachteil, Java
Programme sind optisch von nativen Programmen unterscheidbar, auch wenn
versucht wird das native Aussehen so gut wie möglich nachzubilden.
Swing-AWT-Integration
Die Basisklasse aller Swing-Komponenten ist javax.swing.JComponent. Diese leitet
von java.awt.Container ab, damit können Swing-Komponenten in AWTOberflächen eingebettet werden. Das soll aber nur bei vorhandenen AWTAnwendungen genutzt werden, bei Neuentwicklungen sollte man die Oberfläche
ganz in Swing gestalten.
Wichtige Swing-Komponenten
Die folgende Aufzählung gibt eine thematische Übersicht über die graphischen
Komponenten in Swing:

Buttons:
o JButton
o JRadioButton
o JCheckBox

Drop-Down
Markus Loeberbauer 2010, 2011
12
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
o JComboBox

Fenster
o JFrame: mit Rahmen
o JWindow: ohne Rahmen
o JDialog: Dialogfenster, modal und nicht modal
o JFileChooser: Standarddialog, Datei öffnen, Datei speichern

Layout
o JPanel: Container für weitere UI-Komponenten
o JScrollPane, JScrollBar
o Siehe Interface: LayoutManager

Listen
o JComboBox: Dropdown-Liste
o JList: Liste
o JSpinner: selektieren eines Elements, „flache Dropdown-Liste“
o JTable: Tabelle
o JTree: Baum

Menü:
o JMenuBar, JMenu, JMenuItem
o JToolBar
o JPopupMenu: Kontextmenü
o JSeparator: Trennstrich zwischen Menüeinträgen

Statusanzeige
o JProgressBar

Text
o JLabel: kurze Beschreibungstexte
o JTextField: einzeilig, Text-Eingabe/-Ausgabe
o JTextArea: mehrzeilig, Text-Eingabe/-Ausgabe
Markus Loeberbauer 2010, 2011
13
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Fenster Programmatisch Schließen
Unter dem Begriff Fenster schließen kann man verschiede Sachen verstehen: das
Fenster soll unsichtbar werden, das Fenster soll zerstört werden oder das Fenster
soll so reagieren als hätte der Benutzer das Fenster geschlossen. Um das Fenster
zu verstecken muss man auf dem JFrame die Methode void setVisible(boolean)
mit dem Parameter false aufrufen. Ein solches unsichtbares Fenster kann man mit
setVisible(true) wieder anzeigen. Um ein Fenster zu zerstören muss man die
Methode void dispose() aufrufen, diese gibt alle Betriebssystemressourcen des
Fensters frei. Ein solches Fenster kann nicht mehr angezeigt werden. Will man
aber ein Fenster programmatisch schließen und das Verhalten haben als hätte ein
Benutzer das Fenster geschlossen muss man ein WindowEvent auslösen:
final EventQueue eventQueue = Toolkit.getDefaultToolkit()
.getSystemEventQueue();
final WindowEvent event = new WindowEvent(frame,
WindowEvent.WINDOW_CLOSING);
eventQueue.postEvent(event);
Bei diesem Verfahren werden die installierten WindowListener benachrichtigt und
die DefaultCloseOperation (siehe JFrame: void setDefaultCloseOperation(int))
ausgeführt.
Listendarstellung in Java (JList<E>)
Mit JList kann man Listen von gleichartigen Elementen anzeigen. Die Elemente
können als Array, Vector oder ListModel angegeben werden. Will man die Daten
zur Laufzeit ändern sollte man ein ListModel verwenden. JList zeigt standardmäßig
die Ausgabe von toString der Elemente an. Will man die Ausgabe beeinflussen
muss man einen ListCellRenderer implementieren. Einfache Cellrenderer kann man
von DefaultListCellRenderer ableiten. Wenn man viele Einträge in einer JList
anzeigt, dann sollte man alle Zellen gleich groß rendern, um die Darstellung zu
beschleunigen. Die definierte Größe kann man über setFixedCellWidth und
setFixedCellHeight setzen.
Markus Loeberbauer 2010, 2011
14
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Baumdarstellung in Java (JTree)
In vielen Programmen werden Modelle als Baum dargestellt. Die Klassenbibliothek
enthält dafür die Klasse JTree. JTree ist eine graphische Komponente nach dem
Model-View-Controller-Muster und benötigt ein Modell. Solche Modelle müssen
die Schnittstelle TreeModel implementieren. Da Business-Modelle unabhängig von
der GUI-Technologie sein sollen, wird hier oft das Adapter-Muster eingesetzt, um
zwischen Buisiness-Model und TreeModel zu adaptieren.
JTree stellt für jeden Knoten ein Icon und die Ausgabe von toString dar. Das Icon
zeigt an ob ein Knoten Kinder hat oder ein Blatt-Knoten ist. Durch setzen eines
TreeCellRenderers über setCellRenderer kann man die Darstellung ändern. Will
man nur wenig an der Darstellung ändern, dann kann man die Klasse
DefaultTreeCellRenderer beerben.
Scrollen in Java (JScrollPane)
Komponenten die grösser sind als der zur Verfügung stehende Platz werden
rechts unten abgeschnitten. Mit scrollen hat man eine Möglichkeit solche
Komponenten ganz darzustellen. In Java übernimmt JScrollPane diese Aufgabe.
Damit eine Komponente scrollbar wird muss sie in eine JScrollPane gekapselt
werden, zB: new JScrollPane(new JVeryBigComponent()). Und die Komponente
muss ihre Größe für die JScrollPane berechnen können. Dazu kann man die
Methode getPreferredSize überschreiben. Oder wenn man mehr Einfluss auf die
JScrollPane haben will das Interface Scrollable implementieren.
Wenn man am sichtbaren Ausschnitt der JScrollPane interessiert ist, kann man den
JViewport mit getViewport abfragen. Änderungen am Viewport kann man mit
einem ChangeListener überwachen.
Zeichnen eigener Komponenten in Swing
Viele Anwendungen haben komplexe Daten die man mit Standard-Komponenten
nur schlecht darstellen kann. In diesem Fall kann man eine eigene Swing
Markus Loeberbauer 2010, 2011
15
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Komponente implementieren, indem man von JComponent ableitet. Swing
zeichnet Komponenten über die Methode paintComponent. Überschreibt man
paintComponent kann man seine eigene Visualisierung machen. Achtung, man
sollte immer super.paintComponent aufrufen, damit zum Beispiel der Hintergrund
richtig dargestellt wird.
Oft wird in Beispielen von JPanel abgeleitet, anstelle von JComponent. JPanel ist
eine fertige Klasse, es ist gedacht sie zu verwenden; JComponent ist abstrakt und
es ist gedacht sie zu erweitern. Unterschiede zwischen den Klassen sind zum
Beispiel: JPanel zeichnet die Hintergrundfarbe, JComponent nicht (kann man in
der Ableitung machen); JPanel hat als Layout FlowLayout installiert, JComponent
keines; und für JPanels wird als „Look And Feel“ (L&F) PanelUI benutzt. Das kann
unvorhergesehene Auswirkungen haben, weil das L&F des Panels die abgeleitete
Komponente beeinflusst. Dabei ist zu beachten, dass L&Fs getauscht werden
können!
In Java zeichnet man mit der Klasse Graphics, ein Objekt dieser Klasse bekommt
die Methode paintComponent übergeben. Mit diesem Objekt kann man die
Zeichenfarbe setzen und Graphikprimitive wie beispielsweise Rechtecke, Ellipsen
und Text zeichnen. Das Koordinatensystem startet in der linken oberen Ecke der
Komponente mit (0, 0). Die X-Koordinate wächst nach rechts, die Y-Koordinate
nach unten. Das Graphics-Objekt ist so konfiguriert, dass man nur innerhalb der
vorgegebenen Komponente zeichnen kann (Clipping).
Das Graphics-Objekt darf nur genutzt werden solange die Zeichenmethode aktiv
ist. Speichert man das Objekt in einem Feld und benutzt es später ist das
Verhalten undefiniert.
Ereignisbehandlung
Java setzt das Observer-Muster in seinem Listener-Konzept um. Listener werden in
Interfaces definiert und sollen vom Interface EventListener ableiten. EventListener
Markus Loeberbauer 2010, 2011
16
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
ist ein Marker-Interface, d.h. es enthält keine Methoden. Eine Event-Methode hat
üblicherweise einen Parameter der von EventObject erbt. EventObject speichert
den Ereignis-Sender, alles Weitere speichert man in der abgeleiteten Event-Klasse.
Anmelden an ein Ereignis
Klassen die Ereignisse auslösen, haben Methoden mit denen man sich als Listener
anmelden und abmelden kann. Zum Beispiel löst JComponent MouseEvents aus
dafür können sich Listener mit den Methoden addMouseListener anmelden und
removeMouseListener abmelden.
Auslösen von Ereignissen
Löst ein Objekt Ereignisse aus, muss es eine Methode implementieren mit der sich
ein Listener anmelden und eine Methode mit der er sich wieder abmelden kann.
Die Methode zum Anmelden muss add<ListenerName> und die Methode zum
Abmelden remove<ListenerName> heißen. Unterstützt eine Klasse zum Beispiel
MouseListener dann müssen die Methoden addMouseListener und
removeMouseListener heißen. Außerdem muss es die Methode fire<EventName>
geben mit der die Events verschickt werden können. Meistes ist die fire-Methode
protected, damit ableitende Klassen das Ereignis auslösen können.
Listener-Interface
Für jedes mögliche Ereignis muss ein Listener-Interface existieren. ListenerInterfaces können beliebige Methoden enthalten. Zum Beispiel hat MouseListener
die Methoden mouseClicked, mousePressed und mouseReleased. Die Methoden
müssen als Parameter ein Ereignis-Objekt haben, zB: im MouseListener ein
MouseEvent. Listener-Interfaces müssen von dem Marker-Interface
java.util.EventListener erben.
Ereignis-Objekte
Für jedes Ereignis muss ein Ereignis-Objekt erstellt und an alle Listener verschickt
werden. Jeder Listener erhält dasselbe Ereignis-Objekt, daher müssen EreignisObjekte unveränderbar (immutable) sein. Ereignis-Objekte in Java erben von der
Markus Loeberbauer 2010, 2011
17
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Klasse EventObject. Diese Klasse implementiert Serializable, daher müssen nichtserialisierbare Felder als transient markiert werden.
Push oder Pull?
Ereignisse sind häufig mit Änderungen in einem Datenmodell verbunden. Soll
man die Änderung gleich im Event übertragen (Push)? Oder soll der Interessierte
das Datenmodell selbst inspizieren (Pull)? Eine klare Antwort darauf gibt es nicht.
Man muss von Fall zu Fall unterscheiden was die bessere Alternative ist. Push ist
für den Event-Empfänger häufig bequemer, während Pull bei Änderungen des
Datenmodells stabil ist. Hinweis: Bei der Entscheidung Push oder Pull sollte man
auch an die Serialisierbarkeit der gepushten Objekte denken.
Java2D
Seit Java 1.2 übernimmt Java2D die Zeichenaufgaben in Java, es ist aber
kompatibel zu dem Render-Verhalten von Java 1.1. Java2D ermöglicht zB:
Abstraktion von physikalischen Pixeln, Transformationen, Effekte und Antialiasing.
Der Einstiegspunkt in Java2D ist die Klasse Graphics2D.
Seit Java 1.2 ist jedes Graphics-Objekt ein Graphics2D-Objekt und kann daher
gecastet werden. Graphics wird nur aus Kompatibilitäts-Gründen in der
Schnittstelle übergeben.
Mit Graphics2D kann man die Strichart ändern (setStroke); Transformationen wie
zB Skalieren, Rotieren anbringen (getTransform); das Füllmuster festlegen
(setPaint); den Clipping-Bereich setzen; und die Komposition bestimmen
(setComposite). Unter Komposition versteht man: wie vorhandene Farben auf der
Zeichenfläche mit der aktuellen Zeichenoperation verbunden werden sollen.
Beispielsweise kann man das neue Element darüber oder darunter legen oder
verschiedene Schnitte machen, siehe java.awt.AlphaComposite.
Markus Loeberbauer 2010, 2011
18
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Benötigt man ein spezielles Clipping oder spezielle Transformationen sollte man
mit einer Kopie des übergebenen Graphics-Objekts arbeiten. Eine Kopie kann mit
der Methode Graphics.create anlegen werden.
Schrift
Text wird mit der Methode Graphics.drawString ausgegeben. Die Schriftart
(java.awt.Font) kann man mit Graphics.setFont setzen. Zeichnet man eine
komplexe Komponente die Text enthält, dann braucht man häufig die Länge des
auszugebenden Texts. Damit man umbrechen oder beispielsweise abkürzen kann.
Solche Maßzahlen über Text kann man über die Klasse FontMetrics erhalten.
Objekte der Klasse FontMetrics kann man über Graphics.getFontMetrics abfragen.
Bilder
Bilder werden mit der Methode Graphics.drawImage gezeichnet. Bilder kann man
in Java als BufferedImage erzeugen, und über ein Graphics2D-Objekt verändern.
Ein Graphics2D-Objekt erhält man über die Methoden getGraphics und
createGraphics. Die beiden Methoden liefern das gleiche Objekt, allerdings liefert
getGraphics das Objekt aus Kompatibilitätsgründen als Graphics-Objekt.
Häufig muss man Bilder aus Dateien laden oder in Dateien speichern. Laden kann
man Bilder mit der Methode ImageIO.read, speichern mit ImageIO.write.
Copy & Paste
Mit Copy & Paste kann man Daten von einem Programm in ein anders
übertragen. In Java sind die dafür notwendigen Klassen im Paket
java.awt.datatransfer. Das System-Clipboard bekommt man über
Toolkit.getDefaultToolkit().getSystemClipboard(). Den gespeicherten Wert kann
man aus dem Clipboard mit Transferable getContents(Object) abfragen und mit
void setContents(Transferable, ClipboardOwner) setzen. Das Objekt bei
getContents gibt an wer die Daten haben möchte, da dieses Objekt aber nicht
benutzt wird kann man null übergeben. Bei setContents ist der erste Parameter
Markus Loeberbauer 2010, 2011
19
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
das Objekt, das man in das Clipbord legen will und der zweite Parameter ein
Callback um darüber informiert zu werden wenn das Objekt im Clipboard
überschrieben wird. Braucht man diese Information nicht, dann kann man als
ClipboardOwner null übergeben.
Das Clipboard kann verschiedene Formate von Daten halten, zB String oder Bilder,
das Clipboard dieselben Daten auch gleichzeitig in verschiedenen Formaten
halten. Ob die Daten im Clipboard gerade in einem speziellen Format vorliegen
kann man mit boolean Transferable.isDataFlavorSupported(DataFlavor) prüfen.
DataFloavor definiert Konstanten für die üblichen Formate wie zB String, Liste mit
Dateien Bild.
Hier ein Beispiel wie man Strings vom Clipboard lesen und ins Clipboard
schreiben kann:
void writeHelloWorldToClipboard() {
final Clipboard clip =
Toolkit.getDefaultToolkit().getSystemClipboard();
clip.setContents(new StringSelection("Hello World!"), null);
}
void printContentFromClipboard() {
final Clipboard clip =
Toolkit.getDefaultToolkit().getSystemClipboard();
final Transferable content = clip.getContents(null);
if (content != null &&
content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
final String text = (String)
content.getTransferData(DataFlavor.stringFlavor);
System.out.println(text);
} catch (final UnsupportedFlavorException e) {
System.out.println("Data no longer available in requested flavor.");
} catch (final IOException e) {
System.out.println("Requested flavor not supported.");
}
}
}
Swing und Threads
Das Swing-Framework ist darauf ausgerichtet, dass man einfach graphische
Komponenten entwickeln kann. Diese Ausrichtung hat zur Folge, dass SwingMarkus Loeberbauer 2010, 2011
20
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
Komponenten nicht Threadsicher sind, d.h. der Aufbau, alle Änderungen und alle
Abfragen müssen aus einem Thread erfolgen. Java hat dafür einen dedizierten
Thread, den Event Dispatcher Thread, dieser Thread wird oft auch EDT, AWTThread, GUI-Thread oder Swing-Thread genannt. Einfache Programme (zB
Übungsbeispiele) kommen mit diesem einen Thread aus. Realistisch komplexe
Programme haben aber Threads, damit langlaufende Prozesse (zB NetzwerkKommunikation, Drucken, Berechnungen) die graphische Oberfläche nicht
blockieren. Müssen Threads Änderungen an der GUI machen, dann müssen sie
diese Aufgabe über die Klasse SwingUtilities an den GUI-Thread delegieren. Mit
der Klasse SwingUtilities kann man zB: abfragen ob der aktuelle Thread der GUIThread ist (boolean isEventDispatchThread()) und eine Aufgabe an den GUIThread delegieren (void invokeLater(Runnable doRun), void
invokeAndWait(Runnable doRun)).
Swing-GUI Richtig Anlegen
In vielen Tutorials steht, dass man die GUI in einem beliebigen Thread anlegen
kann und man erst nachdem die GUI über setVisible(true) angezeigt (=realisiert)
wurde mit dem GUI-Thread arbeiten muss. Es kann aber bereits während man die
GUI anlegt durch Listener über den GUI-Thread auf die GUI zugegriffen werden,
d.h. es wird gleichzeitig auf GUI-Komponenten aus mehreren Threads zugegriffen,
was verboten ist. Daher muss man die GUI bereits im GUI-Thread aufbauen. Ein
guter Artikel (Swing threading and the event-dispatch thread) von John Zukowski
dazu ist: http://www.javaworld.com/javaworld/jw-08-2007/jw-08swingthreading.html. Hier ein Beispiel wie man eine GUI threadsicher aufbauen
kann:
public class GoodCreateGUI {
// Use such a method to show GUIs.
public static void show() {
SwingUtilities.invokeLater(createInitTask());
}
// Use such a method if you need the GUI to be ready
// after the method call, e.g. in Applets.
Markus Loeberbauer 2010, 2011
21
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
public static void showSynchronous() throws InvocationTargetException,
InterruptedException {
SwingUtilities.invokeAndWait(createInitTask());
}
private static Runnable createInitTask() {
return new Runnable() {
@Override
public void run() { new GoodCreateGUI().initAndShow(); }
};
}
// Use such a method to show GUIs where you
// need the GUI object outside.
public static GoodCreateGUI createAndShow() throws
InvocationTargetException, InterruptedException {
final GoodCreateGUI gui = new GoodCreateGUI();
SwingUtilities.invokeAndWait(createInitTask(gui));
return gui;
}
private static Runnable createInitTask(final GoodCreateGUI gui) {
return new Runnable() {
@Override
public void run() { gui.initAndShow(); }
};
}
private GoodCreateGUI() {
// nothing to do
}
private void initAndShow() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 60);
frame.add(new JButton("Hello!"));
frame.setVisible(true);
}
}
Änderungen einer Swing-GUI aus einem Thread
Läuft eine Aktion lange, dann soll man diese Aktion in einen eigenen Thread
auslagern damit die GUI reaktionsfähig bleibt. Hier ein Beispiel wie man eine GUI
threadsicher verändern kann:
public class TaskGoodGUI {
public static void show() {
SwingUtilities.invokeLater(createInitTask());
}
private static Runnable createInitTask() {
return new Runnable() {
@Override
public void run() { new TaskGoodGUI().initAndShow(); }
};
}
private TaskGoodGUI() {
Markus Loeberbauer 2010, 2011
22
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
// nothing to do
}
private void initAndShow() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JButton startTaskButton = new JButton();
startTaskButton.setAction(createStartTaskAtion(startTaskButton));
frame.add(startTaskButton);
frame.pack();
frame.setVisible(true);
}
private Action createStartTaskAtion(final JButton startTaskButton) {
return new AbstractAction("Start Task!") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
startTaskButton.setText("Working ...");
final Runnable statusUpdater = new Runnable() {
@Override
public void run() {
startTaskButton.setText("Start Task! (Set from GUI Thread: "
+ SwingUtilities.isEventDispatchThread() + ")");
}
};
final Runnable worker = new Runnable() {
@Override
public void run() {
doSomethingLong();
SwingUtilities.invokeLater(statusUpdater);
}
};
new Thread(worker).start();
}
};
}
private synchronized void doSomethingLong() {
try {
System.out.println("Start");
Thread.sleep(5000);
System.out.println("Done");
} catch (final InterruptedException e) {
Logger.getLogger(TaskGoodGUI.class.getName()).log(Level.SEVERE,
e.getMessage(), e);
}
}
}
Langlaufende Aufgaben die die Swing-GUI verändern
Läuft eine Aktion lange und man lagert sie in einen Thread aus, dann möchte
man oft die GUI aktualisieren wenn Zwischenergebnisse hat. Hier ein Beispiel wie
man aus einer langlaufenden Aufgabe eine GUI threadsicher verändern kann:
Markus Loeberbauer 2010, 2011
23
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
public class WorkerComplexGUI {
private static final int MAX_PARTS = 5;
public static void show() {
SwingUtilities.invokeLater(createInitTask());
}
private static Runnable createInitTask() {
return new Runnable() {
@Override
public void run() {
new WorkerComplexGUI().initAndShow();
}
};
}
private WorkerComplexGUI() {
// nothing to do
}
private void initAndShow() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JProgressBar progressBar = new JProgressBar();
progressBar.setMaximum(MAX_PARTS);
frame.add(progressBar, BorderLayout.SOUTH);
final JButton startTaskButton = new JButton();
startTaskButton.setAction(createStartTaskAtion(startTaskButton,
progressBar));
frame.add(startTaskButton);
frame.pack();
frame.setVisible(true);
}
private Action createStartTaskAtion(final JButton startTaskButton,
final JProgressBar progressBar) {
return new AbstractAction("Start Task!") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
startTaskButton.setText("Working ...");
final SwingWorker<Void, Integer> worker =
new SwingWorker<Void, Integer>() {
@Override
protected Void doInBackground() throws Exception {
progressBar.setIndeterminate(true);
for (int part = 1; part <= MAX_PARTS; ++part) {
doSomethingLong(part);
publish(part);
}
return null;
Markus Loeberbauer 2010, 2011
24
Praktikum aus Softwareentwicklung 2
Graphische Oberflächen
}
@Override
protected void process(List<Integer> chunks) {
progressBar.setIndeterminate(false);
progressBar.setValue(chunks.get(0));
}
@Override
protected void done() {
startTaskButton.setText("Start Task! (Set from GUI Thread: "
+ SwingUtilities.isEventDispatchThread() + ")");
progressBar.setValue(0);
}
};
worker.execute();
}
};
}
private synchronized void doSomethingLong(final int part) {
try {
System.out.println("Start " + part);
Thread.sleep(1000);
System.out.println("Done " + part);
} catch (final InterruptedException e) {
Logger.getLogger(WorkerComplexGUI.class.getName()).log(
Level.SEVERE, e.getMessage(), e);
}
}
}
Markus Loeberbauer 2010, 2011
25
Praktikum aus Softwareentwicklung 2
Dateien
Dateien
Über die Klasse File kann in Java auf das Dateisystem zugegriffen werden. File ist
eine reine Verwaltungsklasse, ein Objekt enthält nur Informationen über eine
Datei, aber keinen Inhalt. Mit File kann man beispielsweise feststellen ob eine
Datei existiert, lesbar und schreibbar ist. Die Klasse File enthält die statischen
Hilfsmethoden: listRoots, sie liefert die Laufwerke des Computers und
createTempFile, mit ihr kann man temporäre Dateien anlegen.
Markus Loeberbauer 2010, 2011
26
Praktikum aus Softwareentwicklung 2
Reflection
Reflection
Mit Reflection kann der Programmierer zur Laufzeit auf Typinformationen
zugreifen, zB: Felder, Methoden und Konstruktoren. Über Reflection kann man
aber auch Objekte erzeugen, Methoden aufrufen, Felder lesen und schreiben. Mit
dynamischen Proxies (java.lang.reflect.Proxy) kann man zur Laufzeit Objekte
erzeugen, die gegebene Interfaces implementieren. Die benötigten Klassen sind in
den Packages java.lang.reflect und java.lang.
Einsatzgebiete
Die JavaVM nutzt Typinformation beispielsweise zur Speicherbereinigung (garbage
collection). In der Java Klassenbibliothek wird Reflection benutzt um Klassen
nachzuladen (java.util.ServiceLoader). Programmier können sie benutzen um
Werkzeuge zu bauen, zB: Klassen-Browser, Test-Frameworks (zB: JUnit) und
Debugger.
Verwendet man Reflection sollte man sich aber der Nachteile bewusst sein zB:
Geschwindigkeitsverlust durch Klassen-Analyse zur Laufzeit sowie
Sicherheitsprobleme, über Reflection kann zB auf private Elemente einer Klasse
zugegriffen werden.
Class
Der Einstiegspunkt in Reflection ist die Klasse Class. Ein Objekt dieser Klasse erhält
man über:
a. <Klassenname>.class, zB: ArrayList.class
b. <Wrapper für einen primitiven Datentyp>.TYPE, zB Integer.TYPE entspricht
int.class
c. ein Objekt mit der Methode getClass()
d. Class.forName("Klassenname als String"), zB:
Class.forName("java.util.ArrayList")
Markus Loeberbauer 2010, 2011
27
Praktikum aus Softwareentwicklung 2
Reflection
Über das Class-Objekt kann man die Elemente in einer Klasse abfragen: Methoden
(getMethods, getDeclaredMethods), Felder (getFields, getDeclaredFields),
Konstruktoren (getConstructors, getDeclaredConstructors), innere Klassen
(getClasses, getDeclaredClasses). Die Methoden mit den Namen get<…> liefer alle
public-Elemente inklusive der geerbten. Die Methoden mit den Namen
getDeclared<…> liefern alle Elemente einer Klasse, unabhängig von ihrer
Sichtbarkeit, aber keine geerbten.
Von allen Elementen kann man mit getDeclaringClass auf die deklarierende Klasse
zugreifen.
Elemente einer Klasse
Reflektierte Elemente kann man weiter untersuchen, zB: Methoden auf
Rückgabetyp (getReturnType) und Parametertypen (getParameterTypes); oder man
kann damit arbeiten:

Constructor, anlegen neuer Objekte (newInstance)

Method, aufrufen einer Methode (invoke)

Field, lesen und schreiben (get, set; und für primitive Datentypen
get<Type>, set<Type>, zB: getInt, setInt)
Auf private Elemente einer Klasse kann über Reflection zugegriffen werden, dazu
muss man setAccessible(true) setzen. Ob ein Element zugreifbar ist kann über
isAccessible abgefragt werden.
Dynamischer Proxy
Ein dynamischer Proxy ist eine Klasse, die eine Liste von Interfaces zur Laufzeit
implementiert. Java leitet Aufrufe an einen dynamischen Proxy an ein HandlerObjekt (Interface InvokationHandler) weiter. Einsatzgebiete für dynamische Proxies
sind zB: Methoden über das Netzwerk aufrufen (Remote Method Invokation),
Testen und Loggen von Methodenaufrufen.
Markus Loeberbauer 2010, 2011
28
Praktikum aus Softwareentwicklung 2
Reflection
Dynamische Proxies können über die Methode Proxy.newProxyInstance angelegt
werden.
Annotationen
Seit Java 1.5 haben Programmierer die Möglichkeit Metainformationen zu
Elementen wie zB: Klassen und Methoden anzugeben. Annotationen sind eine
Alternative zu den vorher benutzten Marker-Interfaces. Marker-Interfaces sind
Interfaces ohne Methoden, sie werden Implementiert, um Aussagen über Klassen
zu treffen. Beispiele für Marker-Interfaces sind Serializable und Cloneable.
Annotation bieten aber mehr Möglichkeiten als Marker-Interfaces. Annotationen
können auf Klassen, Konstruktoren, Methoden, Felder, lokale Variablen, Packages,
Parameter und Annotationen angewendet werden. Diese Elemente werden unter
dem Interface AnnotatedElement zusammengefasst. Pro Element können beliebig
viele Annotationen angebracht werden, aber nur jeweils eine Annototation pro
Annotationsart. Annotationen können mit Konstanten parametriert werden,
folgende Typen sind erlaubt: primitive Datentypen, String, Class, Enums,
Annotationen sowie eindimensionale Arrays dieser Typen.
Arbeiten mit Annotationen
Annotationen werden wie Interfaces definiert, allerdings wird dem Schlüsselwort
interface ein "@" vorangestellt. Die Parameter der Annotationen werden auf
Methoden mit Rückgabetyp abgebildet, wobei ein Default-Wert angegeben
werden kann.
Java bringt folgende Annotationen mit, mit denen man eigene Annotationen
beschreiben kann:

@Target: Welche Elemente können annotiert werden, zB:
@Target(ElementType.TYPE) oder @Target({ElementType.TYPE,
ElementType.METHOD})

@Documented: Soll die Annotation in die JavaDoc aufgenommen werden
Markus Loeberbauer 2010, 2011
29
Praktikum aus Softwareentwicklung 2

Reflection
@Inherited: Soll die Annotation an Subklassen vererbt werden. Das
funktioniert nur für Annoationen die auf Klassen angebracht werden.

@Retention: Wie lange soll die Annotation abfragbar sein, zB:
nur im Quellcode @Retention(RetentionPolicy.SOURCE),
in der Klassendatei @Retention(RetentionPolicy.CLASS) oder
auch zur Laufzeut über Reflection @Retention(RetentionPolicy.RUNTIME)
Beispiel-Annotation mit einem int-Wert und einem String-Wert:
@interface SampleAnnotation {
int value();
String stringProp() default "default";
}
Anwenden der Annotation:
@SampleAnnotation(value=1, stringProp = "test")
public class Test {}
Werte mit default-Klausel können ausgelassen werden:
@SampleAnnotation(value=1)
public class Test {}
Muss nur ein Wert gesetzt werden und dieser Wert hat den Namen "value", dann
kann dieser Wert ohne Namen gesetzt werden:
@SampleAnnotation(1)
public class Test {}
Markus Loeberbauer 2010, 2011
30
Praktikum aus Softwareentwicklung 2
Threads
Threads
Threads sind parallele, oder auf Rechnern mit nur einer CPU quasi-parallele,
Programmabläufe in Java. Sie können beispielsweise benutzt werden, um mehrere
Anforderungen auf einem Server abzuarbeiten, Hintergrundtätigkeiten wie
Animationen durchzuführen oder lang-laufende Aufgaben von der graphischen
Benutzerschnittstelle zu entkoppeln.
Basisklassen
Die Java Klassenbibliothek enthält folgende Klassen zum Umgang mit Threads:
java.lang.Thread

Thread-Objekte bilden Threads ab; und bieten programmatischen Zugriff
darauf, zB: starten (start), unterbrechen (interrupt), abgeben der Kontrolle
(yield) und setzen der Priorität (setPriority).

Die Klasse hat statische Hilfsmethoden, mit denen man auf den aktuellen
Thread zugreifen kann.

Auf Betriebssystemen die Threads unterstützen werden diese genutzt.
java.lang.Runable

Aufgaben die in einem Thread ausgeführt werden müssen in Objekte
gekapselt werden. Diese Objekte müssen das Interface Runnable
implementieren.

Pro Thread kann eine Aufgabe im Konstruktor übergeben werden.
java.lang.Object

Implementiert einen Monitor, d.h. jedes Objekt kann zur ThreadSynchronisation genutzt werden.
java.lang. InterruptedException
Markus Loeberbauer 2010, 2011
31
Praktikum aus Softwareentwicklung 2

Threads
Wird geworfen wenn ein Thread schläft oder wartet und von außen
unterbrochen wird.
Anlegen eines Threads
Die Klasse Thread verwaltet Threads in Java. Will man einen Thread in Java starten
muss man ein Objekt dieser Klasse anlegen und darauf die Methode start
aufrufen. Ein Thread-Objekt kann nur einmal gestartet werden, sobald der Thread
seine Aufgabe abgearbeitet hat ist er tot und kann nicht mehr verwendet werden.
Das Interface Runnable ist die Schnittstelle für Aufgaben. Runnable enthält nur die
Methode void run(). Benötigt man Parameter oder einen Rückgabewert, dann
muss man diese als Felder im Objekt ablegen.
Beispiel: Anlegen eines Threads der die Zahlen von 1 bis 100 ausgibt.
Definieren der Aufgabe als Runnable:
public class CounterTask implements Runnable {
public void run() {
for (int i = 1; i <= 100; ++i) {
System.out.println(i);
}
}
}
Anlegen und starten des Threads:
Thread counterThread = new Thread(new CounterTask());
counterThread.start();
Die Klasse Thread kann auch erweitert werden, wenn man eine spezielle Art von
Threads braucht, zB Threads die Zeitmessungen machen oder Threads die
Ereignisse auslösen. Diese Erweiterbarkeit kann auch verwendet werden, um einen
Thread mit einer Aufgabe zu versehen. Allerdings ist diese Art der Erweiterung im
Markus Loeberbauer 2010, 2011
32
Praktikum aus Softwareentwicklung 2
Threads
objektorientierten Sinn falsch und aus diesem Grund in anderen Programmiersprachen, wie beispielsweise C#, unmöglich.
Negativ-Beispiel: Anlegen einen Threads der die Zahlen von 1 bis 100 ausgibt, als
Thread-Ableitung.
public class CounterThread extends Thread {
public void run() {
for (int i = 1; i <= 100; ++i) {
System.out.println(i);
}
}
}
CounterThread counterThread = new CounterThread();
counterThread.start();
Unterbrechen eines Threads
Es gibt Threads die ihre Aufgabe so lange ausführen bis sie von außen
unterbrochen werden. Zum Beispiel Server-Threads die Client-Anfrage abarbeiten.
Einen Thread kann man zuverlässig und sicher abbrechen lassen, indem man in
der Verarbeitungs-Schleife Thread.interrupted() prüft oder ein als volatile
markiertes Feld ausliest.
Reagiert der Thread auf Thread.interrupted(), dann kann der Thread von außen
über die Methode interrupt beendet werden. Liest der Thread ein volatile Feld
aus, dann kann man von außen auf dieses Feld schreiben um den Thread zu
beenden.
Es ist auch möglich einen Thread über die Methode stop zu beenden. Dabei wird
der Thread allerdings ohne Vorwarnung gestoppt, ohne die Möglichkeit zu haben
begonnene Aufgaben abzuschließen, was zu inkonsistenten Datenmodellen führt.
Korrekter Umgang mit Thread.interrupted():
public class Exiter implements Runnable
Markus Loeberbauer 2010, 2011
33
Praktikum aus Softwareentwicklung 2
Threads
public void run() {
while(!Thread.interrupted()) {
// Endless loop
}
}
}
oder, falls in der Endlosschleife eine InterruptedException auftreten kann
public class Exiter implements Runnable
public void run() {
while (!Thread.interrupted()) {
try {
// do something
sleep(1000); // may throw an InterruptedException
} catch (InterruptedException e) {
// Call interrupt() to set interrupted()
interrupt();
}
// finish work
}
}
}
Korrekter Umgang mit einem volatile Feld:
volatile boolean exit;
private class Exiter implements Runnable {
public void run() {
while (!exit) {
// Endless loop
}
}
}
Synchronisation
In Java nutzen alle Threads einen gemeinsamen Speicherbereich, bei gemeinsam
genutzten Objekten muss der Zugriff daher synchronisiert werden.
Synchronisation kann auf Methoden- und Block-Ebene erfolgen. Synchronisiert
man auf Blockebene, muss explizit ein Objekt angeben werden auf das
Markus Loeberbauer 2010, 2011
34
Praktikum aus Softwareentwicklung 2
Threads
synchronisiert werden soll. Synchronisiert man auf Methodenebene wird das thisObjekt benutzt. Handelt es sich um eine statische Methode wird das KlassenObjekt benutzt. Synchronisation auf Blockebene ist flexibler, weil man bestimmen
kann welches Objekt zur Synchronisation benutzt werden soll; und sie ist sicherer,
weil man das Synchronisationsobjekt lokal halten kann.
Synchronisierter Block
Synchronisierte Methode
Object obj = new Object();
synchronized void bar() {
// do critical stuff here
}
void foo() {
// uncritical stuff
synchronized(obj) {
// do critical stuff here
}
// uncritical stuff
}
// equivalent to
void bar() {
synchronized(this) {
// do critical stuff here
}
}
Bedingtes Warten
Muss in einem Thread auf eine Bedingung gewartet werden bevor weiter
gearbeitet werden kann, muss man mit einem Monitor arbeiten. Threads können
auf einen Monitor warten und wartende Threads benachrichtigen. Jedes Objekt in
Java ist ein Monitor, dazu sind in Objekt die Methoden wait, wait(timeout),
wait(timeout, nanos), notify und notifyAll vorhanden.
Die Methode wait blockiert den Thread bis er über den Monitor notifiziert wird;
oder der Thread mit interrupt unterbrochen wird. Möchte man maximal nur eine
gewisse Zeit warten kann man die Methode wait(timeout) oder wait(timeout,
nanos) benutzen.
Die Methode notify benachrichtigt einen Thread der auf den Monitor wartet, die
Auswahl des Threads erfolgt zufällig. Mit der Methode notifyAll werden alle
wartenden Threads benachrichtigt.
Markus Loeberbauer 2010, 2011
35
Praktikum aus Softwareentwicklung 2
Threads
Beispiel: Überweisen eines Geldbetrags. Wobei am Quellkonto genug Geld
vorhanden sein muss.
public class Bank {
private Object lock = new Object();
private Account[] accounts;
// ...
public void transfer(int from, int to, int amount)
throws InterruptedException {
synchronized(lock) {
while (accounts[from] < amount) {
lock.wait();
}
accounts[from] -= amount;
accounts[to] += amount;
lock.notifyAll();
}
}
}
In diesem Beispiel sieht man warum notifyAll wichtig ist. Bevor von einem Konto
etwas abgebucht werden kann muss genügend Geld vorhanden sein. Das
bedeutet eine Überweisung ist eventuell von einer anderen Überweisung
abhängig. Würde man hier nur notify verwenden könnten die Threads in eine
Blockierung geraten. Mit notifyAll haben alle Threads die Möglichkeit ihre
Bedingung zu prüfen.
Warten auf einen Thread
Teilt man eine Aufgabe auf mehrere Threads auf, dann muss man, spätestens
sobald man das Ergebnis braucht, warten bis alle Threads fertig sind. Dazu kann
man auf Thread-Objekten die Methode join aufrufen.
Beispiel:
Markus Loeberbauer 2010, 2011
36
Praktikum aus Softwareentwicklung 2
Threads
// start an extra thread
Thread t = new Thread(...);
t.start();
// concurrent execution
t.join();
// thread t is dead
Zustände eines Threads

neu: erzeugt aber noch nicht gestartet

lauffähig
o aktiv: wird gerade ausgeführt
o bereit: kann ausgeführt werden und wartet auf Zuteilung des
Prozessors
blockiert

o schlafend: mit sleep schlafen gelegt
o IO-blockiert: wartet auf Beendigung einer IO-Operation
o wartend: wurde mit wait in den wartenden Zustand versetzt
o gesperrt: Wartet auf die Aufhebung einer Objekt-Sperre
o suspendiert: durch suspend vorübergehend blockiert
Achtung: ist veraltet und sollte nicht verwendet werden
tot: run-Methode ausgelaufen

blockiert (blocked)
d()
en
sp
g
un
eis
it-A
nw
e ()
su
roniz
yn ch
um
tify
All
tspe
rre (s
re s
no
Obje
k
p ()
rre
tspe
suspendiert
(suspended)
n
otif
y/
ben
slee
chen
Obje
k
wartend
(waiting)
Aufh
e
aufwa
Ende IO-Operation
ed)
gesperrt
(locked)
wa
IO-blockiert
(IO-blocked)
IO-Opertion
schlafend
(sleeping)
aktiv
(active)
bereit
(ready)
neu
(new)
run terminiert
start()
lauffähig (runnable)
tot
(dead)
Markus Loeberbauer 2010, 2011
37
Praktikum aus Softwareentwicklung 2
Threads
Singletons Threadsicher Anlegen
Das Singleton-Muster ist eines der einfachsten Entwurfsmuster. Das Muster stellt
sicher, dass von einer Klasse nur maximal ein Objekt existiert. In Anwendungen
mit mehreren Threads kann man dieses Objekt threadsicher in einer statischen
Feldinitialisierung oder im statischen Konstruktor (static Initializer) der Klasse
anlegen. Will man das Objekt erst anlegen wenn es das erste Mal benötigt wird
muss man es in einem synchronisierten Bereich anlegen.
Beispiel für ein Singleton das in einer statischen Feldinitialisierung angelegt wird:
public class FieldSingletonTest {
private static FieldSingletonTest instance = new FieldSingletonTest();
private FieldSingletonTest() { /* keep private */ }
// ...
public static FieldSingletonTest getInstance() {
return instance;
}
}
Beispiel für ein Singleton das im statischen Konstruktor angelegt wird:
public class StaticInitSingletonTest {
private static StaticInitSingletonTest instance;
private StaticInitSingletonTest() { /* keep private */ }
// ...
static {
instance = new StaticInitSingletonTest();
// initialize the instance ...
}
public static StaticInitSingletonTest getInstance() {
return instance;
}
}
Beispiel für ein Singleton das bei der ersten Verwendung angelegt wird:
public class SyncInitSingletonTest {
private static final Object lock = new Object();
private static SyncInitSingletonTest instance;
private SyncInitSingletonTest() { /* keep private */ }
// ...
public static SyncInitSingletonTest getInstance() {
synchronized (lock) {
if (instance == null) {
instance = new StaticInitSingletonTest();
Markus Loeberbauer 2010, 2011
38
Praktikum aus Softwareentwicklung 2
Threads
// initialize the instance ...
}
return instance;
}
}
}
Threading ab Java 5
Ab Version 5 gibt es in Java viele neue Klassen und Schnittstellen mit denen man
Aufgaben parallelisieren kann, hier eine Kurze Übersicht:

java.util.concurrent.Callable<V>: ein genisches Interface das eine Aufgabe
mit Rückgabewert kapselt. Mit anderen Worten, ein Runnable mit
Rückgabewert.

java.util.concurrent.Future<V>: ein generische Interface, das eine
asynchrone Berechnung kapselt. Mit einer Future kann man prüfen ob die
Aufgabe beendet ist, sowie das Ergebnis der Berechung abfragen.

java.util.concurrent.ExecutorService: ein Interface für Threadpools, an ein
ExecutorService kann man mit submit und execute Aufgaben an den
Threadpool übergeben. Mit submit bekommt man ein Objekt der Klasse
Future zurück.

java.util.concurrent.Executors: eine Hilfsklasse mit der man ExecutorServices
anlegen kann. Mit der Methode ExecutorService newFixedThreadPool(int
nThreads) bekommt man einen Threadpool mit einer fixen Anzahl von
Threads. Mit der Methode ExecutorService newCachedThreadPool()
bekommt man einen Threadpool bei dem Thread angelegt werden falls
welche benötigt werden und Threads die über 60 Sekunden keine Aufgabe
hatten werden zerstört.

java.util.concurrent.CountDownLatch: eine Synchronisationsklasse mit der
man Threads steuern kann. Eine Latch ist wie eine Tür, sobald die Tür offen
ist können die Threads durchgehen. Ist eine Latch einmal offen, dann bleibt
sie das für immer. Die CountDownLatch öffnet sobald ihr Zähler auf 0 geht.
Ein Thread kann mit der Methode await() warten bis eine Latch öffnet. Mit
Markus Loeberbauer 2010, 2011
39
Praktikum aus Softwareentwicklung 2
Threads
der Methode countDown() kann man den Zähler verringern und mit der
Methode getCount() abfragen.

java.util.concurrent.locks.ReentrantLock: ein flexibler Locking-Mechanismus.
Funktioniert wie ein synchronized-Block bietet aber mehr Möglichkeiten,
wie zB tryLock mit diese Methoden kann man versuchen einen Lock zu
erhalten. Achtung wenn man einen ReentrantLock anstelle eines
synchronized-Block verwendet muss man sicherstellen, dass der Lock
wieder freigegeben wird.
Markus Loeberbauer 2010, 2011
40
Praktikum aus Softwareentwicklung 2
Streams
Streams
In Java sind die Aufgaben Lesen und Schreiben von Datenströmen getrennt.
Weiters unterscheidet Java in Byte- und Character-Ströme. Wobei Byte-Ströme
über InputStreams und OutputStreams abstrahiert werden und Character-Ströme
mit Readern und Writern. Abbildung 7 zeigt wie Daten in einem Java-Programm
gelesen und geschrieben werden können.
InputStream
Programm
Reader
OutputStrea
m Writer
Daten
Daten
quelle
senke
Abbildung 7) Lesen und Schreiben von Daten über Datenströme
Lesen von Byte-Strömen
Die abstrakte Klasse InputStream im Paket java.io ist die Basis aller Eingabeströme.
Will man eine eigene Datenquelle in Java einbinden muss man InputStream
beerben und zumindest die Methode int read() überschreiben. Alle anderen in der
Klasse vorhandenen Methoden bauen auf int read() auf. Die Methode muss pro
Aufruf ein Byte von der Datenquelle liefern, der Rückgabewert ist ein int, damit
man den Wert -1 liefern kann, wenn der Datenstrom das Ende erreicht hat.
Wichtige Ableitungen von InputStream sind: FileInputSteam (lesen einer Datei),
ByteArrayInputStream (lesen aus einem Byte-Array), PipedInputStream
(Kommunikation zwischen Threads), ObjectInputStream (lesen von primitiven
Datentypen und Objekten) und FilterInputStream (Basis aller Dekoratoren für
Eingabeströmen, zB für gepuffertes Lesen).
Markus Loeberbauer 2010, 2011
41
Praktikum aus Softwareentwicklung 2
Streams
Schreiben auf Byte-Ströme
OutputStream im Paket java.io ist die Basisklasse aller Ausgabeströme. Will man
eine eigene Datensenke in Java einbinden muss man OutputStream beerben und
zumindest die Methode write(int) implementieren. Alle anderen Methoden in der
Klasse bauen auf write(int) auf. Write hat aus Symmetriegründen zu int read()
einen int-Parameter, schreibt aber nur das niederwertigste Byte in die Datensenke
und ignoriert die restlichen drei Bytes.
Wichtige Ableitungen von OutputStream sind: FileOutputStream (schreiben in eine
Datei), ByteArrayOutputStream (schreiben in ein Byte-Array), PipedOutputStream
(kommunizieren mit einem anderen Thread), ObjectOutputStream (schreiben von
primitiven Datentypen und Objekten) und FilterOutputStream (Basis aller
Dekoratoren für Ausgabeströme, zB für gepuffertes Schreiben).
Lesen von Character-Strömen
Die Basis aller Character-Eingabeströme ist Reader, Ableitungen von Reader
müssen zumindest die Methoden close() und int read(char[] cbuf, int off, int len)
implementieren. Die Methode füllt cbuf, ab Position off, für maximal len Zeichen;
und liefert die Anzahl der tatsächlich gelieferten Zeichen zurück.
Wichtige Ableitungen von Reader sind: InputStreamReader (lesen von einem
InputStream) mit der Unterklasse FileReader (Komfort-Klasse, lesen von einer
Datei), BufferedReader (gepuffertes Lesen), CharArrayReader und StringReader
(lesen von einem Char-Array bzw. String), PipedReader (Kommunikation zwischen
Threads).
Schreiben von Character-Strömen
Die Basis aller Character-Ausgabeströme ist Writer, Ableitungen von Writer
müssen zumindest die Methoden close(), flush() und int read(char[] cbuf, int off,
int len) überschreiben. Die Methode schreibt len Zeichen von cbuf ab Position off
in die Datensenke.
Markus Loeberbauer 2010, 2011
42
Praktikum aus Softwareentwicklung 2
Streams
Wichtige Ableitungen von Writer sind: OutputStreamWriter (schreiben in einen
OutputStream) mit der Unterklasse FileWriter (Komfort-Klasse, schreiben in eine
Datei), BufferedWriter (gepuffertes Schreiben), CharArrayWriter und StringWriter
(schreiben in einen Char-Array bzw. StringBuffer), PipedWriter (Kommunikation
zwischen Threads).
Standardströme
Programme haben die Standardströme: Standard-Ausgabe-Strom, Standard-ErrorStrom und Standard-Eingabe-Strom. Diese kann man über die statischen Felder
System.out, System.err bzw. System.in abrufen.
Muster Ressourcen und Exceptions
Klassen die mit Betriebssystem-Ressourcen arbeiten, müssen nach der
Verwendung diese wieder freigeben. Das trifft auf Ströme, die zum Beispiel auf
Dateien arbeiten, ebenfalls zu. Um das sicher zu stellen soll man das Muster in
Abbildung 8 verwenden.
Markus Loeberbauer 2010, 2011
43
Praktikum aus Softwareentwicklung 2
1. Variable deklarieren, mit null
initialisieren
2. try-Block öffnen
1. Resource anlegen
2. Resource nutzen
3. catch-Block (optional)
4. finally-Block
1. Resource freigeben
Streams
FileInputStream fis = null;
try {
fis = new FileInputStream(
"test.txt");
int c;
while ((c = fis.read()) != -1) {
char ch = (char) c;
...
}
}
catch (IOException ioex) {
...
}
finally {
if (fis != null) {
try { fis.close(); }
catch {
/* log exception */
...
}
}
}
Abbildung 8) Muster: Ressources und Exceptions
Dekorieren von Datenströmen mit Filter-Streams
Das Entwurfsmuster Decorator wird verwendet, um Klassen mit zusätzlichen
Funktionen auszustatten. In Java wird das Decorator-Muster eingesetzt, um Einund Ausgabeströme mit zusätzlichen Funktionen zu versehen, zB Puffern,
Verschlüsseln oder Komprimieren. Abbildung 9 zeigt schematisch wie FilterStreams verwendet werden können.
Markus Loeberbauer 2010, 2011
44
Praktikum aus Softwareentwicklung 2
Input
Filter
Filter
Strea
Input
Input
Streams
Programm
Stream Stream
Filter
Filter
Input
Input
Input
Strea
Stream Stream
Daten
Daten
quelle
senke
Abbildung 9) Schematische Darstellung von Filter-Streams
Markus Loeberbauer 2010, 2011
45
Praktikum aus Softwareentwicklung 2
Serialisierung
Serialisierung
Über Serialisierung kann man Objekte in Bytes verwandeln und Objekte aus Bytes
aufbauen. In Java kann man das über den ObjectOutputStream bzw.
ObjectInputStream machen.
Java kann alle primitiven Datentypen und beliebige Objekte serialisieren,
allerdings müssen Klassen mit dem Marker-Interface Serializable markiert werden.
Wird ein solches Objekt serialisiert, dann serialisiert Java auch alle Objekte mit, die
über Felder erreichbar sind (transitive Hülle). Zeigt ein Feld auf ein Objekt welches
nicht Serializable implementiert wirft Java eine NotSerializableException. Felder die
man bei der Serialisierung auslassen möchte muss man mit transient markieren.
Klassen können weiterentwickelt werden, damit Änderungen in Klassen nicht zu
korrupten Datenmodellen beim Deserialisieren führen kann man eine
Versionsnummer als Konstante mit dem Namen serialVersionUID in der Klasse
ablegen.
Benutzerdefinierte Serialisierung
Will man mehr Einfluss auf die Serialisierung nehmen kann man die Methoden
writeObject, readObject und readObjectNoData; writerReplace und readResolve
implementieren. Eine genaue Beschreibung dieser Methoden ist in der JavaDoc
des Interfaces Serializable vorhanden.
Über private void writeObject(ObjectOutputStream) kann man den Zustand eines
Objekts speichern, dabei wird der Zustand der Basisklasse automatisch
gespeichert. Mit private void readObject(ObjectInputStream) kann man den
Zustand des Objekts wiederherstellen. Mit private void readObjectNoData() kann
man einen Standard-Zustand herstellen wenn keine Daten für das Objekt
vorhanden sind. Das kann passieren wenn der Datenstrom beschädigt ist oder der
Datenstrom mit einer anderen Version des Objekts geschrieben wurde.
Markus Loeberbauer 2010, 2011
46
Praktikum aus Softwareentwicklung 2
Serialisierung
Über die Methode ANY-ACCESS-MODIFIER Object writeReplace() kann man ein
Stellvertreterobjekt liefern, das anstelle des eigentlichen Objekts serialisiert werden
soll. Mit der Methode ANY-ACCESS-MODIFIER Object readResolve() kann man
beim Deserialisieren das ursprüngliche Objekt wieder liefern.
Externalisieren
Will man noch mehr Einfluss auf die Serialisierung nehmen kann man das
Interface Externalizable mit den Methoden writeExternal und readExternal
implementieren. Implementiert eine Klasse Externalizable, dann sichert Java nur
eine Id für das Objekt, um die Daten des Objekts und die Daten der Superklassen
muss sich der Programmierer kümmern. Externalizable implementiert man nur in
Ausnahmefällen, eingeführt wurde es um die teure (und damals noch teurere)
Reflection aus dem Serialisierungsprozess herausoptimieren zu können. Das kann
notwendig sein wenn man sehr viele Objekte serialisieren muss, zB bei
Methodenaufrufen über das Netzwerk.
Markus Loeberbauer 2010, 2011
47
Praktikum aus Softwareentwicklung 2
Netzwerkprogrammierung
Netzwerkprogrammierung
Will man Programme schreiben die auf unterschiedlichen Rechnern laufen und
miteinander kommunizieren müssen, dann muss man folgende Fragen klären: Wie
finden sich die verteilten Programme? Wie wird die Verbindung aufgebaut? Wie
werden Daten ausgetauscht? Diese Fragen werden in Java durch zwei Modelle
abgedeckt, dem Socket-Streaming und dem Remoting.
Socket-Streaming
Sockets sind eine Programmierschnittstelle für stream-basierte Kommunikation. In
Java wird Socket-Streaming über die Klassen Socket und ServerSocket umgesetzt.
In diesem Modell finden sich Programme über IP-Adressen und Ports. Java
unterstützt IP Version 4 (RFC 790 u.a.) und IP Version 6 (RFC 2373 u.a.). Die
Kommunikation zwischen den Rechnern erfolgt über Ein- und Ausgabeströme.
Clients bauen die Verbindung über die Klasse Socket auf, von diesem Socket kann
man über die Methode getInputStream den Eingabestrom zum Lesen und über
getOutputStream den Ausgabestrom zum Schreiben abrufen.
Server öffnen einen Port über ServerSocket, auf den sich Clients verbinden
können. Am Server wird ein Client über die Methode accept angenommen; accept
liefert einen Socket zurück über den die Kommunikation abgewickelt werden
kann. Häufig wird die Client-Anfrage in einem eigenen Thread abgearbeitet, damit
mehrere Clients gleichzeitig bedient werden können.
Markus Loeberbauer 2010, 2011
48
Praktikum aus Softwareentwicklung 2
Datenbanken
Datenbanken
Datenbanken werden in Java über JDBC (ist ein Eigenname, wird aber oft als
Abkürzung von Java Database Connectivity API gesehen) angesprochen. JDBC ist
eine Abstraktionsschicht über Datenbanktreibern. Ohne Abstraktion müsste man
bei einem Datenbankwechsel die Anwendung umprogrammieren.
Design von JDBC
JDBC wird seit 1995 entwickelt, erste Überlegungen gingen in Richtung einer
Spracherweiterung, diese Ideen wurden aber verworfen und eine
Treiberschnittstelle für Drittanbieter gebaut. JDBC lehnt sich an ODBC an, ist aber
im Stil von Java geschrieben: ODBC hat wenige Befehle aber sehr viele Optionen,
JDBC hat viele einfache Methoden. Außerdem benutzt ODBC void-Zeiger und Java
kennt keine Zeiger.
Befehle an die Datenbank werden als String übergeben. Das erlaubt es
Programmieren SQL-Befehle für eine Datenbank zu optimieren, aber man muss
sich bewusst sein, dass eine solche Optimierung wieder eine Bindung an eine
Datenbank bedeutet.
Treiberarten in JDBC
Es gibt vier Treiberarten in JDBC. Sie sind historisch bedingt: der Typ 1 Treiber ist
eine Brücke von JDBC nach ODBC, Typ 2 Treiber leiten Aufrufe an native Treiber
weiter, Typ 3 Treiber sind voll in Java implementiert und binden an eine
Middleware und Typ 4 Treiber sind voll in Java implementiert und sprechen direkt
eine Datenbank an.
Typ 1: Brücke
Typ 1 die Brücke von JDBC nach ODBC war ein pragmatischer Ansatz von Sun, um
vom Start weg so viele Datenbanken wie möglich anzubinden. Für ODBC waren
damals viele Datenbanktreiber verfügbar. Nachteile dieses Ansatzes sind: die
Markus Loeberbauer 2010, 2011
49
Praktikum aus Softwareentwicklung 2
Datenbanken
zusätzliche ODBC-Schicht kostet Leistung; höhere Wartung, es muss am
Zielrechner ein ODBC-Treiber installiert und gepflegt werden.
Typ 2: Partial Java Driver
Gibt Aufrufe direkt an eine native Implementierung weiter. Da für Datenbanken
native Treiber vorhanden waren, war dies eine Möglichkeit für DatenbankHersteller schnell Java-Treiber anzubieten. Der Nachteil dieses Ansatzes ist die
Betriebssystemabhängigkeit der Treiber.
Typ 3: Reiner Java Treiber zu einer Middleware
Der Treiber ist völlig in Java implementiert und damit Betriebssystemunabhängig.
Durch die Middleware ist das Programm auch Datenbankunabhängig. Nachteile:
es muss einen Server geben wo diese Middleware installiert ist.
Typ 4: Reiner Java Treiber zu einer Datenbank
Der Treiber ist völlig in Java implementiert und dadurch
Betriebssystemunabhängig. Typ 4 Treiber verbinden direkt auf die Datenbank und
sind damit schnell. Ein kleiner Nachteil gegenüber Typ 3 ist die Abhängigkeit von
der Datenbank.
Installation von JDBC-Treibern
Datenbanktreiber für JDBC kann man auf
http://developers.sun.com/product/jdbc/drivers oder bei den
Datenbankherstellerseiten finden. Zur Installation muss man den Treiber in den
Klassenpfad aufnehmen.
Will man einen Treiber benutzen, muss man ihn laden, dazu hat man die
Möglichkeiten:
a. Seit Java 6.0 (JDBC 4), Automatisches Laden als Java Service durch den
DriverManager. Dazu muss der Treiber das Java Service java.sql.Driver
anbieten, was heute üblich ist.
b. System Property: jdbc.drivers, zB:
Markus Loeberbauer 2010, 2011
50
Praktikum aus Softwareentwicklung 2
Datenbanken
a. java -Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriver Xyz
b. System.setProperty("jdbc.drivers",
"org.apache.derby.jdbc.EmbeddedDriver");
c. Manuelles laden der Treiberklasse, zB:
a. Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
Aufbauen einer Verbindung
Eine Verbindung (Connection) zu einer Datenbank kann man über
DriverManager.getConnection aufbauen. Dazu muss man eine Datenbank-Url und
optional einen Benutzernamen und ein Passwort angeben.
DriverManager: Verwaltet registrierte Treiber, baut Verbindungen auf

Connection getConnection(String url, String user, String password)
Datenbank URL

Aufbau: jdbc:<subprotrokoll>:<subname>

jdbc:<Datenbanktreiber>:<treiberspezifische Angaben>

Derby
o jdbc:derby:/path/to/Database
o jdbc:derby:Databasename

MySQL
o jdbc:mysql://<host>:<port>/<Database>
Arten von Statements
Über die Connection kann man die Verbindung zur Datenbank verwalten, zB
schließen, Informationen über die Datenbank abfragen, Transaktionen verwalten
und Statement-Objekte anfordern.
JDBC unterscheidet die Statement-Arten: Statement, PreparedStatement und
CallableStatement:
Markus Loeberbauer 2010, 2011
51
Praktikum aus Softwareentwicklung 2

Datenbanken
Statement (Connection.createStatement): Absetzen von beliebigen SQLBefehlen, einsetzbar für Werkzeuge, bei denen der Benutzer den Befehl
eingeben kann und für vordefinierte Befehle.
Beispiel:
Statement stat = con.createStatement();
stat.executeUpdate("INSERT INTO test VALUES ('Hallo')");

PreparedStatement (Connection.prepareStatement): Absetzen von SQLBefehlen mit Parametern, einsetzbar wenn Benutzer Parameter eingeben
können. Parameter werden mit einem „?“ in den SQL-String eingesetzt, und
mit set-Methoden vor dem Ausführen über ihre Position gesetzt, Positionen
werden von 1 ab gezählt. Verhindert SQL-Injection. Kann auf der
Datenbank vorkompiliert werden und ist damit schneller bei der
Ausführung.
Beispiel:
PreparedStatement stat;
stat = con.prepareStatement("INSERT INTO test VALUES (?,?)");
stat.setString(1, "Hallo");
stat.setString(2, "Welt");
stat.executeUpdate();
stat.setString(2, "Jane");
stat.executeUpdate();

CallableStatement (Connection.prepareCall): Ausführen von DatenbankProzeduren. CallableStatements können wie PreparedStatements
parametrisiert werden. Ausgangsparameter werden unterstützt, sie müssen
aber registriert werden (registerOutParameter). Übergangsparameter müsse
ebenfalls als Ausgangsparameter registriert werden.
Beispiel:
Markus Loeberbauer 2010, 2011
52
Praktikum aus Softwareentwicklung 2
Datenbanken
CallableStatement cs;
cs = con.prepareCall("{ CALL GET_NUMBER_FOR_NAME(?, ?) }");
cs.registerOutParameter(2, java.sql.Types.INTEGER);
cs.setString(1, "Duke");
cs.execute();
int number = cs.getInt(2);
Abfragen der Ergebnisse
Statements liefern Abfrageergebnisse als ResultSet zurück. ResultSet ist ein Cursor,
es funktioniert wie ein Iterator von dem man Werte abfragen kann. Am Anfang
steht das ResultSet vor der ersten Zeile, mit boolean next() kann man die nächste
Zeile anspringen, der Rückgabewert von next gibt an ob eine gültige Zeile erreicht
wurde.
Die Werte einer Zeile können mit Methoden der Art get<Typ>(int spalte) und
get<Typ>(String spaltenName) abgefragt werden. Fragt man einen Wert ab, kann
man mit wasNull abfragen ob der Wert in der Datenbank SQL-NULL ist. Folgende
weitere Methoden stehen zur Verfügung:

int findColumn(String spaltenName): sucht die Spaltennummer zu einer
Spalte mit gegebenem Spaltennamen.

boolean first(): Springt in die erste Zeile im ResultSet, liefert true wenn
gültige Zeile erreicht wird.

void beforeFirst(): Springt vor die erste Zeile im ResultSet.

boolean last(): Springt in die letzte Zeile im ResultSet, liefert true wenn
gültige Zeile erreicht wird.

void afterLast(): Springt hinter die letzte Zeile im ResultSet.

boolean absolute(int row): Spring in die Zeile mit der gegebenen Nummer:
o row > 0 ... von oben (1 erste Zeile, 2 zweite Zeile, ...)
o row < 0 ... von unten (-1 letzte Zeile, -2 vorletzte Zeile, ...)
o liefert true wenn gültige Zeile erreicht wird.
Markus Loeberbauer 2010, 2011
53
Praktikum aus Softwareentwicklung 2

Datenbanken
int getRow(): Liefert die Nummer der aktuellen Zeile
Markus Loeberbauer 2010, 2011
54
Praktikum aus Softwareentwicklung 2
Datenbanken
Abbildung der SQL-Typen auf Java-Typen
SQL-Typ
Java-Typ
CHAR, VARCHAR, LONGVARCHAR
String
NUMERIC, DECIMAL
java.math.BigDecimal
BIT
boolean
TINYINT
byte
SMALLINT
short
INTEGER
int
BIGINT
long
REAL
float
FLOAT, DOUBLE
BINARY, VARBINARY,
LONGVARBINARY
DATE
TIME
TIMESTAMP
…
double
byte[]
java.sql.Date
java.sql.Time
java.sql.Timestamp
siehe JSR-221, Appendix B, Date Type
Conversion Tables
Metadaten einer Datenbank
Über DatabaseMetaData Connection.getMetadata() kann man auf Informationen
der Datenbank zugreifen. Das ist wichtig wenn man Programme entwickelt, die
die Datenbank nicht kennen, zB: Administrationsoberflächen; oder wenn man die
Datenbank bei der ersten Verwendung initialisieren will.
Markus Loeberbauer 2010, 2011
55
Praktikum aus Softwareentwicklung 2
Datenbanken
Es kann auf Daten wie die Datenbank-Url (getURL), den Benutzernamen
(getUserName) und Beschreibbarkeit (isReadOnly) zugegriffen werden. Man
abfragen was eine Datenbank unterstützt, zB: Transaktionen
(supportsTransactions), Gruppierung (supportsGroupBy). Welche Beschränkungen
eine Datenbank hat, zB: Maximale Länge eines Statements
(getMaxStatementLength), Maximale Anzahl der parallel absetzbaren Statements
(getMaxStatements) und Maximale Anzahl geöffneter Verbindungen
(getMaxConnections). Wird bei den Beschränkungen 0 geliefert, bedeutet das,
dass keine Beschränkung gibt oder die Beschränkung unbekannt ist. Weiters kann
man inhaltsbezogene Daten abfragen, zB: welche Tabellen in einer Datenbank
liegen, welche Spalten in einer Tabelle existieren und welche Typen die Spalten
haben.
Daten über Ergebnistabellen kann man über ResultSetMetaData
ResultSet.getMetaData() abfragen. Darüber kann man beispielsweise abfragen wie
viele und welche Spalten geliefert wurden; welche Typen und Namen die Spalten
haben und ob man in eine Spalte schreiben kann.
Transaktionen
Eine Transaktion wird in JDBC gestartet, sobald man ein Statement absetzt und
noch keine Transaktion läuft. Standardmäßig wird in JDBC jedes Statement als
eine Transaktion behandelt. Braucht man länger laufende Transaktionen, dann
muss man die Eigenschaft autoCommit der Verbindung auf false setzen
(Conncetion.setAutoCommit). Eine laufende Transaktion kann man mit
Connection.commit abschließen und mit Connection.rollback rücksetzen.
Zusätzlich kann man während einer Transaktion Sicherheitspunkte (Savepoints)
angelegen auf die man mit einem Rollback zurückspringen kann.
Markus Loeberbauer 2010, 2011
56
Praktikum aus Softwareentwicklung 2
Datenbanken
Unterstützte Transaktions-Isolation
Welcher Transaktions-Isolations-Level von einer Datenbank unterstützt wird kann
man über DatabaseMetaData. supportsTransactionIsolationLevel abfragen. Die in
JDBC bekannten Transaktionslevels sind in der Klasse Connection definiert:

NONE: Kein Transaktionssupport => kein JDBC Treiber

READ_UNCOMMITTED: dirty reads, non-repeatable reads und phantom
reads können auftreten

READ_COMMITTED: dirty reads sind verhindert; non-repeatable reads und
phantom reads können auftreten

REPEATABLE_READ: dirty reads und non-repeatable reads sind verhindert;
phantom reads können auftreten

SERIALIZABLE: dirty reads, non-repeatable reads und phantom reads sind
verhindert.
Beispiel:
Connection con;
...
try {
con.setAutoCommit(false);
Statement stat = con.createStatement();
stat.executeUpdate("INSERT ...");
stat.executeUpdate("INSERT ...");
stat.executeUpdate("UPDATE ...");
con.commit();
} catch (SQLException e) { con.rollback(); }
Ausnahmebehandlung
Alle JDBC-bezogenen Exceptions erben von SQLException, seit Java 6.0 gibt es
eine feingranulare Aufteilung in die Fehlerklassen: SQLNonTransientException,
SQLTransientException und SQLRecoverableException. Nicht-transient bedeutet,
dass ein erneuter Versuch wieder fehlschlagen wird; transient bedeutet, dass ein
erneuter Versuch durchgehen kann; und recoverable bedeutet, dass ein erneuter
Versuch mit geänderten Daten durchgehen kann.
Markus Loeberbauer 2010, 2011
57
Praktikum aus Softwareentwicklung 2
Datenbanken
Java DB (Derby)
Seit Java 6.0 wird die Datenbank Java DB mit dem JDK geliefert. Diese Datenbank
ist auch unter dem Namen Derby oder Apache Derby bekannt. Derby ist eine
kompakte (Kern: 2,5MB), in Java entwickelte, einfach zu nutzende (ohne
Installation), standardkonforme (SQL 99, und Teile aus späteren Standards)
Datenbank. Interessant ist, dass die erzeugten Daten-Dateien
betriebssystemunabhängig sind.
Will man von der Konsole aus mit Derby arbeiten muss man die
Umgebungsvariablen JAVA_HOME, DERBY_HOME und PATH setzen:

JAVA_HOME=Pfad zur Java JDK Installation

DERBY_HOME=Pfad zur Derby Installation

PATH um DERBY_HOME/bin erweitern
In DERBY_HOME sind die jar-Dateien von Derby:

derby.jar: Kern, genügt für embedded DB

derbynet.jar: Netzzugriff, Serverseitig

derbyclient.jar: Netzzugriff, Clientseitig

derbytools.jar: Verwaltungs-Werkzeuge

derbyrun.jar verweist auf: derby.jar, derbyclient.jar, derbytools.jar und
derbynet.jar
Auf die Verwaltungs-Werkzeuge in derbytools.jar kann man über Batch-Dateien
zugreifen. Das Werkzeug sysinfo liefert Informationen über die Java-Installation
auf dem System; mit dblook kann man das Datenbankschema exportieren. ij ist
eine Konsole mit der man SQL-Befehle an eine Derby-Datenbank absetzen kann.
Markus Loeberbauer 2010, 2011
58
Praktikum aus Softwareentwicklung 2
Remoting
Remoting
Über Remoting können Objekte über JavaVMs hinweg miteinander
kommunizieren. Das ist auch mit Socket-Programmierung möglich. Aber Remoting
abstrahiert die Kommunikation als Methodenaufrufe, während Sockets nur
Byteströme übertragen. Remove-Methodenaufrufe unterscheiden sich von lokalen
Methodenaufrufen dadurch, dass sie eine RemoteExceptions auslösen können. Das
kann passieren wenn zB der Server abstürzt oder ein Netzwerkfehler auftritt.
Remoting arbeitet mit dem Proxy-Muster um von der Netzwerkkommunikation zu
abstrahieren. Das bedeutetet: auf der Client-Seite ist ein Proxy (Stub) der die
Methodenaufrufe entgegennimmt und über das Netz überträgt. Auf der ServerSeite ist ein Proxy (Skeleton) der die Anfragen vom Netz liest und den
gewünschten Methodenaufruf auf dem echten Server-Objekt macht. Hat die
Methode einen Rückgabewert, dann überträgt der Server-Proxy diesen zurück an
den Client-Proxy. Der Client-Proxy nimmt den Rückgabewert vom Netz und gibt
ihn an den Rufer zurück. Siehe Abbildung 10.
client
server_stub
server_skeleton
server
request
request
response
response
JVM 1
JVM 2
Abbildung 10) Remoting Kommunikation
Die Kommunikation zwischen den JavaVMs erfolgt über die Netzwerk-Schicht des
Betriebssystems, auch wenn die VMs auf demselben Rechner laufen. Die
Netzwerkverbindung läuft über TCP/IP, der Standard-Port ist 1099, als
Markus Loeberbauer 2010, 2011
59
Praktikum aus Softwareentwicklung 2
Remoting
Kommunikationsprotokoll kann man zB: das Java Remote Method Protokol (JRMP)
oder Internet Inter-ORB Protocol (IIOP) eingesetzten.
Damit man eine Methode aufrufen kann muss ein Empfänger-Objekt existieren.
Das Empfänger-Objekt am Server muss also existieren solange Clients darauf
zugreifen. Damit das Objekt erhalten bleibt, auch wenn es am Server keine
Referenz im Programm mehr gibt, gibt es einen Remote Reference Layer der auf
Server-Objekte verweist. Seit Java 2 übernimmt der Remote Reference Layer die
Aufgabe vom Skeleton am Server. Abbildung 11 zeigt die Schichten der
Kommunikation, inklusive Remote Reference Layer.
Abbildung 11) Schichten der Kommunikation bei Remoting
Objektregistrierung und Objektsuche
Eine Frage der Kommunikation zwischen zwei Objekten ist, wie finden sich die
Objekte? In Java Remoting wird das über eine RMI-Registry gelöst. Server
registrieren bei einer RMI-Registry die exportierten Remote-Objekte mit einem
Namen; und Clients fragen über diese RMI-Registry Objekte mit dem Namen ab.
Als RMI-Registry kann man das Kommandozeilenwerkzeug rmiregistry benutzen
oder programmatisch eine über LocateRegistry.createRegistry erstellen.
Registrieren und suchen kann man Objekte über die Klasse Naming, mit den
Methoden bind und rebind bzw. lookup.
Markus Loeberbauer 2010, 2011
60
Praktikum aus Softwareentwicklung 2
Remoting
Stub und Skeleton
Die Proxies für den Client und den Server kann man mit dem
Kommandozeilenwerkzeug rmic erstellen. Gibt man den Parameter -keep an
werden die erzeugten Klassen im Quellcode ausgegeben, sonst nur als classDateien.
Da ab Java 2 die Aufgabe des Skeletons in der Remote Reference Schicht
implementiert ist erzeugt rmic nur Stubs. Braucht man Skeletons muss man den
Parameter -v1.1 angeben.
Nutzt man Klasse UnicastRemoteObjekt fällt auch die Notwendigkeit für einen
Stub weg. Die Klasse UnicastRemoteObjekt exportiert ein Objekt und erzeugt die
nötige Remote-Reference. Diese Klasse kann beerbt werden, dann wird das Objekt
im Konstruktor exportiert; oder man nutzt die statische Methode
exportObject(Remote obj, int port), um ein beliebiges Remote-Objekt zu
exportieren. Als port kann man 0 angeben, dann sucht Java selbst nach einem
freien Port.
Parameter und Rückgabewerte
Sinnvoll werden Methodenaufrufe erst wenn man Parameter übergeben und
Rückgabewerte erhalten kann. Bei Remoting werden alle serialisierbaren
Datentypen als Parameter unterstützt. Das sind: die Basisdatentypen (int, boolean,
double, …), Datentypen die das Interface Serializable implementieren und RemoteObjekte, diese implementieren das Interface Remote. Für Remote-Objekte wird
anstelle des eigentlichen Objekts eine Remote-Referenz übertragen. Somit können
Methodenaufrufe an das echte Objekt weitergeleitet werden. Achtung, bei den
direkt serialisierten Objekten wird auf der anderen Seite eine Kopie aufgebaut,
Änderungen sind also lokal zu der ausführenden JavaVM. Objekte aller anderen
Klassen können nicht verwendet werden.
Markus Loeberbauer 2010, 2011
61
Praktikum aus Softwareentwicklung 2
Remoting
Übergibt man Remote-Objekte an ein Server-Objekt, kann der Server
Methodenaufrufe am Client machen. Die Rolle des Servers und des Clients
vertauscht sich für diesen Aufruf. Das kann zum Beispiel genutzt werden um
Remote-Listener am Server zu installieren.
Objektvergleich
Remote-Objekte haben eine andere Gleichheitssemantik als lokale Objekte. Der
Remote Reference Layer legt jedes Mal wenn ein Objekt abgefragt wird ein neues
Stub-Objekt an. Damit schlägt der Referenzvergleich (==) auf der Clientseite fehl,
auch wenn es sich um dasselbe Objekt auf Serverseite handelt.
Will man feststellen ob zwei Objekt-Referenzen auf der Clientseite auf dasselbe
Objekt der Serverseite zeigen muss man equals benutzen. Die Methode equals
wird also in Remoting benutzt, um Referenzgleichheit am Server festzustellen.
Braucht man eine Vergleichsmethode die Objektgleichheit (equals) am Server
prüft, muss man sich eine eigene Remotemethode schreiben. Häufig wird dafür
der Name remoteEquals benutzt.
Distributed Garbage Collection
Übergibt man ein Remote-Objekt an eine andere JavaVM, zB: als Listener oder als
Arbeitsobjekt aus einer Factory, stellt sich die Frage wie lange man das reale
Objekt am Leben erhalten muss. Sowohl Listener als auch Objekte die man aus
einer Factory erzeugt, um sie einem Remote-Client zu übergeben speichert man
Lokal nur selten. Der Garbage Collector würde diese Objekte also aufräumen.
Damit die Objekte erhalten bleiben solange sie noch von Clients benötigt werden
implementiert der Java Remote Reference Layer einen verteilten Garbage
Collector.
Der verteilte Garbage Collector arbeitet mit Reference Counting und einer Lease
Time. Erst wenn der Referenz-Zähler auf null ist werden Objekte freigegeben. Ein
Client gilt eine Zeit (Lease Time, Standard 10 Minuten) lang als aktiv, innerhalb
Markus Loeberbauer 2010, 2011
62
Praktikum aus Softwareentwicklung 2
Remoting
dieser Zeit muss er sich melden, um weiter als aktiv zu gelten. Das Erneuern der
Lease übernimmt der Remote Reference Layer des Clients, der Programmierer ist
davon unbehelligt.
Die Lease Time kann über das System-Property java.rmi.dgc.leaseValue verändert
werden. Je kürzer die Zeit, umso schneller werden unbenutzte Objekte
freigegeben, aber die Last am Netz steigt. Einen idealen Wert gibt es nicht,
normalerweise kann man den Standardwert von 10 Minuten beibehalten, in
Spezialfällen muss man sich die Netz-Infrastruktur und die Objekt-Last am Server
ansehen und den Wert anpassen.
Nachladen von Klassen
Übergibt man Objekte als Parameter oder Rückgabewerte die auf der Gegenstelle
unbekannt sind muss Code nachgeladen werden. Zum Beispiel kennt ein Client
nur das Interface eines Remote-Objekts, der Stub ist aber eventuell nur am Server
bekannt. Damit der Client dennoch mit den Objekten umgehen kann muss er die
Klassen nachladen.
Remoting unterstützt nachladen von Klassen, dazu muss über das SystemProperty java.rmi.server.codebase eine Url angegeben werden; und ein
SecurityManager installiert sein. Damit der Sicherheitsmanager den Zugriff auf die
Codebase erlaubt muss über eine Policy-Datei Zugriff auf den Server erlaubt sein.
Beispiel:
public class MyClient {
public static void main(String[] args) {
System.setSecurityManager(new SecurityManager());
System.setProperty("java.security.policy", "client.policy");
...
client.policy:
Markus Loeberbauer 2010, 2011
63
Praktikum aus Softwareentwicklung 2
Remoting
grant {
permission java.net.SocketPermission "server-url:1024-65535",
"connect";
permission java.net.SocketPermission "server-url:80", "connect";
permission java.net.SocketPermission "server-url:8080", "connect";
}
Aufteilung der Klassen
Die Klassen können wie folgt aufgeteilt werden:

Server: Klassen die für die Ausführung des Servers erforderlich sind

Download-Bereich: Klassen die vom Client nachgeladen werden sollen,
inklusive aller Basisklassen und Interfaces.

Client: Klassen die unmittelbar am Client benötigt werden; und die PolicyDatei, die Zugriff auf den Download-Server erlaubt.
Der Download-Bereich kann ein Web-Server sein, aber auch ein Verzeichnis auf
einem FTP-Server oder ein Verzeichnis auf dem lokalen Rechner.
Beispiel Remote Calculator
Das Beispiel eines verteilten Rechners zeigt wie man mit Remoting einen
verteilten Dienst implementieren kann. Als erstes muss eine Schnittstelle definiert
werden die der Remote-Service implementieren soll, in unserem Fall ein Rechner
mit den Grundrechnungsarten:
public interface Calculator extends java.rmi.Remote {
public long add(long a, long b) throws java.rmi.RemoteException;
public long sub(long a, long b) throws java.rmi.RemoteException;
public long mul(long a, long b) throws java.rmi.RemoteException;
public long div(long a, long b) throws java.rmi.RemoteException;
}
Jede Methode im Interface muss die eine java.rmi.RemoteException werfen
können.
Markus Loeberbauer 2010, 2011
64
Praktikum aus Softwareentwicklung 2
Remoting
Dazu eine Implementierung der Schnittstelle:
public class CalculatorImpl implements Calculator
public long add(long a, long b) { return a + b;
public long sub(long a, long b) { return a - b;
public long mul(long a, long b) { return a * b;
public long div(long a, long b) { return a / b;
}
{
}
}
}
}
Auf der Server-Seite kann hier keine Exception auftreten, also kann auf die
Deklaration der RemoteException verzichtet werden. Der Client muss dennoch mit
RemoteExceptions umgehen können, zB könnte der Server durch
Netzwerkprobleme unerreichbar werden.
Und einen Server der die Implementierung exportiert:
public class CalculatorServer {
public static void main(String args[]) throws RemoteException,
MalformedURLException {
Calculator c = new CalculatorImpl();
Remote calcStub = UnicastRemoteObject.exportObject(c, 0);
Naming.rebind("rmi://localhost:1099/CalculatorService",
calcStub);
}
}
Dieser Server benutzt eine RMI-Registry am lokalen Rechner auf Port 1099 und
exportiert den von UnicastRemoteObject generierten Stub unter dem Namen
CalculatorService.
Bevor der Server gestartet werden kann, muss eine RMI-Registry am lokalen
Rechner gestartet werden, zB über das Kommandozeilenwerkzeugt rmiregistry.
Markus Loeberbauer 2010, 2011
65
Praktikum aus Softwareentwicklung 2
Remoting
Abschließend noch ein Test-Client der den Remote-Calculator benutzt:
public class CalculatorClient {
public static void main(String[] args) {
try {
Calculator c = (Calculator) Naming
.lookup("rmi://localhost/CalculatorService");
System.out.println(c.sub(4, 3));
System.out.println(c.add(4, 5));
System.out.println(c.mul(3, 6));
System.out.println(c.div(9, 3));
} catch (MalformedURLException murle) {
System.out.println("MalformedURLException");
System.out.println(murle);
} catch (RemoteException re) {
System.out.println("RemoteException");
System.out.println(re);
} catch (NotBoundException nbe) {
System.out.println("NotBoundException");
System.out.println(nbe);
} catch (java.lang.ArithmeticException ae) {
System.out.println("java.lang.ArithmeticException");
System.out.println(ae);
}
}
}
Markus Loeberbauer 2010, 2011
66
Praktikum aus Softwareentwicklung 2
XML
XML
XML (Extensible Markup Language) ist eine Auszeichnungssprache mit
hierarchischer Struktur. Die Daten werden als Text abgelegt und mit
Metaelementen strukturiert. Java unterstützt XML über die Java API for XML
Processing (JAXP). Mit JAXP kann man XML-Dokumente lesen, schreiben,
transformieren und erstellen. JAXP ist pluggable implementiert, Objekte werden
über Factory-Methoden geliefert und über Interfaces benutzt, somit kann die
Implementierung getauscht werden, siehe Abbildung 12.
Client
API
Plugability-Layer
Implementations
Abbildung 12) JAXP, Plugability-Layer
XML-Dokumente
Bevor wir uns näher mit der XML-Unterstützung in Java beschäftigen, sehen wir
uns XML-Dokumente im Überblick an. Abbildung 13 zeigt ein Adressbuch als
Beispiel für ein XML-Dokument. In dem Beispiel sehen wir: XML-Dokumente
bestehen aus Deklarationen und einem Baum von Elementen.
In der Deklaration wird angegeben welche Version (version) von XML verwendet
wird, wie die Zeichen kodiert (encoding) sind und ob auf weitere Dokumente
verwiesen wird (standalone). Über das Element DOCTYPE werden das verwendete
Schema (Aufbau der XML-Datei) und das Wurzelelement angegeben.
Markus Loeberbauer 2010, 2011
67
Praktikum aus Softwareentwicklung 2
XML
Jedes Element im Dokument hat einen Namen, optionalen Inhalt und beliebig
viele Attribute. Davon optional eine eindeutige ID im Attribut id, der Wert der ID
muss im Dokument eindeutig sein.
Das erste Element im Dokument heißt root, in unserem Beispiel ist das das
Element addressbook. Kinder (children) von addressbook sind Personen (person)
und Firmen (companies). Kinder von Personen-Elementen sind zB: der Vorname
(firstname) und der Nachname (lastname). Die Kinder und die Kindeskinder
bezeichnet man als Nachfolger (descendents), zB die Nachfolger von addressbook
sind person, companay, firstname, email, usw. Umgekehrt ist das Eltern-Element
(parent) von firstname: person und von person: addressbook. Weitergehend sind
alle Vorgänger (ancestors) von firstname: person und addressbook. Elemente auf
gleicher Ebene sind Geschwister-Elemente (siblings), zB: firstname, lastname und
email sind Geschwister.
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE addressbook SYSTEM "addressbook.dtd">
<addressbook owner="p1" date="2005-03-12">
<person id="p1">
<firstname>Thomas</firstname>
<lastname>Kotzmann</lastname>
<email>[email protected]</email>
</person>
<company id="c1">
<companyname>Sun</companyname>
<url>www.sun.com</url>
</company>
<person id="p2">
<firstname>Markus</firstname>
<lastname>Loeberbauer</lastname>
<email>[email protected]</email>
Abbildung</person>
13) Beispiel: XML-Dokument Addressbook
Markus Loeberbauer 2010, 2011
68
Praktikum aus Softwareentwicklung 2
XML
Schemadefinition
In der Schemadefinition wird die Struktur von XML-Dokumenten festgelegt. Hat
man ein Schema, dann kann man XML-Dokumente gegen dieses Schema prüfen
(validieren) und feststellen ob die Dokumente gültig (valide) sind. Die Struktur
eines Dokuments besteht aus den Elementen, Attributen und der Verschachtelung
der Elemente.
Document Type Defnition
Die Document Type Definition (DTD) ist eine Schemabeschreibungssprache.
Element-Definitionen haben die Syntax: <!ELEMENT element_name
(content_model)>; Attribut-Definitionen: <!ATTLIST target_elem attr_name
attr_type default …>. Die DTD-Schemadefinition für unser Adressbuchbeispiel ist
in Abbildung 14 gegeben.
<!ELEMENT
<!ATTLIST
#IMPLIED>
<!ELEMENT
<!ATTLIST
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ATTLIST
<!ELEMENT
<!ATTLIST
<!ELEMENT
<!ELEMENT
addressbook ((person | company)*)>
addressbook owner IDREF #REQUIRED date CDATA
person (firstname, lastname, email)>
person id ID #REQUIRED>
firstname (#PCDATA)>
lastname (#PCDATA)>
email (#PCDATA)>
email type (home | business) "business">
company (companyname, url)>
company id ID #REQUIRED>
companyname (#PCDATA)>
url (#PCDATA)>
Abbildung 14) Document Type Definition für Beispiel: Addressbook
Element-Definitionen haben einen Namen und beschreiben den Inhalt des
Elements. Dabei werden folgende Meta-Symbole benutzt: Reihenfolgen: „,“,
Alternativen: „|“, Gruppen: „()“, beliebige Wiederholung „*“, mindestens einmal:
„+“, optional: „?“, ohne Inhalt: „EMPTY“, beliebiger Inhalt: „ANY“.
Markus Loeberbauer 2010, 2011
69
Praktikum aus Softwareentwicklung 2
XML
Attribut-Definitionen deklarieren den Namen des betroffenen Elements, den
Namen des Attributes, den Attribut-Typ und optional einen Default-Wert. Als
Typen gibt es: beliebige Zeichenketten ohne Leerzeichen „CDATA“, eindeutige IDs
„ID“, Referenz auf ID „IDREF“ sowie mehrere Referenzen„IDREFS“ und
Enumerationen „(…|…|…)“. Der Attribut-Wert kann optional (#IMPLIED), gefordert
(#REQUIRED), unveränderlich (#FIXED "value") und vorgegeben ("value") sein.
XML-Schema
Neben DTD gibt es noch weitere Schemadefinitionssprachen, eine davon ist XMLSchema. XML-Schema beschreibt das Schema von XML-Dokumenten in XML,
unterstützt Datentypen zB: einfache Datentypen wie string, integer und boolean,
außerdem kann man komplexe (zusammengesetzte) Datentypen spezifizieren. Eine
genaue Beschreibung kann man auf www.w3.org finden.
DOM-Parser/-Builder
Das Document Object Model (DOM) ist eine Hauptspeicherstruktur die den Inhalt
einer XML-Datei darstellt. DOM hat eine Knotenstruktur, den DOM-Baum. Auf
diesen Baum kann über eine objektorientierte Java-API zugegriffen werden.
Abbildung 15 zeigt für einen Ausschnitt des Addressbook-Beispiels den DOMBaum.
Markus Loeberbauer 2010, 2011
70
Praktikum aus Softwareentwicklung 2
Document
DocumentType
…
Element: addressbook
XML
<?xml version="1.0"
encoding="utf-8"?>
<!DOCTYPE addressbook
SYSTEM
"addressbook.dtd">
<addressbook
Attr: owner = "p1"
owner="p1"
Attr: date = 2005-03-12
date="2005-03-12">
Element: person
Attr: id = "p1"
Element: firstname
Text: Thomas
Element: lastname
Text: Kotzmann
<person
id="p1">
<firstname>
Thomas</firstname>
<lastname>
Kotzmann</lastname>
Abbildung 15) DOM-Baum für Beispiel: Addressbook
Lesen eines DOM-Baumes
Der DOM-Baum kann durch parsen aus einem XML-Dokument aufgebaut werden:
Markus Loeberbauer 2010, 2011
71
Praktikum aus Softwareentwicklung 2
XML
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setValidating(true);
factory.setIgnoringElementContentWhitespace(true);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
File file = new File("addressbook.xml");
Document doc = builder.parse(file);
...
} catch (ParserConfigurationException e) { …
} catch (SAXException e) { …
} catch (IOException e) { …
}
Im Dokument kann über Getter navigiert werden, mit getDocumentElement kann
das Wurzelelement eines Dokuments geholt werden. Mit getNodeName der
Name eines Elements. Attribute können mit Attr getAttribute(String) oder mit
NamedNodeMap getAttributes() ausgelesen werden, von der NamedNodeMap
kann man Attribute mit getNamedItem abfragen. Kinder kann man mit NodeList
getChildNodes() oder NodeList getElementsByTagName() abfragen. Von TextKnoten kann man den Wert über String getNodeValue() auslesen. Der Direkte
Zugriff auf ein Element im Dokument erfolgt über die Methode Element
getElementById(String).
Schreiben eines DOM-Baumes
Ein DOM-Baum kann auch geschrieben werden, das ist interessant wenn man
einen eingelesenen Baum verändern oder einen Baum programmatisch erzeugen
will.
Ein neues Dokument kann über die Methode
Document DocumentBuilder.newDocument() erzeugt werden. Neue Elemente
können über Element Document.createElement(String) erzeugt werden. Erzeugte
Elemente können an Parent-Elemente über appendChild angehängt werden.
Achtung, an ein Dokument darf nur ein Kind-Element gehängt werden, da das
Markus Loeberbauer 2010, 2011
72
Praktikum aus Softwareentwicklung 2
XML
Wurzelelement in XML eindeutig sein muss. Attribute von Elementen kann man
über setAttribute setzen.
Geschrieben wird der Baum über eine XSL-Transformation, wobei hier die
identische Transformation erfolgt, also alles gleich bleibt. Es wird nur die
Hauptspeicher-Darstellung in einen Datenstrom verwandelt:
File file = new File(" ... ");
TransformerFactory fact = TransformerFactory.newInstance();
Transformer t = fact.newTransformer();
t.setOutputProperty("doctype-system", "addressbook.dtd");
t.setOutputProperty("indent", "yes");
t.transform(
new DOMSource(doc),
// source
new StreamResult(new FileOutputStream(file)) // target
);
SAX-Parser
Die Simple API for XML (SAX) ist eine API zum Lesen von XML-Dateien. Ein SAXParser läuft über ein XML-Dokument und ruft dabei Callback-Methoden des
Benutzers auf (ereignisorientiert). Es gibt Callback-Methoden für zB den
Start/Ende des Dokuments, Start/Ende eines Element und Zeichen-Daten in einem
Element. Die Callback-Methoden sind im Interface org.xml.sax.ContentHandler
definiert und werden leer in der Hilfsklasse DefaultHandler implementiert. Ein
SAX-Parser kann wie folgt benutzt werden:
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
try {
SAXParser saxParser = factory.newSAXParser();
File file = new File("addressbook.xml");
saxParser.parse(file, new PrintElementsHandler());
} catch (ParserConfigurationException e1) { …
} catch (SAXException e1) { …
} catch (IOException e) { …
}
Markus Loeberbauer 2010, 2011
73
Praktikum aus Softwareentwicklung 2
XML
Da SAX ereignis-getrieben ist können mit der SAX-Schnittstelle XML-Dokumente
nur gelesen werden.
StAX-Reader/-Writer
Die Streaming API for XML (StAX) ist eine Datenstrom-orientierte Schnittstelle
zum Verarbeiten von XML-Dateien. Die Verarbeitung durch das Programm muss
wie bei SAX on-the-fly erfolgen, aber das Parsen ist programmgetrieben. D.h. wie
bei einem Iterator oder Cursor fordert das Programm das nächste Element an:
FileInputStream fis = null;
try {
fis = new FileInputStream("addressbook.xml");
XMLInputFactory xmlInFact = XMLInputFactory.newInstance();
XMLStreamReader reader = xmlInFact.createXMLStreamReader(fis);
while (reader.hasNext()) {
reader.next();
…
}
} catch (IOException exc) { …
} catch (XMLStreamException exc) { …
} finally { … }
Der StAX-Reader kann über Properties konfiguriert werden
(XMLInputFactory.setProperty(String, Object)), zB ob die gelesenen Daten validiert
werden sollen kann über das Property XMLInputFactory.IS_VALIDATING bestimmt
werden; und ob Namespaces von Tags ausgewertet werden sollen kann über
XMLInputFactory.IS_NAMESPACE_AWARE bestimmt werden.
Mit StAX kann man auch XML-Dokumente schreiben, wie das Lesen erfolgt auch
das Schreiben on-the-fly und programmgetrieben:
Markus Loeberbauer 2010, 2011
74
Praktikum aus Softwareentwicklung 2
XML
FileOutputStream fos = null;
try {
fos = new FileOutputStream("addressbook.xml");
XMLOutputFactory xmlOutFact = XMLOutputFactory.newInstance();
XMLStreamWriter writer = xmlOutFact.createXMLStreamWriter(fos);
writer.writeStartDocument();
writer.writeStartElement("addressbook");
// … write addressbook …
writer.writeEndElement();
writer.flush();
} catch (IOException exc) { …
} catch (XMLStreamException exc) { …
} finally { … }
JAXB-InputStream/-OutputStream
Die Java Architecture for XML Binding (JAXB) wird seit Java 6.0, in Version 2.0
mitgeliefert. Über JAXB ist es möglich Java-Objekte zu Serialisieren. Wie serialisiert
werden soll kann über Annotationen festgelegt werden. Gelesen werden XMLDateien über das Interface javax.xml.bind.Unmarshaller einen solchen
Unmarshaller kann man über JAXBContext.newInstance erzeugen. Damit die
Klassen gelesen werden können muss der Kontext mit den benutzten Klassen
initialisiert werden. Dabei genügt es allerding die Wurzelklassen anzugeben, JAXB
erkennt selbstständig alle statisch referenzierten Klassen.
Über Annotationen kann zB festgelegt werden welche Klassen Elemente sein
sollen (@XmlRootElement), welche Felder als Elemente (@XmlElement) und
welche als Attribute (@XmlAttribute) serialisiert werden sollen. Die Annotationen
können über Attribute konfiguriert werden, zB kann der Name eines Elements in
der XML Datei über das Attribut „name“ festgelegt werden, Standard ist der
Feldname.
Lesen eines Adressbuchs:
Markus Loeberbauer 2010, 2011
75
Praktikum aus Softwareentwicklung 2
XML
AddressBook adr = new AddressBook();
FileInputStream adrFile = null;
try {
adrFile = new FileInputStream("addressbook.xml");
JAXBContext ctx = JAXBContext.newInstance(AddressBook.class);
Unmarshaller um = ctx.createUnmarshaller();
adr = (AddressBook) um.unmarshal(adrFile);
} catch (IOException exc) { …
} catch (JAXBException exc) { …
} finally { … }
Schreiben eines Adressbuchs:
FileOutputStream adrFile = null;
try {
adrFile = new FileOutputStream("addressbook.xml");
JAXBContext ctx = JAXBContext.newInstance(AddressBook.class);
Marshaller ma = ctx.createMarshaller();
ma.marshal(adr, adrFile);
} catch (IOException exc) { …
} catch (JAXBException exc) { …
} finally { … }
XSL-Transformation
XML-Transformation mit XSLT ermöglicht die Transformation von XMLDokumenten über XSL-Stylesheets. In einem XSL-Stylesheet beschrieben wie ein
XML-Dokument transformiert werden soll, zB kann aus einem XML-Dokument
eine HTML-Seite erzeugt werden.
Abbildung 16 zeigt ein Stylesheet mit dem Adressbücher in HTML-Seiten
verwandelt werden können. In Abbildung 17 ist eine Beispiel-Transformation von
einem Adressbuch auf eine HTML-Seite gezeigt.
Will man ein XML-Dokument transformieren muss man einen Transformer, eine
Source und ein Result anlegen, dann kann man mit dem Transformer das QuellMarkus Loeberbauer 2010, 2011
76
Praktikum aus Softwareentwicklung 2
XML
Dokument in die Ziel-Darstellung verwandeln und gegebenenfalls dabei eine
Transformation durchführen:
// create Transformer
TransformerFactory facty = TransformerFactory.newInstance();
File styleSheetFile = new File("addressbook.xsl");
StreamSource styleSheet = new StreamSource(styleSheetFile);
Transformer t = facty.newTransformer(styleSheet);
// create Source
File inFile = new File("addressbook.xml");
Document doc = builder.parse(inFile);
DOMSource source = new DOMSource(doc);
// create Result
File outFile = new File("addressbook.html");
StreamResult result = new StreamResult(
new FileOutputStream(outFile));
// start transformation
t.transform(source, result);
Markus Loeberbauer 2010, 2011
77
Praktikum aus Softwareentwicklung 2
XML
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head> <title>XML Address Book</title> </head>
<body>
<table border="3" cellspacing="10" cellpadding="5">
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="addressbook">
<xsl:apply-templates select="person"/>
</xsl:template>
<xsl:template match="person">
<tr>
<td> <xsl:value-of select="firstname"/> </td>
<td> <b><xsl:value-of select="lastname"/></b> </td>
<td> <xsl:value-of select="email"/> </td>
</tr>
</xsl:template>
</xsl:stylesheet>
Abbildung 16) XSL-Stylesheet von Addressbook.XML nach Addressbook.html
Die Adressierung im Stylesheet erfolgt über XPath. Schritte (Steps) in einem
XPath-Ausdruck sind über „/“ getrennt. Ein Schritt kann ein Element
(element_name), ein Attribut (@attribute_name), eine Bedingung (condition) oder
eine Position (index) beschreiben.
Beispiele von XPath-Ausdrücken:
"*"
"/"
"/addressbook/*"
"/addressbook/person[1]"
"/addressbook/*/firstname"
Markus Loeberbauer 2010, 2011
Selektiert alle Knoten
Wurzelknoten
Selektiert alle Elemente unter dem
addressbook-Element
Liefert die ersten person-Elemente
von addressbook-Elementen
Liefert alle firstname-Elemente
unter den addressbook-Elementen
78
Praktikum aus Softwareentwicklung 2
XML
"/addressbook/person[@id="p1"]" Liefert den Personenknoten mit
id="p1"
"/*/email[@type="home"]"
Liefert alle email-Knoten vom Typ
"home"
<?xml version='1.0' encoding="utf-8"?>
<addressbook owner="1">
<person id="1">
<firstname>Thomas</firstname>
<lastname>Kotzmann</lastname>
<email>[email protected]</email>
</person>
<person id="2">
<firstname>Markus</firstname>
<lastname>Loeberbauer</lastname>
<email>[email protected]</email>
</person>
<html>
<head>
</addressbook>
<META http-equiv="Content-Type"
content="text/html; charset=utf-8">
<title>XML-AddressBook</title>
</head>
<body>
<table border="3" cellspacing="10" cellpadding="5">
<tr>
<td>Thomas</td>
<td><b>Kotzmann</b></td>
<td>[email protected]</td>
</tr>
<tr>
<td>Markus</td>
<td><b>Loeberbauer</b></td>
<td>[email protected]</td>
</tr>
</table>
</body>
</html>
Abbildung 17) Beispiel Transformation: Addressbook.xml nach Addressbook.html
Vergleich der XML-APIs
Lesen
Schreiben
Ändern
Speicherbedarf Einfachheit
SAX
Ja
Nein
Nein
Niedrig
Komplex
StAX
Ja
Ja
Nein
Niedrig
Einfach
DOM
Ja
Ja
Ja
Hoch
Einfach
JAXB
Ja
Ja
Markus Loeberbauer 2010, 2011
-
-
Einfach bis
Komplex
79
Praktikum aus Softwareentwicklung 2
Java Servlet
Java Servlet
Java Servlets sind auf Java basierende Web-Komponenten. Sie werden von einem
Container verwaltet und können dynamisch Inhalt erzeugen. Ein Container
(Servlet-Engine) ist ein Teil eines Web-Servers, leitet Anfragen an die Servlets und
liefert Antworten zurück. Der Container verwaltet die Servlets und ihren
Lebenszyklus. Abbildung 18 zeigt die grobe Architektur von Webanwendungen
die auf Java Servlets aufbauen.
Die Spezifikation der Java Servlets kann in JSR 145 für die Version 2.5 und in JSR
315 für die Version 3.0 nachgelesen werden. Die Version 3.0 bringt einige
interessante Neuerungen, zB Annotationen in Servlets und File Upload. Es ist aber
interessant sich auch mit Version 2.5 zu beschäftigen um die Grundlagen zu
verstehen und weil einige Web-Hoster nur Version 2.5 anbieten, zB Google App
Engine.
Client
Firefox, Opera,
Reques
Web-Server & Servlet-
Respons
Engine
Servlet 1 Servlet 2 Servlet 3
Ressourcen
Dateien,
Abbildung 18) Grobe Architektur von Webanwendungen mit ServletsDatenbanken,
Servlets vs. Common Gateway Interface
Java Servlets sind vergleichbar mit dem Common Gateway Interface (CGI) und
proprietären Server-Erweiterungen, haben aber folgende Vorteile:
Markus Loeberbauer 2010, 2011
80
Praktikum aus Softwareentwicklung 2

Java Servlet
Schneller als CGI-Skripte, weil der Code im Speicher bleibt und der Prozess
nach einer Anfrage weiterläuft. Bei CGI wird pro Aufruf ein Prozess
gestartet

Unterstützung für Sitzungen, CGI ist von sich aus zustandslos

In Java entwickelt:
o Nur von der JavaVM abhängig, aber sonst Systemunabhängig
o Große Klassenbibliothek
Entwickeln eines Servlets
Will man ein Servlet entwickeln, muss man eine Klasse schreiben die das Interface
Servlet im Packet javax.servlet implementiert. Servlets sind generisch gehalten, die
Methode service(ServletRequest, ServletResponse) bekommt eine Anfrage als
ServletReqest und beantwortet diese im ServletResponse. Dabei wird keine
Aussage getroffen woher die Anfragen kommen. Meistens schreibt man aber
Servlets für das Web, die Http-Anfragen beantworten. Dazu leitet man von
HttpServlet ab, das ist eine abstrakte Basisklasse die von GenericServlet ableitet
und Servlet implementiert.
HttpServlet hat eine Methode für jede Http-Methode (GET, HEAD, POST, PUT,
DELETE, TRACE, CONNECT und OPTIONS), zB doGet für Get-Anfragen und doPost
für Post-Anfragen. Je nachdem welche Http-Methoden man unterstützen will
muss man die zugehörige Servlet-Methode überschreiben. Das Servlet in
Abbildung 19 beantwortet Get-Anfragen mit dem Text „Hello!“.
Markus Loeberbauer 2010, 2011
81
Praktikum aus Softwareentwicklung 2
Java Servlet
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
try {
out.println("Hello!");
} finally {
out.close();
}
}
}
Abbildung 19) Beispiel Hello World Servlet
Installieren von Web-Anwendungen
Um Web-Anwendungen in einem Servlet-Container wie zB Tomcat zu installieren
muss man einen Deployment Descriptor (web.xml) schreiben. Für das Beispiel in
Abbildung 19 ist die web.xml in Abbildung 20 gegeben. Abbildung 21 zeigt die
Verzeichnisstruktur von Tomcat 6 (Tomcat 7 hat die gleiche Verzeichnisstruktur)
mit installiertem Hello-Beispiel. Im Verzeichnis webapps sind die installierten WebAnwendungen, in unserem Fall hello5. Im Verzeichnis der Web-Anwendung kann
beliebiger Inhalt liegen zB: Bilder oder HTML-Dateien, dieser Inhalt kann über
Web-Zugriffe abgefragt werden. Die einzige Ausnahme ist das Verzeichnis WEB-
INF, das ist vor Zugriffen von außen geschützt. In diesem liegen die Servlets im
Verzeichnis classes und falls nötig jar-Dateien im Verzeichnis lib.
Markus Loeberbauer 2010, 2011
82
Praktikum aus Softwareentwicklung 2
Java Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>
at.jku.ssw.psw2.servlet.hello.HelloServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello.do</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>hello.do</welcome-file>
</welcome-file-list>
</web-app>
Abbildung 20) web.xml für Beispiel Hello World Servlet
Abbildung 21) Verzeichnisstruktur von Tomcat 6
Markus Loeberbauer 2010, 2011
83
Praktikum aus Softwareentwicklung 2
Java Servlet
Lebenszyklus von Servlets
Der Servlet-Container lädt die Klasse des Servlets, erzeugt ein (= 1) Objekt, d.h.
die Felder werden einmal initialisiert und leben so lange das Servlet-Objekt lebt.
Clients (Web-Browser) stellen Anfragen an den Web-Server, dieser ruft die ServletEngine auf und diese leitet die Anfrage an das Servlet weiter. Pro Anfrage erzeugt
die Servlet-Engine einen Thread, d.h. mehrere Threads können gleichzeitig
Methoden auf einem Servlet-Objekt ausführen. Die Servlet-Engine bestimmt wann
ein Servlet weggeworfen wird, dabei muss das Servlet Ressourcen frei geben und
eventuell seinen Zustand speichern. Der Lebenszyklus aus der Sicht eines Servlets
ist in Abbildung 22 zu sehen.
init(ServletConfig)
Variablen initialisieren, Ressourcen anfordern
service(HttpServletRequest, HttpServletResponse)
doGet(HttpServletRequest, HttpServletResponse)
doPost(HttpServletRequest, HttpServletResponse)
doPut(HttpServletRequest, HttpServletResponse)
...
Abbildung 22) Lebenszyklus eines Servlets
Sitzungen (Sessions)
Http ist ein nicht sitzungsorientiertes Protokoll. Sinnvolle Web-Anwendungen
benötigen aber Sitzungen, zB: Warenkorb einer Shop-Anwendung oder
Anmeldung einer E-Mail-Anwendung. Intern verwenden Servlets Cookies, URLParameter und versteckte Formular-Felder zur Sitzungs-Verwaltung. Für den
Programmierer wird die Sitzungsverwaltung über die Klasse HttpSession
abstrahiert. Ein Objekt dieser Klasse kann man von
HttpServletRequest.getSession() abfragen.
Markus Loeberbauer 2010, 2011
84
Praktikum aus Softwareentwicklung 2
Java Servlet
Servlet 3.0
Am 10 Dezember 2009 hat Sun die Spezifikation für Servlet 3.0 veröffentlicht. Mit
Version 3.0 werden Annotationen eingeführt mit denen man Informationen aus
dem Deployment Descriptor direkt zu den Servlet-Klassen schreiben kann. Das
Beispiel Hello World Servlet aus Abbildung 19 kann mit der Annotation
@WebServlet angereichert werden, dadurch kann man die Information aus
web.xml entfernen. Der neue Code und die neue web.xml ist in Abbildung 23 bzw.
Abbildung 24 gegeben.
@WebServlet(name = "HelloServlet", urlPatterns = {"/hello.do"})
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
try {
out.println("Hello from JavaEE 6!");
} finally {
out.close();
}
}
}
Abbildung 23) Beispiel Hello World Servlet mit @WebServlet Annotation
Markus Loeberbauer 2010, 2011
85
Praktikum aus Softwareentwicklung 2
Java Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>hello.do</welcome-file>
</welcome-file-list>
</web-app>
Abbildung 24) web.xml für Beispiel Hello World Servlet mit @WebServlet Annotation
Um dieses Servlet ausführen zu können braucht man einen Web-Container der
Servlets in der Version 3.0 unterstützt, zB: GlassFish Server 3, jetty://
(Experimental) oder Tomcat 7.
JavaServer Pages (Erstellen von HTML-Seiten)
Servlets sind die Basis der Java Web-Technologie. Will man aber HTML-Seiten aus
Servlets erstellen ist das sehr aufwendig. Dazu müsste man programmatisch, über
den PrintWriter, den Text in den HttpServletResponse schreiben. Daher hat Sun
die JavaServer Pages eingeführt, um diesen Standard-Anwendungsfall zu
unterstützen.
JavaServer Pages (JSP) werden vom Web-Container in Servlets übersetzt und dann
wie alle anderen Servlets behandelt. Der Web-Container übersetzt JSPs bei
Veränderungen automatisch neu. Schreiben von JSPs ist Servlet-Programmierung
auf abstrakter Ebene.
Man kann mit JSP alles machen was man auch mit Servlets machen kann. Am
besten ist es jedoch Servlets und JSP als Team zu verwenden: wobei Servlets den
Ablauf steuern, die Daten aufbereiten und die Geschäftslogik ansprechen; und
JSPs die Darstellung übernehmen. Die Trennung entspricht dem Model-ViewMarkus Loeberbauer 2010, 2011
86
Praktikum aus Softwareentwicklung 2
Java Servlet
Controller-Muster und heißt in der Java Servlet Welt „Model 2 Architecture“. Die
selten verwendete „Model 1 Architecture“ benutzt JSPs zur Ablaufsteuerung und
Darstellung; die Datenaufbereitung und Verbindung zur Geschäftslogik erfolgt in
Java Beans. Heute wird fast nur die „Model 2 Architecture“ eingesetzt, weil sie
klarer und einfacher ist.
Abbildung 25 zeigt eine einfache Hello World JSP, Abbildung 26 zeigt dazu
Ausschnitte aus dem generierten Java Servlet. Man kann sehen wie die Zeilen aus
der JSP-Datei in einzelne write-Anweisungen im Code verwandelt werden. Will
man für eine JSP das generierte Servlet sehen, kann man das in Tomcat unter:
work/Catalina/localhost/<Web-Anwendung>/org/apache/jsp/<JSPName>_jsp.java.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<title>Hello Page</title>
</head>
<body>
Hello!
</body>
</html>
Abbildung 25) Beispiel Hello World JSP
Markus Loeberbauer 2010, 2011
87
Praktikum aus Softwareentwicklung 2
Java Servlet
public final class hello_jsp extends
org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
// ...
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request,
response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write("
<title>Hello Page</title>\r\n");
out.write(" </head>\r\n");
out.write(" <body>\r\n");
out.write("
Hello!\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
out.write("\r\n");
} catch (Throwable t) { /* ... */ } finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
Abbildung 26) Generiertes Servlet für Hello World JSP
Markus Loeberbauer 2010, 2011
88
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
Dynamischer Kontext in JavaServer Pages
Mit JavaServer Pages (JSP) kann man dynamischen Inhalt erzeugen. Dazu kann
man: Java-Code in die Seite schreiben, Ausdrücke in der Skriptsprache Expression
Language (EL) auswerten und programmierte Tags einfügen.
Skriptelemente
Skriptelemente sind die älteste und unübersichtlichste Art Logik in JSP
einzubringen. Skriptelemente enthalten Java-Code (Scriptlet) der direkt in die
Handler-Methode kopiert wird wenn der Web-Container das Servlet aus der JSP
erzeugt. Der JSP-Compiler erkennt Scriptlets an dem Klammern-Paar: <% und %>.
Abbildung 27 zeigt ein einfaches Beispiel für Scriptlets. Man sieht, dass Scriptlets
und HTML beliebig gemischt werden können. Das erste Scriptlet beginnt mit einer
Schleife, dann kommt HTML-Code und das zweite Scriptlet schließt die Schleife
ab. Der JSP-Compiler kopiert dabei den HTML-Code in out.write-Anweisungen
und den Scriptlet-Code direkt in die generierte Handler-Methode. Vertippt man
sich im Java-Code, kann die Fehlersuche mühsam sein. Je nach verwendetem
Web-Container werden Fehlermeldungen vom Java-Compiler mehr oder weniger
auf die eigentliche Fehlerstelle in der JSP-Seite zurückgeführt.
Markus Loeberbauer 2010, 2011
89
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head><title>JSP Scriptlet Page</title></head>
<body>
<h1>Let's Count!</h1>
<%
for (int i = 0; i < 10; ++i) {
out.println(i);
%>
,
<%
}
%>
...
</body>
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , ...
Let's Count!
</html>
Abbildung 27) Beispiel-JSP mit Scriptlet
In seltenen Fällen muss man in JSP Felder und Methoden deklarieren. Das ist in
Deklarations-Elementen möglich, sie werden mit <%! eingeleitet und mit %>
abgeschlossen.
Abbildung 28 zeigt wie man in einer JSP ein Feld deklariert, hier die Konstante
MESSAGE_FORMAT, und wie man Methoden deklariert, hier die Methode format.
Wie bei programmierten Servlets muss man auch bei durch JSP generierten
Servlets darauf achten, dass durch ein Objekt mehrere Benutzeranfragen
gleichzeitig bearbeitet werden können. Also auch hier gilt, Felder sind potentiell
ein Synchronisations-Problem.
Markus Loeberbauer 2010, 2011
90
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<%@page contentType="text/html" pageEncoding="UTF-8"
import="java.util.Date"%>
<html>
<head><title>JSP Declarations Page</title></head>
<body>
<%
String message = format("Hello World!");
out.println(message);
%>
</body>
<%!
private static final String MESSAGE_FORMAT = "%s : %s";
%>
<%!
%>
</html>
private String format(String message) {
return String.format(MESSAGE_FORMAT, new Date(),
message);
}
Fri Jun 04 08:27:27 CEST 2010 : Hello World!
Abbildung 28) Beispiel-JSP mit Feld- und Methoden-Deklaration
Im Beispiel in Abbildung 28 rufen wir eine Methode auf und geben das Ergebnis
in der Web-Seite aus. Das wird in JSP mit Expression-Elementen unterstützt.
Expression-Elemente werden mit <%= eingeleitet und mit %> abgeschlossen.
Innerhalb eines Expression-Elements muss ein Ausdruck stehen, zB: eine
Berechnung oder ein Methodenaufruf mit Rückgabewert. In Abbildung 29 werden
Expression-Elemente verwendet, um das Ergebnis der Methode format und um
die Zahlen 0 bis 9 in einer Schleife auszugeben.
Kommentare in JSP werden in <%-- und --%> eingeschlossen, diese Kommentare
kommen nur in der JSP vor. Will man Kommentare auch im generierten Servlet
wiederfinden, dann muss man sie als normale Java-Kommentare in Scriptlets
schreiben.
Markus Loeberbauer 2010, 2011
91
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<%@page contentType="text/html" pageEncoding="UTF-8"
import="java.util.Date"%>
<html>
<head><title>JSP Expressions Page</title></head>
<body>
<%= format("Hello World!") %><br>
<%
for (int i = 0; i < 10; ++i) {
%>
<%= i %>
<%
}
%>
</body>
<%!
private static final String MESSAGE_FORMAT = "%s : %s";
%>
<%!
%>
</html>
private String format(String message) {
return String.format(MESSAGE_FORMAT, new Date(),
message);
}
Fri Jun 04 08:42:31 CEST 2010 : Hello World!
0123456789
Abbildung 29) Beispiel-JSP mit Expressions
Weitere Befehle in JSP:

<jsp:include>: Einfügen einer anderen Seite zur Laufzeit.

<jsp:forward>: Weiterleiten an eine andere Seite.

<jsp:param>: Parameter an eine andere Seite weitergeben, die mit
<jsp:include> oder <jsp:forward> verwendet wird.

<jsp:useBean>: Verwenden von JavaBean-Komponenten in JSP. Syntax:
<jsp:useBean id="Instanzname" scope="Geltungsbereich"
class="Klassenname"/>
Markus Loeberbauer 2010, 2011
92
Praktikum aus Softwareentwicklung 2

Dynamischer Kontext in JavaServer Pages
<jsp:getProperty> und <jsp:setProperty>: Abfragen bzw. Setzen eines
Bean-Properties.
Die JSP-Syntax der Skriptelemente mit dem Prozentzeichen passt nur schlecht
zum XML-Stil von HTML. Deshalb gibt es für die Skriptelemente eine XML-Syntax.
Dabei heißen die Tags: <jsp:scriptlet> für Scriptlets, <jsp:expression> für
Ausdrücke und <jsp:declaration> für Deklarationen. Dabei ist zu beachten, dass
innerhalb einer Seite entweder konsequent die JSP-Syntax oder die XML-Syntax
verwendet werden muss. Einen Überblick über die gesamte Syntax von JSP kann
man im JSR 152 oder unter http://java.sun.com/products/jsp/docs.html
bekommen.
Implizite Objekte in JSP
In den Scriptlet-Beispielen haben wir mit out.println Text in die Web-Seite
geschrieben. Aber wo kommt dieses out her? In JSP werden einige Objekte in der
Handler-Methode implizit zur Verfügung gestellt:

out, Klasse: JspWriter (Schreiben von Text in die Web-Seite)

request, Klasse: HttpServletRequest

response, Klasse: HttpServletResponse

session, Klasse: HttpSession

application, Klasse: ServletContext

config, Klasse: ServletConfig

exception, Klasse: JspException (Nur verfügbar in Error-Pages. Treten Fehler
in Servlets oder JSP auf wird standardmäßig eine technische Fehlerseite
erzeugt, diese hilft dem Entwickler, für den Anwender wirkt sie aber
unprofessionell. Daher kann man in einer JSP page-Direktive eine JSP
angeben die im Fehlerfall angezeigt werden soll, zB:
<%@ page errorPage="/error.jsp" %>. Auch im Deployment Descriptor
(web.xml) können mit error-page-Abschnitten Fehlerbehandlungsseiten
angegeben werden. Mit error-code können http-Fehlercodes abgefangen
Markus Loeberbauer 2010, 2011
93
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
werden (zB: 404); und mit exception-type können Exceptions abgefangen
werden.

pageContext, Klasse: PageContext (Kapselt die Impliziten Objekte, kann zB
benutzt werden, um diese an eine Methode zu übergeben.

page, Klasse: Object (Verweis auf das Seiten-Objekt = this-Pointer)
Expression Language
Skriptelemente verleiten dazu, zu viel Java-Code in eine JSP einzubetten, daher
möchte man gerne auf Skriptelemente verzichten. Die Verwendung von Beans ist
häufig zu aufwändig oder zu eingeschränkt. Aus diesen Gründen hat Sun mit JSP
2.0 die Expression Language (EL) eingeführt. Ausdrücke in EL beginnen mit einem
Dollar-Zeichen und sind in geschwungene Klammern eingeschlossen, zB: ${42.0 /
23}, ${person.name} oder ${car.engine.power}.
EL erlaubt den Zugriff auf Properties von Properties, usw. Damit ist es mächtiger
als die JSP-Bean-Syntax. Und dabei wesentlich kompakter. Für jeden PropertyZugriff kommt ein Punkt gefolgt vom Namen des Properties. Existiert in einer
Klasse eine Methode getName(), dann heißt das Property name. Der Zugriff
erfolgt damit über ${objektname.name}. Der Aufbau von EL-Ausdrücken ist in
Abbildung 30 zu sehen.
Markus Loeberbauer 2010, 2011
94
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
${ersterBezeichner.weitereBezeichne
Implizite Objekte
pageScope
pageScope
requestScope
requestScope
sessionScope
sessionScope
applicationScope
applicationScope
param
Objekte werden
paramValues
zuerst im Page-,
Schlüssel einer Map
oder
Property
Muss sich an die Java
Namenskonvention halten!
dann im Request-,
header
dann im Session-
headerValues
und wenn sie dort
auch nicht gefunden
Abbildung 30) Aufbau eines EL-Ausdrucks
Muss man auf Elemente in einer Map oder Liste zugreifen, dann verletzen die
Zugriffsnamen häufig den Java-Namenskonventionen. In diesem Fall gibt es den
Klammer-Operator ([]) als Alternative zum Dot-Operator(„.“). Die Syntax sieht dann
wie folgt aus: ${objektname[bezeichner]} wobei objektname eine Map, ein Bean,
eine List oder ein Array sein kann; bezeichner ist ein Schlüssel in der Map, ein
Property, ein Listen-Index bzw. ein Array-Index. Der Zugriff kann auch
verschachtelt sein zB: ${cars[favoiritCars[0]]}.
Achtung, Property-Namen müssen unter Anführungszeichen gestellt werden, sonst
wird der Name selbst als Objekt gesucht, zB: der Ausdruck im Dot-Stil
${person.name} entspricht ${person["name"]} im Klammer-Stil. Verwendet man nur
${person[name]}, dann wird zuerst name ausgewertet und das Ergebnis als
Property-Name in person gesucht.
Markus Loeberbauer 2010, 2011
95
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
Operationen in der Expression Language
Mit EL kann man arithmetische und logische Operationen ausdrücken: Addition
(+), Subtraktion (-), Multiplikation (*), Division (/, div) und Divisionsrest (%, mod)
sowie logisches Und (&&, and), Oder (||, or) und Nicht (!, not). Vergleiche
durchführen: Gleichheit (==, eq), Ungleichheit (!=, ne), Kleiner (<, lt), Grösser (>,
gt), Kleiner gleich (<=, le) und Grösser gleich (>=, ge).
Die folgenden Literale sind in EL definiert: true, false, null, und empty (zeigt an ob
ein Attribut gesetzt ist, zB: ${not empty persons}. Das Schlüsselwort instanceof ist
reserviert, hat aber noch keine Bedeutung.
Java Standard Tag Libaray
Mit EL kann man einfach auf einzelne Werte zugreifen. Häufig benötigt man aber
auch einfache Kontrollstrukturen wie Iterationen oder Alternativen. Dazu kann die
Java Standard Tab Library (JSTL) genutzt werden. Seit JSP 2.1 (JavaEE 5) ist die
JSTL in Version 1.2 Teil der JavaEE-Spezifikation.
Damit man die JSTL in Tomcat nutzen kann muss man die Dateien jstl.jar und
standard.jar entweder in das lib-Verzeichnis von Tomcat oder seiner WebAnwendung kopieren. Die Dateien sind in der Standard-Installation von Tomcat in
den Beispielanwendungen zu finden.
Die JSTL ist nach Aufgaben in die Bereiche Core, Formatierung, Funktionen, SQL
Datenbank-Zugriff und XML Verarbeitung geteilt. Wir werden kurzen Einblick auf
den Core der JSTL geben; weitere Informationen können im JSR-52 nachgelesen
werden. Wichtige Tags der Core Tag Library sind: <c:out>, <c:forEach>, <c:if>,
<c:choose>, <c:when>, <c:otherwise>, <c:url> und <c:param>.
<c:out>
Mit dem Tag out kann man berechnete Ausgaben in die Webseite schreiben.
Dabei wird null mit einem gegebenen Default-Wert ersetzt, ist keiner gegeben mit
Markus Loeberbauer 2010, 2011
96
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
dem leeren String. Wichtig ist auch, dass folgende HTML-Sonderzeichen ersetzt
werden:

< gegen <

> gegen >

& gegen &

' gegen '

" gegen "
<c:forEach>
Mit dem forEach-Tag kann man über Sammlungen oder Zahlenbereiche iterieren,
Abbildung 31 zeigt eine Beispielanwendung. In der page-Direktive wird
isELIgnored auf false gesetzt, das stellt sicher, dass die EL-Ausdrücke richtig
übersetzt werden. In Tomcat 6 und Glassfish 3 ist das bereits der Standardwert. In
der Beispielanwendung wird davon ausgegangen, dass eine Sammlung mit dem
Namen „Cars“ in einem der Scopes verfügbar ist.
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
Print all cars:
<c:forEach var="car" items="${Cars}">
${car}<br />
</c:forEach>
Print the numbers from 0 to 23 with a step of 5:
<c:forEach begin="0" end="23" step="5" varStatus="counter">
${counter.count}<br />
</c:forEach>
</body>
...
</html>
Abbildung 31) JSTL Beispiel mit <c:forEach>
Markus Loeberbauer 2010, 2011
97
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<c:if>
Der Tag if wird eingesetzt wenn optionale Teile in einer JSP vorhanden sind,
Abbildung 32 zeigt ein Beispiel. Anders als in den meisten Programmiersprachen
fehlt bei dem if-Tag in JSTL ein else-Zweig, benötigt man eine Auswahl aus
mehreren Alternativen muss man entweder jede Alternative mit einem if-Tag
eindeutig beschreiben oder einen choose-Tag benutzen.
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
<html>
<body>
<c:if test="${car eq 'Smart'}">
Be smart drive Smart!
</c:if>
<c:if test="${car eq 'SUV'}">
Real men drive hard!
</c:if>
</body>
...
</html>
%>
Abbildung 32) JSTL Beispiel mit <c:if>
<c:choose>, <c:when>, <c:otherwise>
Der choose-Tag entspricht der switch-Anweisung oder if-else-if-Kaskade in
Programmiersprachen, Abbildung 33 zeigt ein Beispiel.
Markus Loeberbauer 2010, 2011
98
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
<html>
<body>
<c:choose>
<c:when test="${car eq 'Smart'}">
Be smart drive Smart!
</c:when>
<c:when test="${car eq 'SUV'}">
Real man drive hard!
</c:when>
<c:otherwise>
Porsche, all a lady can expect.
</c:otherwise>
</c:choose>
</body>
...
</html>
%>
Abbildung 33) JSTL Beispiel mit <c:choose>, <c:when> und <c:otherwise>
<c:url>, <c:param>
Mit <c:url> kann man Urls mit Parametern in JSP zusammensetzen, siehe
Abbildung 34. Dieser Tag ermöglicht Webanwendungen ohne Cookies, dazu fügt
der url-Tag eine Session-ID als Parameter an die Url, falls der Client keine Cookies
unterstützt.
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
Please visit our
<a href="<c:url value='exhibition.do'>
<c:param name='color' value='${customer.favouritColor}' />
</c:url>"> car exhibition</a>
to see your next vehicle!
<a href="<c:url value='logout.jsp' />" >Logout</a>
</body>
</html>
Abbildung 34) JSTL Beispiel mit <c:url> und <c:param>
Markus Loeberbauer 2010, 2011
99
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
JSP aus Servlets Nutzen
In sauber entwickelten Programmen sollte die Businesslogik von Servlets aus
angesprochen und die Oberfläche in JSP gestaltet werden. Diese Vorgangsweise
heißt bei JavaEE Model 2 Architektur und setzt das Model-View-Controller-Muster
bei Webanwendungen um.
Über die Klasse javax.servlet.RequestDispatcher kann man von einem Servlet auf
ein anderes Servlet oder eine JSP umleiten. Einen RequestDispatcher kann man
aus dem ServletContext über die Methode getRequestDispatcher(String
absolutPath) aus einem Absoluten Pfad holen. Hier darf der Pfad auch in das
Verzeichnis WEB-INF zeigen, obwohl dieses Verzeichnis gegen Zugriffe von außen
geschützt ist. Oder über die Methode getNamedDispatcher(String name) aus
einem Namen erzeugen, der Name entspricht dem Namen (servlet-name) aus
dem Deployment Descriptor (web.xml). Relativ zum aktuellen Servlet kann man
sich auch aus dem ServletRequest über die Methode getRequestDispatcher(String
path) einen RequestDispatcher holen.
Hat man einen RequestDispatcher, dann kann man den Request über die
Methoden include oder forward weiterleiten. Nutzt man die Methode include
behält man die Kontrolle und kann nachdem das gerufene Servlet fertig ist noch
etwas ausgeben oder aufräumen. Meistens ist das aber unnötig und man
verwendet forward, dabei gibt man die Kontrolle an das gerufene Servlet ab.
Die aufbereiteten Daten gibt man an das gerufene Servlet als Attribute im
HttpServletRequest mit. Attribute kann man in den Request über die Methode
setAttribute(String name, Object o) stellen und über getAttribute(String name)
abfragen. In einer JSP kann man Attribute über EL abfragen, wobei man den
Namen des Attributes angeben muss, zB: ${name}.
Abbildung 35 zeigt ein Servlet, das den Parameter name aus dem Request
ausliest, den gelesenen Wert in Großbuchstaben umwandelt, das Ergebnis als
Markus Loeberbauer 2010, 2011
100
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
Attribut ablegt und den Request an eine JSP weiterleitet. Wobei die JSP im
Verzeichnis WEB-INF liegt (und damit vor direkten Zugriffe von außen geschützt
ist). Die zugehörige JSP ist in Abbildung 36 gezeigt.
@WebServlet(name = "Greeter", urlPatterns = {"/Greeter"})
public class Greeter extends HttpServlet {
public static final String NAME_PARAMETER = "name";
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
String name = request.getParameter(NAME_PARAMETER);
String upperCaseName;
upperCaseName = name != null ? name.toUpperCase() : "???";
request.setAttribute(NAME_PARAMETER, upperCaseName);
RequestDispatcher rd = getServletContext()
.getRequestDispatcher("/WEB-INF/jsp/namePrinter.jsp");
rd.forward(request, response);
}
}
Abbildung 35) Beispiel: Servlet mit Request-Weiterleitung
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>Name Printer Page</title>
</head>
<body>
<h1>Hello <c:out value="${name}"/>!</h1>
</body>
</html>
Abbildung 36) Beispiel: JSP das einen Namen über EL und <c:out> ausgibt.
Markus Loeberbauer 2010, 2011
101
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
Schemadefinitionen für web.xml
Der Deployment Descriptor (web.xml) ist eine XML-Datei, hier sind die Rahmen für
die gängigen Versionen abgebildet. Version 2.3 wird noch über DTD beschrieben,
d.h. die Elemente innerhalb der web.xml müssen in der richtigen Reihenfolge
angegeben werden, zB: servlet vor servlet-mapping.
Version 2.3
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- ... -->
</web-app>
Version 2.4
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- ... -->
</web-app>
Version 2.5
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- ... -->
</web-app>
Version 3.0
<?xml version="1.0" encoding="UTF-8"?>
Markus Loeberbauer 2010, 2011
102
Praktikum aus Softwareentwicklung 2
Dynamischer Kontext in JavaServer Pages
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!-- ... -->
</web-app>
Markus Loeberbauer 2010, 2011
103
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
JSP Custom Tags
EL und die JSTL ersetzen Scriptlets in weiten Teilen von Web-Anwendungen,
manchmal braucht man aber Funktionen die in der JSTL fehlen. Da wir auf
Scriptlets verzichten wollen, um die JSP-Seiten sauber zu halten brauchen wir eine
Lösung eigenen Code in Tags zu packen. In JSP können wir dazu Custom Tags
implementieren. Seit JSP 2.0 (zeitgleich mit Servlet API 2.4 und J2EE 1.4) gibt es
mit Simple Tags und Tag Files bequeme Möglichkeiten eigene Tags zu erzeugen.
Vor JSP 2.0 war es komplexer eigene Tags zu implementieren. Sollte es notwendig
sein die alte API zu verwenden findet man dazu Dokumentation wenn man nach
JSP classic Tags sucht.
Tag Files
Tag Files sind die einfachste Art eigene Tags zu erstellen. Mit Tag Files kann man
Teile einer JSP-Seite auslagern, zB: Header oder Footer die auf allen Seiten einer
Webanwendung gleich sind. Tag Files können parametriert werden, zB der Titel
der Web-Seite im Header. Größere Text- und Html-Blöcke können als Tag-Body
übergeben werden.
Installiert werden Tag Files indem man sie im Verzeichnis WEB-INF/tags/ (oder
einem Unterverzeichnis davon) ablegt, um sie zu nutzen muss man eine taglibDirektive in die JSP-Seite einfügen. Über die taglib-Direktive muss angegeben
werden welcher Präfix verwendet werden soll und wo die Tags abgelegt sind.
In Abbildung 37 ist eine JSP-Seite zu sehen die Tag Files nutzt. In der taglibDirektive ist angegeben, dass die Tags mit dem Präfix psw2tf angesprochen
werden, und dass die Tags direkt im Verzeichnis WEB-INF/tags liegen. Das Beispiel
nutzt die Tags header, footer und disclaimer. Mit dem Tag header wird der HTMLSeiten-Header eingebunden, er benötigt den Parameter title, dieser wird als Titel
der Webseite ausgegeben. Parameter in einem Tag-File werden mit der Direktive
attribute bekannt gemacht. Die Implementierung ist in Abbildung 38 (Datei:
Markus Loeberbauer 2010, 2011
104
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
header.tag) gegeben. Weiter nutzt die Seite den Tag footer der die Webseite
abschließt (siehe Abbildung 39, Datei: footer.tag), er ist parameterlos. Und den
Tag disclaimer (siehe Abbildung 40) der den übergebenen Body als Parameter
verwendet und als Disclaimer-Text ausgibt.
Will man Tag-Files in mehreren Anwendungen verwenden, dann lohnt es sich
diese in eine JAR-Datei zu verpacken. Tag-Files in JAR-Dateien müssen im
Verzeichnis META-INF/tags/ (oder einem Unterverzeichnis davon) liegen und eine
Beschreibung der Tags muss als Tag Library Descriptor vorliegen. Der Aufbau
dieser Beschreibungen ist im Abschnitt Simple Tags beschrieben.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="psw2tf" tagdir="/WEB-INF/tags/" %>
<psw2tf:header title="Hello Page"/>
<h1>Hello PSW2 Students!</h1>
<p>
Welcome to the shiny world of JSP programming.
In this course you will learn everything
you'll ever need!
</p>
Hello PSW2
<psw2tf:disclaimer>
Everything written on this
Students!
page may be exaggerated,
or just plain wrong.
Welcome to the shiny world of JSP programming. In
this course you will learn everything you'll ever
</psw2tf:disclaimer>
need!
<psw2tf:footer />
Abbildung 37) JSP Seite mit Custom Tags aus Tag Files
Markus Loeberbauer 2010, 2011
105
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
<%@tag description="standard header tag for our web application"
pageEncoding="UTF-8"%>
<%@attribute name="title" required="true"
description="title of the page" rtexprvalue="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>
Abbildung 38) Beispiel Tag mit Parameter (Seiten-Header)
<%@tag description="standard footer for our web application"
pageEncoding="UTF-8"%>
</body>
</html>
Abbildung 39) Beispiel Tag File ohne Parameter (Seiten-Footer)
<%@tag description="web site disclaimer" pageEncoding="UTF-8"%>
<p>
Disclaimer<br>
<em>
<jsp:doBody />
</em>
</p>
Abbildung 40) Beispiel Tag File das den Body des Tags nutzt (Disclaimer).
Simple Tags
Tag-Files vermeiden Code-Verdopplung in JSP-Seiten, aber wenn man
Programmlogik braucht sind Tag Files zu wenig. Dann kommen Custom-Tags ins
Spiel. Die einfachste Möglichkeit Custom-Tags zu programmieren sind Simple
Tags. Jeder Simple-Tag muss das Interface SimpleTag implementieren, in der
Praxis leitet man dafür von der Klasse SimpleTagSupport ab.
Markus Loeberbauer 2010, 2011
106
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="psw2tag"
uri="http://ssw.jku.at/Teaching/Lectures/PSW2/2010/" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title>Students</title></head>
<body>
Active
<h1>Active Students</h1>
<ol>
Students
<psw2tag:StudentFilter activeOnly="true"
students="${students}"
1. Susi Brain
var="student">
2. Max Power
<li>${student.name}</li>
</psw2tag:StudentFilter>
All Students
</ol>
<h1>All Students</h1>
1. Susi Brain
<ol>
<psw2tag:StudentFilter students="${students}">
<li>${var.name}</li>
</psw2tag:StudentFilter>
</ol>
</body>
</html>
Abbildung 41) Beispiel-JSP-Seite mit Simple Tags
Abbildung 41 zeigt eine JSP-Seite die Studenten, mit Hilfe des Custom Tags
StudentFilter, anzeigt. Mit der Direktive taglib wird die Tag-Library in die Seite
eingebunden. Abbildung 42 zeigt den Tag StudentFilterTag.
Markus Loeberbauer 2010, 2011
107
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
public class StudentFilterTag extends SimpleTagSupport {
private String controlVariable = "var";
private boolean activeOnly;
private Iterable<Student> students;
@Override
public void doTag() throws JspException, IOException {
JspFragment body = getJspBody();
if (body == null) { return; }
JspContext context = getJspContext();
for (final Student student : students) {
if (activeOnly && !student.isActive()) { continue; }
context.setAttribute(controlVariable, student);
body.invoke(null);
}
}
public void setStudents(Iterable<Student> students) {
this.students = students;
}
public void setActiveOnly(boolean activeOnly) {
this.activeOnly = activeOnly;
}
public void setVar(String controlVariable) {
this.controlVariable = controlVariable;
}
}
Abbildung 42) Beispiel Simple Tag; Liefert die Studenten der Reihe nach, gefiltert nach Aktivität
Simple Tags erben von SimpleTagSupport und überschreiben die Methode doTag,
diese wird zum Rendern des Tags aufgerufen. Der Lebenszyklus eines Tags ist wie
folgt:
0. Der Web-Container lädt die Tag-Klasse (bei der ersten Verwendung eines
Tags).
1. Erzeugt ein Objekt des gewünschten Tags über den Default-Konstruktor.
2. Setzt den JSP-Context über setJSPContext.
3. Setzt den Eltern-Tag, falls der Tag in einem anderen Tag geschachtelt ist.
4. Setzt alle Attribute über die Setter-Methoden.
Markus Loeberbauer 2010, 2011
108
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
5. Setzt den Body über setJSPBody, falls der Tag einen Body hat.
6. Ruft die Methode doTag auf.
7. Verwirft das Tag-Objekt.
Aus dem Lebenszyklus sehen wir, dass eine Tag-Klasse einen einen
parameterlosen Default-Konstruktor benötigt und ein Tag-Objekt nur einmal
benutzt wird, d.h. man kann sich darauf verlassen, dass die Attribute die richtigen
Werte haben; und manuelle Bereinigung des inneren Zustands unnötig ist.
Damit der Web-Container die Tags findet muss man eine Tag Library Description
(TLD) schreiben. Abbildung 43 zeigt den TLD für unseren StudentFilter, darin ist
festgelegt welche Klasse für den Tag verwendet werden soll und welche Attribute
der Tag hat. Ein TLD hat einen eindeutigen Namen (uri), eine Kurzbezeichnung
(short-name), eine Version (tlib-version) und optionale Daten wie zB: eine
Beschreibung (description) und ein Icon (icon). In einer TLD können beliebig viele
Tags (tag) und Tag Files (tag-file) beschrieben werden. Die TLD-Dateien (Endung
.tld) muss im Verzeichnis WEB-INF oder einem Unterverzeichnis davon liegen.
Häufig legt man sie in das Verzichnis WEB-INF/tlds oder in das Verzeichnis wo die
geschützten JSPs liegen (häufig WEB-INF/jsp).
Abbildung 44 zeigt die Datenklasse Student für das Beispiel. Ein Student hat einen
Namen und einen Status ob er aktiv ist. Abbildung 45 zeigt das Servlet mit der
Anbindung an die Geschäftslogik und dem Aufruf der Seite showStudents.jsp.
Anmerkung: Alle notwendigen Klassen um Tags zu implementieren liegen im
Paket javax.servlet.jsp.tagext.
Markus Loeberbauer 2010, 2011
109
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<short-name>psw2tag</short-name>
<tlib-version>1.0</tlib-version>
<uri>http://ssw.jku.at/Teaching/Lectures/PSW2/2010/</uri>
<tag>
<name>StudentFilter</name>
<tag-class>
at.jku.ssw.psw2.jsptutorial.tag.StudentFilterTag
</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>students</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Iterable</type>
</attribute>
<attribute>
<name>activeOnly</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>boolean</type>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
<description>control variable</description>
</attribute>
</tag>
</taglib>
Abbildung 43) Tag Library Descriptor für den Beispiel-Tag StudentFilterTag
Markus Loeberbauer 2010, 2011
110
Praktikum aus Softwareentwicklung 2
JSP Custom Tags
package at.jku.ssw.psw2.jsptutorial.model;
public class Student {
private final String name;
private final boolean active;
public Student (final String name, final boolean active) {
this.name = name;
this.active = active;
}
public String getName() {
return name;
}
public boolean isActive() {
return active;
}
}
Abbildung 44) Beispiel-Datenklasse: Student
public class ShowStudentsServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
request.setAttribute("students", getStudents());
RequestDispatcher rd = request.getRequestDispatcher(
"/WEB-INF/jsp/showStudents.jsp");
rd.forward(request, response);
}
private Iterable<Student> getStudents() {
// read students, e.g. from a database
// ...
return students;
}
}
Abbildung 45) Beispiel-Servlet: Liest Studenten und leitet an die Beispiel-JSP-Seite weiter
Markus Loeberbauer 2010, 2011
111
Praktikum aus Softwareentwicklung 2
Applets
Applets
Mit Applets hat Sun die Möglichkeit geschaffen Java Programme in Webseiten
einzubetten. Applets können mit AWT und mit Swing entwickelt werden, dazu
muss man eine Klasse von java.applet.Applet bzw. javax.swing.JApplet ableiten.
Damit Applets auf dem lokalen Rechner keinen Schaden anrichten können werden
sie in einer sicheren Umgebung (Sandbox) ausgeführt. Dadurch wird verhindert,
dass: Applets auf das lokale Dateisystem zugreifen, Netzwerkverbindungen
aufbauen, gefährliche System-Aufrufe ausführen (zB: System.exit), die
Zwischenablage auslesen oder sicherheitskritische System-Properties abfragen.
Benötigt man Zugriff auf diese sicherheitskritischen Dinge muss man sein Applet
signieren, dann fragt Java den Benutzer ob er dem Applet die Zugriffe erlaubt.
Der Lebenszyklus von Applets besteht aus vier Methodenaufrufen:
1. init, wird aufgerufen sobald das Applet geladen wird, hier kann zB: die GUI
aufgebaut, Threads gestartet oder Ressourcen geladen werden
2. start, wird jedes Mal aufgerufen wenn das Applet angezeigt wird, zeigt ein
Applet eine Animation, kann diese hier gestartet werden
3. stop, der Browser ruft diese Methode wenn das Applet nicht mehr
angezeigt wird, hier kann die Animation wieder gestoppt werden
4. destroy, hier können Ressourcen freigegeben werden
Wir beschäftigen uns hier mit der Swing-Version von Applets, und wie Swing
allgemein sind auch JApplets nicht threadsicher. Das heißt, Änderungen an der
GUI müssen im GUI-Thread erfolgen.
Abbildung 46 zeigt eine JSP-Seite mit einem Applet, im Tag applet wird festgelegt
welche Ausmaße das Applet haben soll, wo die Jar-Datei liegt und wie die AppletKlasse heißt. Im Body des Applet-Tags können Parameter angegeben werden, in
unserem Fall wird der Parameter name auf Alex gesetzt. Außerdem ist im Body
Markus Loeberbauer 2010, 2011
112
Praktikum aus Softwareentwicklung 2
Applets
ein Text hinterlegt der angezeigt werden soll falls der Browser nicht mit Applets
umgehen kann. Die zugehörige Applet-Klasse ist in Abbildung 47 gegeben.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Applet Test</title>
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8">
</head>
<body>
<applet width="200" height="150"
code="at.jku.ssw.psw2.applet.TestApplet"
codebase="<c:url value="/"/>"
archive="TestApplet.jar">
<param name="name" value="Alex"/>
Please enable Applets in your browser.
</applet>
</body>
</html>
Abbildung 46) Beispiel JSP-Seite mit Applet
Markus Loeberbauer 2010, 2011
113
Praktikum aus Softwareentwicklung 2
Applets
package at.jku.ssw.psw2.applet;
public class TestApplet extends JApplet {
private String name;
private JLabel label;
@Override public void init() {
name = getParameter("name");
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
label = new JLabel("Name: " + name);
getContentPane().add(label, BorderLayout.CENTER);
}
});
} catch (Exception ex) {
Logger.getLogger(TestApplet.class.getName())
.log(Level.SEVERE, null, ex);
}
// allocate resources here
}
@Override public void start() {
// start animations here
}
@Override public void stop() {
// stop animations here
}
@Override public void destroy() {
// cleanup resources here
}
}
Abbildung 47) Beispiel Applet, zeigt den Wert des Parameters "name" in einem Label an
Markus Loeberbauer 2010, 2011
114
Praktikum aus Softwareentwicklung 2
Java Service
Java Service
Ein Java Service ist ein Interface, Objekte die dieses Interface implementieren
können zur Laufzeit angefordert werden. Implementierungen eines Java Services
(Java Service Provider) müssen das Service-Interface implementieren, in einer JarDatei liegen und dort als Service Provider registriert werden. Dazu muss man im
Verzeichnis META-INF/services/ eine Text-Datei mit dem Namen des Interfaces
anlegen, als Inhalt der Datei muss der voll qualifizierte Name der Klasse
angegeben werden. Es können in der Datei auch mehrere Klassen angegeben
werden, diese müssen jeweils in einer eigenen Zeile stehen. Für Verwender gibt es
seit Java 6 mit der Klasse java.util.ServiceLoader eine bequeme Möglichkeit Service
Provider zu erzeugen. In Java Versionen vor 6 muss man die Dateien im
Verzeichnis services auslesen (zB mit Class.getResourceAsStream) und Service
Provider über Reflection (zB mit Class.forName und Class.newInstance) anlegen.
Services können verwendet werden, um Anwendungen erweiterbar oder Teile
davon austauschbar zu machen; oder um Anwendungen klar zu strukturieren. In
der Praxis werden Java Services (zB in Java selbst) eingesetzt, um XML-Provider
und JDBC-Treiber einzubinden. NetBeans nutzt Java Services als Basis des Plug-inMechanismus.
Hinweis! Als Services werden häufig Factories geliefert
mit denen man dann die gewünschten Objekte
erzeugen kann.
Beispiel
Abbildung 47 zeigt eine Beispiel-Klasse die Services für das Interface Runnable
lädt und die run-Methoden der gefunden Services ausführt. In Abbildung 48 ist
ein Service abgebildet, das das Interface Runnable implementiert.
Markus Loeberbauer 2010, 2011
115
Praktikum aus Softwareentwicklung 2
Java Service
public class Executor {
public static void main(String[] args) {
ServiceLoader<Runnable> runnableLoader =
ServiceLoader.load(Runnable.class);
for (Runnable runnable : runnableLoader) {
runnable.run();
}
}
}
Abbildung 47) Beispiel: Java Service, Executor lädt alle Services die Runnable
implementieren und führt ihre run-Methode aus
HelloService.java:
package at.jku.ssw.psw2.javaservice.provider;
public class HelloService implements Runnable {
@Override
public void run() {
System.out.println("Hello!");
}
}
META-INF/services/java.lang.Runnable:
Abbildung 48) Beispiel Java Service: HelloService implementiert das Interface Runnable
at.jku.ssw.psw2.javaservice.provider.HelloService
und ist im Verzeichnis META-INF/services/ registriert
Damit der Executor das Hello-Service findet muss das Hello-Service in eine JarDatei verpackt werden und die Jar-Datei in den Klassenpfad von Executor
eingefügt werden. Jar-Dateien können mit dem Kommandozeilenwerkzeug jar
erstellt werden. In unserem Beispiel mit dem Befehl: "jar cf HelloService.jar –
C bin/ ." wenn wir davon ausgehen, dass die Klassen und das Verzeichnis METAINF in bin liegen.
Der Executor kann mit dem Befehl:
"java –cp .;HelloService.jar at.jku.ssw.psw2.javaservice.Executor" ausgeführt werden.
Markus Loeberbauer 2010, 2011
116
Praktikum aus Softwareentwicklung 2
Java Service
Dabei gehen wir davon aus, dass der Klassenpfad von Executor das lokale
Verzeichnis ist und die Datei HelloService.jar ebenfalls im lokalen Verzeichnis liegt.
Liegen die Klassen oder die Jar-Datei woanders, dann muss der erste Teil (".")
bzw. der zweite Teil ("HelloService.jar") im Klassen-Pfad ("-cp") angepasst werden.
Java Services in Entwicklungsumgebungen erstellen
Entwickeln wir diese Anwendung in einer Entwicklungsumgebung wie zB Eclipse,
dann haben wir zwei unabhängige Projekte. Eines für den Executor und eines für
das HelloService. Das Verzeichnis META-INF geben wir in das src-Verzeichnis des
HelloService-Projekts. Eclipse kopiert es in das bin-Verzeichnis wenn es das
Projekt übersetzt, damit kann das Service gefunden werden. Führen wir das
Executor-Projekt aus, dann kann es das HelloService aber nicht finden. Erst
müssen wir in der Run Configuration für das Executor-Projekt das HelloServiceProjekt in den Classpath eintragen. D.h. wir stellen eine Laufzeitabhängigkeit
zwischen den Projekten her.
Andere Entwicklungsumgebungen haben ähnliche Einstellungsmöglichkeiten um
den Laufzeit-Classpath zu setzen. In NetBeans beispielsweise kann man den
Laufzeit-Classpath in den Properties eines Projekts in der Kategorie Libraries im
Karteireiter Run einstellen. In IntelliJ IDEA zum Beispiel kann man in der Project
Structure in den Modul-Einstellungen die Abhängigkeiten unter Dependencies
einstellen. Die Einstellung Scope bestimmt ob es sich um eine Übersetzungszeitoder Laufzeit-Abhänigkeit handelt.
Markus Loeberbauer 2010, 2011
117
Praktikum aus Softwareentwicklung 2
Sicherheit
Sicherheit
Das Thema Sicherheit war vom Start an Bestandteil von Java. Wobei Sicherheit in
Java bedeutet, dass der Code keinen Schaden anrichten darf. Auf Sprachebene
werden dazu Bereichsprüfungen bei Arrays durchgeführt, auf Zeigerarithmetik
verzichtet und Typ-Casts nur erlaubt wenn das gecastete Objekt den Typ des
Casts erfüllt. Auf Ebene der Klassenbibliothek werden Zugriffe auf Ressourcen wie
Dateien und das Netz über einen Sicherheitsmanager geschützt. Den
Sicherheitsmanager kann man konfigurieren, wobei man Code Rechte zuteilt, das
kann abhängig zB vom Speicherort oder der Signatur geschehen.
Das Sicherheitsmanagement verteilt sich auf: die Virtuell Maschine (Arrayzugriffe,
Typ-Casts, Bytecode-Prüfung), den Klassenlader (lädt die Klassen),
Sicherheitsmanager (prüft ob ein Zugriff erlaubt ist), Verschlüsselung
(Codesignierung) und der Java Authentication and Authorization Service
(Autorisierung von Benutzern).
Die Konfiguration des Sicherheitsmanagers (java.lang.SecurityManager) erfolgt
über Policy-Dateien. Java liest standardmäßig zwei Policy-Dateien aus, eine im
Verzeichnis der Java-Installation "jre/lib/security/java.policy" und eine im
Basisverzeichnis des Benutzers ".java.policy". Zusätzlich kann man über das
System-Property java.security.policy eine Policy-Datei angeben. Das funktioniert
als Kommandozeilenparameter (zB: java.-Djava.security.policy=MyPolicy.policy
MyApp) oder im Programm (zB: System.setProperty("java.security.policy",
"MyPolicy.policy");). Damit die Policy-Datei ausgewertet wird muss ein
Sicherheitsmanager aktiv sein. Ein Sicherheitsmanager kann durch Angabe des
System-Properties java.security.manager beim Programmstart installiert werden;
oder progammatisch über System.setSecurityManager(new SecurityManager());
gesetzt werden. Policy-Dateien und SecurityManager können aus
Markus Loeberbauer 2010, 2011
118
Praktikum aus Softwareentwicklung 2
Sicherheit
Sicherheitsgründen nur gesetzt werden, wenn bisher kein SecurityManager
installiert ist oder die aktive Policy das erlaubt (policy.allowSystemProperty=true).
Policy-Dateien
Die Policies werden in Text-Dateien abgelegt. Der Inhalt besteht aus grantEinträgen, pro Eintrag kann der betroffene Code-Quelle und beliebig viele
Permissions angegeben werden:
grant Codesource
{
Permission_1;
Permission_2;
}
Die Codesource besteht aus einer URL der Code-Basis und vertrauenswürdigen
Zertifikaten:
grant
codebase codebase-URL
certificate-name
{
...
Eine Permission besteht aus einer Permission-Klasse, einem Zielwert und einer
Aktion:
{
permission className target action, ...;
...
}
Beispiel einer Policy-Datei:
grant codeBase "file:${java.home}/lib/ext/*" {
permission java.security.AllPermission;
Markus Loeberbauer 2010, 2011
119
Praktikum aus Softwareentwicklung 2
Sicherheit
};
grant {
permission java.lang.RuntimePermission "stopThread";
permission java.net.SocketPermission "localhost:1024-", "listen";
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
};
grant {
permission javax.crypto.CryptoPermission "DES", 64;
permission javax.crypto.CryptoPermission "DESede", *;
};
grant codeBase "http://www.ssw.uni-linz.ac.at/classes/" {
permission java.net.SocketPermission "*:1024-65535", "connect";
permission java.io.FilePermission "${user.home}${/}-",
"read,write,execute“;
};
Regeln und Meta-Zeichen in Policy-Dateien
•
Die Code-Basis ist eine URL, daher muss immer "/" und niemals "\"
verwendet werden, zB: "file:/C:/bla/"
•
Mit ${…} kann auf System-Properties zugegriffen werden. Als Kürzel für
${file.separator} ist ${/} definiert.
•
Pfad-Endungen haben folgende Bedeutung:
•
"/": alle class-Dateien in der angegeben URL
•
"/*": alle class- und jar-Dateien in der angegebenen URL
•
"/-": alle class- und jar-Dateien in der angegebenen URL und
Unterverzeichnissen
Permissions
Damit Code sicherheitskritische Methoden aufrufen darf muss er die
entsprechende Permission haben; oder eine Permission, die die gewünschte
Permission impliziert. Beispiel: Will ein Programm die Datei c:\autoexec.bat lesen,
dann muss es die Permission java.io.FilePermission (oder mehr zB
java.security.AllPermission) mit dem Target "file:/C:${/}autoexec.bat" (oder mehr zB:
Markus Loeberbauer 2010, 2011
120
Praktikum aus Softwareentwicklung 2
Sicherheit
"file:${/}*" oder "<<ALL_FILES>>") und der Action "read" (oder mehr zB:
"read,write") haben.
Die Permissions aus den Policy-Dateien werden zu Laufzeit auf Java-Objekte
abgebildet. Alle Permissions erben von java.security.Permission, eine beispielhafte
Auswahl von Permissions ist in Abbildung 49 gegeben.
Permission
java.io.FilePermission
java.net.SocketPermission
Target
Action
Dateipfad
read, write, execute,
<<ALL FILES>>
delete
Host:Portrange
accept, connect, listen,
resolve
java.util.PropertyPermission
Name des
read, write
Systemproperties
java.lang.RuntimePermission
createClassLoader
setSecurityManager
exitVM
stopThread
…
java.net.NetPermission
setDefaultAuthenticator
setCookieHandler
setResponseCache
…
java.awt.AWTPermission
accessClipboard
watchMousePointer
readDisplayPixels
…
Markus Loeberbauer 2010, 2011
121
Praktikum aus Softwareentwicklung 2
java.security.SecurityPermission
Sicherheit
getPolicy
setPolicy
…
java.security.AllPermission
Abbildung 49) Beispielhafte Aufzählung von Security Permissions
Signieren von Jar-Dateien
Damit man Code in Jar-Dateien, unabhängig von ihrem Speicherort vertrauen
kann ist es möglich Jar-Dateien zu signieren. Signierte Dateien haben die Vorteile
vor nachträglichen Veränderungen geschützt zu sein und an ihren Urheber
zuordenbar zu sein. Damit kann man zB firmeninternen Jar-Dateien alle Rechte
geben und Code von außen einschränken.
Mit dem Kommandozeilenwerkzeug jarsigner kann man Jar-Dateien signieren, zB:
jarsinger demo.jar MyKeyStore dabei ist demo.jar die zu signierende Jar-Datei und
MyKeyStore der zu verwendende Keystore.
Bevor man mit jarsigner arbeiten kann muss man einen Schlüssel im Keystore
haben. Mit dem Kommandozeilenwerkzeug keytool kann man einen Schlüssel
erstellen, zB: keytool -genkey.
Ablauf eine Berechtigungsprüfung
Wird eine Sicherheitskritische Methode (zB System.exit) aufgerufen, dann fragt
diese den SecurityManager ob das erlaubt ist. Der SecurityManager untersucht
den Methoden-Stack (Aktivierungssätze) und prüft ob jede Methode in der
Aufrufkette zumindest eine Permission hat die diese Methode impliziert. Eine
Methode hat die Permissions ihrer Klasse, einer Klasse sind Permissions über eine
ProtectionDomain zugeordent. Eine ProtectionDomain speichert die Herkunft
(URL), die Zertifikate und eine Sammlung von Permissions (PermissionCollection).
Bespiel: System.exit
Markus Loeberbauer 2010, 2011
122
Praktikum aus Softwareentwicklung 2
Sicherheit
Die Methode System.exit ruft Runtime.exit auf:
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
Runtime.exit prüft ob ein Sicherheitsmanager installiert ist, wenn ja prüft sie mit
checkExit ob exit erlaubt ist.
public void exit(int status) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
}
Shutdown.exit(status);
}
SecurityManager.checkExit erzeugt ein Objekt der Klasse RuntimePermission und
prüft diese Permission über SecurityManager.checkPermission.
public void checkExit(int status) {
checkPermission(new RuntimePermission("exitVM."+status));
}
SecurityManager.checkPermission läuft über den Stack (stack walk) und prüft ob
jede Methode am Stack zumindest eine Permission hat die exit impliziert.
Markus Loeberbauer 2010, 2011
123
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
Java Native Interface (JNI)
Über das Java Native Interface können Java-Programme Funktionen aus nativen
Bibliotheken aufrufen. Das ist beispielsweise notwendig wenn man: plattformabhängige Bibliotheken (zB GUI) an Java anbinden will, mit Legacy-Systemen
kommunizieren muss oder wenn man langlaufende, zeitkritische Teile von
Algorithmen in maschinennahen Sprachen wie Assembler programmieren muss.
Details über das JNI kann man nachlesen unter:
http://download.oracle.com/javase/6/docs/technotes/guides/jni/
Beispiel
Als Beispiel verwenden wir einen Rechner für die Grundrechenarten, Abbildung 50
zeigt das C++-Interface, Abbildung 52 die Implementierung. Der Rechner zählt
die Anzahl der ausgeführten Operationen im Feld calcCount mit.
class Calculator {
private:
long calcCount;
void incCalcCount();
public:
long add(long x, long y);
long sub(long x, long y);
long mul(long x, long y);
long div(long x, long y);
long getCalculationCount();
};
Abbildung 50) Beispiel: C++-Interface eines Rechners mit den Grundrechenarten.
#include "Calculator.hpp"
long
long
long
long
long
void
Calculator::add(long x, long y) { incCalcCount(); return
Calculator::sub(long x, long y) { incCalcCount(); return
Calculator::mul(long x, long y) { incCalcCount(); return
Calculator::div(long x, long y) { incCalcCount(); return
Calculator::getCalculationCount() { return calcCount; }
Calculator::incCalcCount() { calcCount++; }
x
x
x
x
+
*
/
y;
y;
y;
y;
}
}
}
}
Abbildung 52) Beispiel: C++-Implementierung eines Rechners mit den Grundrechenarten.
Damit wir die C++-Klasse nutzen können müssen einen Java-Proxy erstellen.
Dieser Proxy muss die gewünschten Methoden als native Methoden deklarieren.
Abbildung 53 zeigt die Java Klasse at.jku.ssw.psw2.jni.calculator.Calculator. In der
Markus Loeberbauer 2010, 2011
124
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
Java-Klasse Calculator habe ich die Methoden gleich benannt wie in der C++Klasse, damit wir uns leicht zurechtfinden. Wir könnten die Methoden aber
beliebig benennen, weil wir auf der nativen Seite ohnehin eine C-Zwischenschicht
implementieren müssen. Neben den Methoden für den Rechner enthält die Klasse
einen Konstruktor der die native Methode initNative aufruft, einen Finalizer der
die native Methode destroyNative aufruft und das Feld nativeObject. Diese Dinge
brauchen wir weil wir ein zustrandsbehaftetes C++-Objekt an Java binden und
nicht nur zustandslose C-Bibliotheksfunktionen aufrufen wollen. Die Methode
initNative schreibt den Zeiger auf das C++-Objekt in das Feld nativeObject damit
die nativen Methoden des Rechners darauf zugreifen können. Würden wir das
nicht haben, dann würde die C-Zwischenschicht immer ein neues C++-Objekt
anlegen müssen und wir würden den Zustand verlieren, d.h., die Zählfunktion
würde nicht mehr funktionieren. Die Methode destroyNative gibt das C++-Objekt
wieder frei. Unserer Java-Klasse implementiert zusätzlich noch das Interface
Cloasable, damit das native Objekt explizit freigegeben werden kann und man
nicht auf den Finalizer warten muss. Bei unserem kleinen C++-Objekt wäre das
nicht nötig, aber bei größeren Objekten, oder bei Objekten die knappe
Betriebssystemressourcen verwenden ist das sinnvoll.
Markus Loeberbauer 2010, 2011
125
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
package at.jku.ssw.psw2.jni.calculator;
import java.io.Closeable;
import java.io.IOException;
public class Calculator implements Closeable {
static {
System.loadLibrary("CalculatorAdapter");
}
private long nativeObject;
public Calculator() { initNative(); }
private native void initNative();
protected void finalize() throws Throwable { close(); };
@Override
public void close() throws IOException {
if (nativeObject != 0) {
destroyNative();
nativeObject = 0;
}
}
private native void destroyNative();
public
public
public
public
public
native
native
native
native
native
int
int
int
int
int
add(int x, int y);
sub(int x, int y);
mul(int x, int y);
div(int x, int y);
getCalculationCount();
}
Abbildung 53) Beispiel: Java-Implementierung der Anbindung an den C++-Rechner
Die C-Zwischenschicht muss Funktionen für jede Methode der Java-Klasse
implementieren. Die Schnittstelle für diese Zwischenschicht muss man mit javah
erstellen. In unserem Beispiel also mit dem Kommandozeilenaufruf
javah at.jku.ssw.psw2.jni.calculator.Calculator. Abbildung 54 zeigt die generierte CSchnittstelle. Wir sehen, Java nutzt eigene Datentypen (zB jobject, jint), um die
Kompatibilität zwischen Java und C zu gewährleisten. Diese Datentypen sind in
der JNI-Spezifikation in Kapitel 3 beschrieben (siehe
http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html).
Damit man diese Datentypen benötigte Hilfsfunktionen nutzen kann ist jni.h
inkludiert.
Markus Loeberbauer 2010, 2011
126
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class at_jku_ssw_psw2_jni_calculator_Calculator */
#ifndef _Included_at_jku_ssw_psw2_jni_calculator_Calculator
#define _Included_at_jku_ssw_psw2_jni_calculator_Calculator
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_initNative
(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_destroyNative
(JNIEnv *, jobject);
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_add
(JNIEnv *, jobject, jint, jint);
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_sub
(JNIEnv *, jobject, jint, jint);
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_mul
(JNIEnv *, jobject, jint, jint);
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_div
(JNIEnv *, jobject, jint, jint);
JNIEXPORT jint JNICALL
Java_at_jku_ssw_psw2_jni_calculator_Calculator_getCalculationCount
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Abbildung 54) Beispiel: C++-Interface eines Rechners mit den Grundrechenarten.
Die Imlementierung der C-Schicht ist in Abbildung 55 gegeben. Alle Funktionen
die in der generierten Schnittstelle sind werden hier auf die native
Implementierung des Rechners umgesetzt. In den Hilfsmethoden getCalcField und
setCalcField sehen wir ausserdem wie man von C aus auf Felder des Java-Objekts
zugreifen kann. Die dafür notwendigen Funktionen sind in der JNIEnv-Klasse,
siehe Kapitel 4 der JNI-Spezifikation http://download.oracle.com/javase/6/docs/
technotes/guides/jni/spec/functions.html. Dort ist zB auch beschriben wie man
von C aus Objekte auf der Java-Seite erstellen und Java-Methoden aufrufen kann.
Anmerkung: Aus Performanzgründen kann man das native Feld auf der
C-Seite in einen Cache (zB: in eine Abbildung von dem jobject auf das
Feld) geben, die reflektiven Zugriffe über JNIEnv sind zeitintensiv.
Markus Loeberbauer 2010, 2011
127
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
#include <jni.h>
#include "Calculator.hpp"
#include "at_jku_ssw_psw2_jni_calculator_Calculator.h"
jfieldID getCalcFieldId(JNIEnv *env, jobject obj) {
jclass calcClass = env->GetObjectClass(obj);
return env->GetFieldID(calcClass, "nativeObject", "J");
}
void setCalcField(JNIEnv *env, jobject obj, Calculator *calc) {
env->SetLongField(obj, getCalcFieldId(env, obj), (jlong) calc);
}
Calculator* getCalcField(JNIEnv *env, jobject obj) {
return (Calculator*) env->GetLongField(obj, getCalcFieldId(env, obj));
}
JNIEXPORT void JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_initNative
(JNIEnv *env, jobject obj) {
setCalcField(env, obj, new Calculator());
}
JNIEXPORT void JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_destroyNative
(JNIEnv *, jobject) {
Calculator *calc = getCalcField(env, obj);
delete calc;
}
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_add
(JNIEnv *env, jobject obj, jint x, jint y) {
Calculator *calc = getCalcField(env, obj);
return calc->add(x, y);
}
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_sub
(JNIEnv *env, jobject obj, jint x, jint y) {
Calculator *calc = getCalcField(env, obj);
return calc->sub(x, y);
}
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_mul
(JNIEnv *env, jobject obj, jint x, jint y) {
Calculator *calc = getCalcField(env, obj);
return calc->mul(x, y);
}
JNIEXPORT jint JNICALL Java_at_jku_ssw_psw2_jni_calculator_Calculator_div
(JNIEnv *env, jobject obj, jint x, jint y) {
Calculator *calc = getCalcField(env, obj);
return calc->div(x, y);
}
JNIEXPORT jint JNICALL
Java_at_jku_ssw_psw2_jni_calculator_Calculator_getCalculationCount
(JNIEnv *env, jobject obj) {
Calculator *calc = getCalcField(env, obj);
return calc->getCalculationCount();
}
Abbildung 55) Beispiel: C++-Implementierung der Anbindung and den C++- Rechners.
Markus Loeberbauer 2010, 2011
128
Praktikum aus Softwareentwicklung 2
Java Native Interface (JNI)
Damit unser Programm funktioniert müssen wir die C-Bibliotheken noch
kompilieren. Achtung, damit Java die Bibliotheken findet müssen die
Namenskonventionen der Ziel-Plattform eingehalten werden, zB muss in Windows
eine Bibliothek X den Namen X.dll haben in unserem Fall also
CalculatorAdapter.dll, unter Mac Os X muss man den Prefix lib vor den
Bibliotheksnamen stellen und die Endung dylib oder jnilib verwenden in unserem
fall also libCalculatorAdapter.dylib. Unter anderen Unixen muss man häufig den
Prefix lib und die Endung so verwenden in unserem Fall also
libCalculatorAdapter.so.
Die kompilieren Bibliotheken müssen in den Bibliothekspfad eingefügt werden,
damit sie Java finden kann. In Windows müssen sie dafür im PATH liegen.
Plattformübergreifend kann man den Ordner mit den Bibliotheken als
Kommandozeilenparameter der Java-Runtime -Djava.library.path übergeben. In
einem Eclipse-Projekt kann man das in der Run-Configuration machen, zB als
-Djava.library.path=${project_loc}/NativeCalculator
Damit das Betribssystem abhängige Bibliotheken (in uneserem Fall Calculator)
findet müssen diese im Bibliothekspfad des Betribssystems liegen. Der Parameter
java.library.path nutzt dafür nichts. Für unser Testprogramm ist es am einfachsten
wenn wir die Bibliotheken in das Ausführungsverzeichnis geben, da sowohl
Windows als auch Mac Os X dort nach Bibliotheken suchen. In Eclipse kann man
das Ausführungsverzeichnis in der Run Configuration im Karteireiter Arguments im
Bereich Working Directory einstellen.
Markus Loeberbauer 2010, 2011
129
Praktikum aus Softwareentwicklung 2
Eclipse-Tastenkürzel
Eclipse-Tastenkürzel
1. Ctrl+1: Kontextabhängige Vorschläge, zB: Code-Erzeugung und Korrekturen
2. Ctrl+Space: Code-Vervollständigung
3. Alt+Shift+M: Extrahieren einer Methode
4. Alt+Curser-Up, Alt+Cursor-Down: Verschiebt die aktuelle Zeile nach oben
bzw. unten. Funktioniert auch mit markierten Bereichen.
5. Alt+Shift+L: Erstellt eine lokale Variable aus einem markierten Teilausdruck.
6. Ctrl+7: Aus-/Ein-Kommentieren einer Zeile oder eines markierten Bereichs.
7. Ctrl-Shift-F: Code formatieren.
8. Ctrl-Shift-O: Fehlende Import-Statements einfügen, überflüssige entfernen.
9. Ctrl-Shift-R: Umbenennen eines Elements, zB: Variable, Klasse oder
Methode.
10. F3 oder Ctrl-Click: zur Deklaration des Elements gehen
11. F4: Vererbungshierarchie einer Klasse anzeigen
12. Ctrl-Shift-G: Verwendungen einer Klasse, Methode, etc. suchen
13. Ctrl-G: Implementierungen einer Klasse oder Methode suchen.
Markus Loeberbauer 2010, 2011
130
Herunterladen