131
Praktikum aus Softwareentwicklung 2 Version: 2011-09-15_11:53 Markus Loeberbauer 2010, 2011

Praktikum aus Softwareentwicklung 2 - System Softwaressw.jku.at/Teaching/Lectures/PSW2/2012/PSW2-Skriptum.pdf · // Manual (Un)Boxing ... man eine GUI mit AWT oder Swing erstellen

Embed Size (px)

Citation preview

Praktikum aus Softwareentwicklung 2

Version: 2011-09-15_11:53

Markus Loeberbauer

2010, 2011

Praktikum aus Softwareentwicklung 2 Inhalt

Markus Loeberbauer 2010, 2011 1

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

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 2

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 for-

Schleife 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.

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 3

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.

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

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 4

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).

// 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

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 5

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.

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

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

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 6

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.

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

Praktikum aus Softwareentwicklung 2 Java

Markus Loeberbauer 2010, 2011 7

// 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

Praktikum aus Softwareentwicklung 2 Subversion

Markus Loeberbauer 2010, 2011 8

Subversion

Zur Übungsabgabe benutzen wir die Versionsverwaltung Subversion. Die

Zugangsdaten haben Sie auf Ihre Kusss-Email-Adresse bekommen. Ihr Subversion-

Repository 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.

Praktikum aus Softwareentwicklung 2 Subversion

Markus Loeberbauer 2010, 2011 9

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-/Merge-

Support. Eignet sich gut für die Verteilte Softwareentwicklung, wie zB bei Open-

Source-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,

mv

Verschieben oder umbenennen von Dateien und Verzeichnissen, zB:

svn mv Main.java Test.java

checkout,

co

Auschecken einer Arbeitskopie, zB: svn co svn://server.tld/<repoPfad>

oder svn co svn://server.tld/<repoPfad> <lokalerPfad>

commit, Einchecken der lokalen Änderungen in das Repository, zB: svn ci

Praktikum aus Softwareentwicklung 2 Subversion

Markus Loeberbauer 2010, 2011 10

ci

update,

up

Aktualisieren der Arbeitskopie, zB: svn up

status, st Anzeigen der Änderungen in der Arbeitskopie, zB: svn st

delete,

del, rm

Löschen einer Datei aus der Versionskontrolle, Gegenstück zu add, zB:

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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 11

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-

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 12

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 AWT-

Oberflächen eingebettet werden. Das soll aber nur bei vorhandenen AWT-

Anwendungen 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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 13

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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 14

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.

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 15

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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 16

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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 17

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. Listener-

Interfaces 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 Ereignis-

Objekte unveränderbar (immutable) sein. Ereignis-Objekte in Java erben von der

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 18

Klasse EventObject. Diese Klasse implementiert Serializable, daher müssen nicht-

serialisierbare 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.

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 19

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

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 20

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 Swing-

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 21

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, AWT-

Thread, 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 Netzwerk-

Kommunikation, 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 GUI-

Thread ist (boolean isEventDispatchThread()) und eine Aufgabe an den GUI-

Thread 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-08-

swingthreading.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.

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 22

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() {

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 23

// 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:

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 24

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;

Praktikum aus Softwareentwicklung 2 Graphische Oberflächen

Markus Loeberbauer 2010, 2011 25

}

@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);

}

}

}

Praktikum aus Softwareentwicklung 2 Dateien

Markus Loeberbauer 2010, 2011 26

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.

Praktikum aus Softwareentwicklung 2 Reflection

Markus Loeberbauer 2010, 2011 27

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")

Praktikum aus Softwareentwicklung 2 Reflection

Markus Loeberbauer 2010, 2011 28

Ü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 Handler-

Objekt (Interface InvokationHandler) weiter. Einsatzgebiete für dynamische Proxies

sind zB: Methoden über das Netzwerk aufrufen (Remote Method Invokation),

Testen und Loggen von Methodenaufrufen.

Praktikum aus Softwareentwicklung 2 Reflection

Markus Loeberbauer 2010, 2011 29

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

Praktikum aus Softwareentwicklung 2 Reflection

Markus Loeberbauer 2010, 2011 30

@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 {}

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 31

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 Thread-

Synchronisation genutzt werden.

java.lang. InterruptedException

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 32

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

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 33

objektorientierten Sinn falsch und aus diesem Grund in anderen Programmier-

sprachen, 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

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 34

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

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 35

synchronisiert werden soll. Synchronisiert man auf Methodenebene wird das this-

Objekt benutzt. Handelt es sich um eine statische Methode wird das Klassen-

Objekt 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

Object obj = new Object();

void foo() {

// uncritical stuff

synchronized(obj) {

// do critical stuff here

}

// uncritical stuff

}

Synchronisierte Methode

synchronized void bar() {

// do critical stuff here

}

// 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.

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 36

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:

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 37

// 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)

lauffähig (runnable)

neu

(new)

aktiv

(active)

bereit

(ready)

tot

(dead)

schlafend

(sleeping)

IO-blockiert

(IO-blocked)

gesperrt

(locked)

wartend

(waiting)

sleep()

aufw

achen

IO-O

pe

rtio

n

Ende IO

-Opera

tion

Obje

kts

perr

e (

synchro

niz

ed)

Aufh

eben O

bje

kts

perre

wait-

Anw

eis

ung

notify / n

otifyA

ll

sta

rt()

run te

rmin

iert

suspendiert

(suspended)

susp

end() resum

e()

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 38

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();

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 39

// 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

Praktikum aus Softwareentwicklung 2 Threads

Markus Loeberbauer 2010, 2011 40

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.

Praktikum aus Softwareentwicklung 2 Streams

Markus Loeberbauer 2010, 2011 41

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.

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).

Programm

Daten

quelle

Daten

senke

InputStream

Reader

OutputStrea

m Writer

Abbildung 7) Lesen und Schreiben von Daten über Datenströme

Praktikum aus Softwareentwicklung 2 Streams

Markus Loeberbauer 2010, 2011 42

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.

Praktikum aus Softwareentwicklung 2 Streams

Markus Loeberbauer 2010, 2011 43

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-Error-

Strom 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.

Praktikum aus Softwareentwicklung 2 Streams

Markus Loeberbauer 2010, 2011 44

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 Ein-

und Ausgabeströme mit zusätzlichen Funktionen zu versehen, zB Puffern,

Verschlüsseln oder Komprimieren. Abbildung 9 zeigt schematisch wie Filter-

Streams verwendet werden können.

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 */

...

}

}

}

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

Abbildung 8) Muster: Ressources und Exceptions

Praktikum aus Softwareentwicklung 2 Streams

Markus Loeberbauer 2010, 2011 45

Programm

Daten

quelle

Daten

senke

Filter

Input

Stream

Filter

Input

Stream

Input

Strea

Input

Strea

Filter

Input

Stream

Filter

Input

Stream

Abbildung 9) Schematische Darstellung von Filter-Streams

Praktikum aus Softwareentwicklung 2 Serialisierung

Markus Loeberbauer 2010, 2011 46

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.

Praktikum aus Softwareentwicklung 2 Serialisierung

Markus Loeberbauer 2010, 2011 47

Ü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.

Praktikum aus Softwareentwicklung 2 Netzwerkprogrammierung

Markus Loeberbauer 2010, 2011 48

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.

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 49

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

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 50

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 Datenbank-

Hersteller 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:

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 51

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:

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 52

Statement (Connection.createStatement): Absetzen von beliebigen SQL-

Befehlen, 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 SQL-

Befehlen 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 Datenbank-

Prozeduren. 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:

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 53

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.

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 54

int getRow(): Liefert die Nummer der aktuellen Zeile

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 55

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

double

BINARY, VARBINARY,

LONGVARBINARY

byte[]

DATE java.sql.Date

TIME java.sql.Time

TIMESTAMP 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.

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 56

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.

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 57

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.

Praktikum aus Softwareentwicklung 2 Datenbanken

Markus Loeberbauer 2010, 2011 58

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.

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 59

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 Server-

Seite 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

request

response

JVM 2JVM 1

request

response

server_stub server_skeleton

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

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 60

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.

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 61

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 class-

Dateien.

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 Remote-

Objekte, 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.

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 62

Ü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

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 63

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 System-

Property 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:

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 64

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 Policy-

Datei, 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.

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 65

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.

Praktikum aus Softwareentwicklung 2 Remoting

Markus Loeberbauer 2010, 2011 66

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);

}

}

}

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 67

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.

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.

Client

API

Implementations

Plugability-Layer

Abbildung 12) JAXP, Plugability-Layer

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 68

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>

</person> Abbildung 13) Beispiel: XML-Dokument Addressbook

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 69

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-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“.

<!ELEMENT addressbook ((person | company)*)>

<!ATTLIST addressbook owner IDREF #REQUIRED date CDATA

#IMPLIED>

<!ELEMENT person (firstname, lastname, email)>

<!ATTLIST person id ID #REQUIRED>

<!ELEMENT firstname (#PCDATA)>

<!ELEMENT lastname (#PCDATA)>

<!ELEMENT email (#PCDATA)>

<!ATTLIST email type (home | business) "business">

<!ELEMENT company (companyname, url)>

<!ATTLIST company id ID #REQUIRED>

<!ELEMENT companyname (#PCDATA)>

<!ELEMENT url (#PCDATA)>

Abbildung 14) Document Type Definition für Beispiel: Addressbook

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 70

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 XML-

Schema. 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 DOM-

Baum.

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 71

Lesen eines DOM-Baumes

Der DOM-Baum kann durch parsen aus einem XML-Dokument aufgebaut werden:

Document

DocumentType

Element: addressbook

Attr: owner = "p1"

Attr: date = 2005-03-12

Element: person

Attr: id = "p1"

Element: firstname

Text: Thomas

Element: lastname

Text: Kotzmann

<?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>

Abbildung 15) DOM-Baum für Beispiel: Addressbook

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 72

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 Text-

Knoten 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

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 73

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 SAX-

Parser 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) { …

}

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 74

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:

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 75

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 XML-

Dateien ü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:

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 76

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 XML-

Dokumenten ü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 Quell-

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 77

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);

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 78

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:

"*" Selektiert alle Knoten

"/" Wurzelknoten

"/addressbook/*" Selektiert alle Elemente unter dem

addressbook-Element

"/addressbook/person[1]" Liefert die ersten person-Elemente

von addressbook-Elementen

"/addressbook/*/firstname" Liefert alle firstname-Elemente

unter den addressbook-Elementen

<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

Praktikum aus Softwareentwicklung 2 XML

Markus Loeberbauer 2010, 2011 79

"/addressbook/person[@id="p1"]" Liefert den Personenknoten mit

id="p1"

"/*/email[@type="home"]" Liefert alle email-Knoten vom Typ

"home"

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 - - Einfach bis

Komplex

<?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>

</addressbook> <html>

<head>

<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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 80

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.

Servlets vs. Common Gateway Interface

Java Servlets sind vergleichbar mit dem Common Gateway Interface (CGI) und

proprietären Server-Erweiterungen, haben aber folgende Vorteile:

Abbildung 18) Grobe Architektur von Webanwendungen mit Servlets

Servlet 1 Servlet 2 Servlet 3

Web-Server & Servlet-

Engine

Client

Firefox, Opera,

Ressourcen

Dateien,

Datenbanken,

Reques

Respons

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 81

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!“.

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 82

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 Web-

Anwendungen, 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.

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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 83

Abbildung 21) Verzeichnisstruktur von Tomcat 6

<?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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 84

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 Servlet-

Engine 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.

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, URL-

Parameter 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.

Abbildung 22) Lebenszyklus eines Servlets

init(ServletConfig)

Variablen initialisieren, Ressourcen anfordern

service(HttpServletRequest, HttpServletResponse)

doGet(HttpServletRequest, HttpServletResponse)

doPost(HttpServletRequest, HttpServletResponse)

doPut(HttpServletRequest, HttpServletResponse)

...

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 85

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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 86

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-View-

<?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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 87

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/<JSP-

Name>_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

Praktikum aus Softwareentwicklung 2 Java Servlet

Markus Loeberbauer 2010, 2011 88

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

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 89

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.

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 90

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.

<%@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>

</html> Abbildung 27) Beispiel-JSP mit Scriptlet

Let's Count!

0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , ...

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 91

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.

<%@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";

%>

<%!

private String format(String message) {

return String.format(MESSAGE_FORMAT, new Date(),

message);

}

%>

</html>

Fri Jun 04 08:27:27 CEST 2010 : Hello World!

Abbildung 28) Beispiel-JSP mit Feld- und Methoden-Deklaration

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 92

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"/>

<%@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";

%>

<%!

private String format(String message) {

return String.format(MESSAGE_FORMAT, new Date(),

message);

}

%>

</html>

Abbildung 29) Beispiel-JSP mit Expressions

Fri Jun 04 08:42:31 CEST 2010 : Hello World!

0 1 2 3 4 5 6 7 8 9

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 93

<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

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 94

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 Property-

Zugriff 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.

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 95

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.

${ersterBezeichner.weitereBezeichne

pageScope

requestScope

sessionScope

applicationScope

param

paramValues

header

headerValues

pageScope

requestScope

sessionScope

applicationScope

Objekte werden

zuerst im Page-,

dann im Request-,

dann im Session-

und wenn sie dort

auch nicht gefunden

Implizite Objekte Schlüssel einer Map

oder

Property

Muss sich an die Java

Namenskonvention halten!

Abbildung 30) Aufbau eines EL-Ausdrucks

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 96

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 Web-

Anwendung 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

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 97

dem leeren String. Wichtig ist auch, dass folgende HTML-Sonderzeichen ersetzt

werden:

< gegen &lt;

> gegen &gt;

& gegen &amp;

' gegen &#039;

" gegen &#034;

<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>

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 98

<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.

<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.

<%@ 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>

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 99

<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>

<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>

<%@ 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>

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 100

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

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 101

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.

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 102

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"?>

Praktikum aus Softwareentwicklung 2 Dynamischer Kontext in JavaServer Pages

Markus Loeberbauer 2010, 2011 103

<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>

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 104

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 taglib-

Direktive 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 taglib-

Direktive 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 HTML-

Seiten-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:

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 105

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&#39;ll ever need!

</p>

<psw2tf:disclaimer>

Everything written on this

page may be exaggerated,

or just plain wrong.

</psw2tf:disclaimer>

<psw2tf:footer />

Abbildung 37) JSP Seite mit Custom Tags aus Tag Files

Hello PSW2

Students!

Welcome to the shiny world of JSP programming. In

this course you will learn everything you'll ever

need!

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 106

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.

<%@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>

<%@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).

Abbildung 38) Beispiel Tag mit Parameter (Seiten-Header)

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 107

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.

<%@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>

<h1>Active Students</h1>

<ol>

<psw2tag:StudentFilter activeOnly="true"

students="${students}"

var="student">

<li>${student.name}</li>

</psw2tag:StudentFilter>

</ol>

<h1>All Students</h1>

<ol>

<psw2tag:StudentFilter students="${students}">

<li>${var.name}</li>

</psw2tag:StudentFilter>

</ol>

</body>

</html>

Abbildung 41) Beispiel-JSP-Seite mit Simple Tags

Active

Students

1. Susi Brain 2. Max Power

All Students

1. Susi Brain

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 108

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.

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

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 109

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.

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 110

<?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

Praktikum aus Softwareentwicklung 2 JSP Custom Tags

Markus Loeberbauer 2010, 2011 111

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

Praktikum aus Softwareentwicklung 2 Applets

Markus Loeberbauer 2010, 2011 112

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 Applet-

Klasse 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

Praktikum aus Softwareentwicklung 2 Applets

Markus Loeberbauer 2010, 2011 113

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

Praktikum aus Softwareentwicklung 2 Applets

Markus Loeberbauer 2010, 2011 114

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

Praktikum aus Softwareentwicklung 2 Java Service

Markus Loeberbauer 2010, 2011 115

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 Jar-

Datei 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-in-

Mechanismus.

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.

Praktikum aus Softwareentwicklung 2 Java Service

Markus Loeberbauer 2010, 2011 116

Damit der Executor das Hello-Service findet muss das Hello-Service in eine Jar-

Datei 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 META-

INF in bin liegen.

Der Executor kann mit dem Befehl:

"java –cp .;HelloService.jar at.jku.ssw.psw2.javaservice.Executor" ausgeführt werden.

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:

at.jku.ssw.psw2.javaservice.provider.HelloService Abbildung 48) Beispiel Java Service: HelloService implementiert das Interface Runnable und ist im Verzeichnis META-INF/services/ registriert

Praktikum aus Softwareentwicklung 2 Java Service

Markus Loeberbauer 2010, 2011 117

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 HelloService-

Projekt 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 Übersetzungszeit-

oder Laufzeit-Abhänigkeit handelt.

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 118

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

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 119

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 grant-

Einträ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;

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 120

};

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:

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 121

"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 Target Action

java.io.FilePermission Dateipfad

<<ALL FILES>>

read, write, execute,

delete

java.net.SocketPermission Host:Portrange accept, connect, listen,

resolve

java.util.PropertyPermission Name des

Systemproperties

read, write

java.lang.RuntimePermission createClassLoader

setSecurityManager

exitVM

stopThread

java.net.NetPermission setDefaultAuthenticator

setCookieHandler

setResponseCache

java.awt.AWTPermission accessClipboard

watchMousePointer

readDisplayPixels

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 122

java.security.SecurityPermission 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

Praktikum aus Softwareentwicklung 2 Sicherheit

Markus Loeberbauer 2010, 2011 123

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.

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 124

Java Native Interface (JNI)

Über das Java Native Interface können Java-Programme Funktionen aus nativen

Bibliotheken aufrufen. Das ist beispielsweise notwendig wenn man: plattform-

abhä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.

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

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 Calculator::add(long x, long y) { incCalcCount(); return x + y; }

long Calculator::sub(long x, long y) { incCalcCount(); return x - y; }

long Calculator::mul(long x, long y) { incCalcCount(); return x * y; }

long Calculator::div(long x, long y) { incCalcCount(); return x / y; }

long Calculator::getCalculationCount() { return calcCount; }

void Calculator::incCalcCount() { calcCount++; }

Abbildung 52) Beispiel: C++-Implementierung eines Rechners mit den Grundrechenarten.

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 125

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.

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 126

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 C-

Schnittstelle. 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.

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 native int add(int x, int y);

public native int sub(int x, int y);

public native int mul(int x, int y);

public native int div(int x, int y);

public native int getCalculationCount();

}

Abbildung 53) Beispiel: Java-Implementierung der Anbindung an den C++-Rechner

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 127

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.

/* 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.

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 128

#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.

Praktikum aus Softwareentwicklung 2 Java Native Interface (JNI)

Markus Loeberbauer 2010, 2011 129

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.

Praktikum aus Softwareentwicklung 2 Eclipse-Tastenkürzel

Markus Loeberbauer 2010, 2011 130

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.