Java 7 - Mehr als eine Insel : Das Expertenbuch zu den Java SE Bibliotheken [1., neue Ausg ed.]
 3836215071, 9783836215077 [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Christian Ullenboom

Java 7 – Mehr als eine Insel Das Handbuch zu den Java SE-Bibliotheken

Liebe Leserin, lieber Leser, Sie halten die erste Auflage der Insel zu den Java SE-Bibliotheken in den Händen! Ja, es ist ein ganz neues Buch, das es hoffentlich so weit bringen wird wie unsere bewährte Insel zu den Java-Grundlagen, die Sie vielleicht schon kennen. Java wächst und gedeiht, und da wir nicht alles zu Java und seinen Bibliotheken in einem Buch zusammenfassen können, weil dies buchbinderische Grenzen sprengen würde, haben wir uns entschlossen, mit den Ausgaben zu Java 7 zwei umfangreiche und in sich vollständige Werke anzubieten: Ein Einsteiger- und Lehrwerk für Studenten und Auszubildende, die umfassendes Grundlagenwissen brauchen. Und eine Insel für den erfahrenen Java-Programmierer, der konkrete Fragen aus der Praxis hat und sich schnell und gründlich in verschiedene Spezialgebiete einarbeiten muss. Herausgekommen sind 2700 Seiten Java-Wissen, fundiert und reich an Details, passgenau auf ihre jeweiligen Bedürfnisse zugeschnitten. Ohne dass Sie Abstriche machen müssen! Die Inseln sind Kultbücher. Und diese Bücher wollen sorgfältig gepflegt und aktualisiert werden. Dass der Inhalt up-to-date ist, versteht sich von selbst. Die Präsentation, sprich: das Layout, ist es jetzt auch. Frischer, aufgeräumter und lesefreundlicher. Sie haben bestimmt ihre Freude daran. Wir hoffen, dass keine Java-Fragen offen bleiben! Sollten Sie dennoch kleine Fehler oder Ungenauigkeiten finden, so haben Sie jederzeit die Möglichkeit, sich an Christian Ullenboom ([email protected]) oder an mich zu wenden. Wir freuen uns über eine freundliche Rückmeldung!

Judith Stevens-Lemoine Lektorat Galileo Computing

[email protected] www.galileocomputing.de Galileo Press · Rheinwerkallee 4 · 53227 Bonn

Auf einen Blick 1

Neues in Java 7 ..................................................................................................

41

2

Threads und nebenläufige Programmierung ..............................................

65

3

Datenstrukturen und Algorithmen ...............................................................

175

4

Raum und Zeit ...................................................................................................

305

5

Dateien, Verzeichnisse und Dateizugriffe ...................................................

357

6

Datenströme ......................................................................................................

427

7

Die eXtensible Markup Language (XML) ......................................................

531

8

Dateiformate .....................................................................................................

617

9

Grafische Oberflächen mit Swing .................................................................

645

10

Grafikprogrammierung ...................................................................................

865

11

Netzwerkprogrammierung .............................................................................

955

12

Verteilte Programmierung mit RMI .............................................................. 1035

13

RESTful und SOAP Web-Services .................................................................... 1057

14

JavaServer Pages und Servlets ....................................................................... 1079

15

Applets ................................................................................................................ 1123

16

Datenbankmanagement mit JDBC ................................................................ 1139

17

Technologien für die Infrastruktur ................................................................ 1207

18

Reflection und Annotationen ......................................................................... 1227

19

Dynamische Übersetzung und Skriptsprachen .......................................... 1289

20

Logging und Monitoring .................................................................................. 1315

21

Java Native Interface (JNI) ............................................................................... 1337

22

Sicherheitskonzepte ......................................................................................... 1355

23

Dienstprogramme für die Java-Umgebung ................................................. 1381

Der Name Galileo Press geht auf den italienischen Mathematiker und Philosophen Galileo Galilei (1564– 1642) zurück. Er gilt als Gründungsfigur der neuzeitlichen Wissenschaft und wurde berühmt als Verfechter des modernen, heliozentrischen Weltbilds. Legendär ist sein Ausspruch Eppur si muove (Und sie bewegt sich doch). Das Emblem von Galileo Press ist der Jupiter, umkreist von den vier Galileischen Monden. Galilei entdeckte die nach ihm benannten Monde 1610. Lektorat Judith Stevens-Lemoine, Anne Scheibe Korrektorat Friederike Daenecke, Zülpich Cover Barbara Thoben, Köln Titelbild Karte: @Christian Nitz – fotolia.com, Palme: @pati – fotolia.com Typografie und Layout Vera Brauner Herstellung Norbert Englert Satz SatzPro, Krefeld Druck und Bindung Bercker Graphischer Betrieb, Kevelaer

Gerne stehen wir Ihnen mit Rat und Tat zur Seite: [email protected] bei Fragen und Anmerkungen zum Inhalt des Buches [email protected] für versandkostenfreie Bestellungen und Reklamationen [email protected] für Rezensions- und Schulungsexemplare

Dieses Buch wurde gesetzt aus der TheAntiqua (9,5/14 pt) in FrameMaker.

Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

ISBN 978-3-8362-1507-7 © Galileo Press, Bonn 2012 1. Auflage 2012

Das vorliegende Werk ist in all seinen Teilen urheberrechtlich geschützt. Alle Rechte vorbehalten, insbesondere das Recht der Übersetzung, des Vortrags, der Reproduktion, der Vervielfältigung auf fotomechanischem oder anderen Wegen und der Speicherung in elektronischen Medien. Ungeachtet der Sorgfalt, die auf die Erstellung von Text, Abbildungen und Programmen verwendet wurde, können weder Verlag noch Autor, Herausgeber oder Übersetzer für mögliche Fehler und deren Folgen eine juristische Verantwortung oder irgendeine Haftung übernehmen. Die in diesem Werk wiedergegebenen Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. können auch ohne besondere Kennzeichnung Marken sein und als solche den gesetzlichen Bestimmungen unterliegen.

Inhalt

Inhalt Vorwort ......................................................................................................................................................

35

1

Neues in Java 7

1.1

Sprachänderungen ........................................................................................................................

41

1.1.1

Zahlen im Binärsystem schreiben ..........................................................................

41

1.1.2

Unterstriche bei Ganzzahlen ....................................................................................

41

1.1.3

switch (String) ................................................................................................................

42

1.1.4

Zusammenfassen gleicher catch-Blöcke mit dem multi-catch ......................

43

1.1.5

Präzisiertes rethrows ..................................................................................................

47

1.1.6

Automatisches Ressourcen-Management (try mit Ressourcen) ...................

51

1.1.7

try mit Ressourcen .......................................................................................................

51

1.1.8

Ausnahmen vom close() bleiben bestehen ..........................................................

53

1.1.9

Die Schnittstelle AutoCloseable ...............................................................................

53

1.1.10

Mehrere Ressourcen nutzen .....................................................................................

55

1.1.11

Unterdrückte Ausnahmen * ......................................................................................

56

JVM-Änderungen ...........................................................................................................................

59

1.2.1

invokedynamic .............................................................................................................

60

Neue Pakete und Klassen ............................................................................................................

63

1.3.1

63

1.2 1.3

Weitere Änderungen ...................................................................................................

2

Threads und nebenläufige Programmierung

2.1

Threads erzeugen ..........................................................................................................................

2.2

65

2.1.1

Threads über die Schnittstelle Runnable implementieren .............................

65

2.1.2

Thread mit Runnable starten ...................................................................................

66

2.1.3

Die Klasse Thread erweitern .....................................................................................

68

Thread-Eigenschaften und -Zustände .....................................................................................

71

2.2.1

Der Name eines Threads ............................................................................................

71

2.2.2

Wer bin ich? ....................................................................................................................

72

2.2.3

Die Zustände eines Threads * ....................................................................................

72

5

Inhalt

2.3

2.4

2.5

6

2.2.4

Schläfer gesucht ...........................................................................................................

73

2.2.5

Mit yield() auf Rechenzeit verzichten ...................................................................

75

2.2.6

Der Thread als Dämon ................................................................................................

76

2.2.7

Das Ende eines Threads .............................................................................................

78

2.2.8

Einen Thread höflich mit Interrupt beenden .....................................................

79

2.2.9

UncaughtExceptionHandler für unbehandelte Ausnahmen ........................

81

2.2.10

Der stop() von außen und die Rettung mit ThreadDeath * .............................

82

2.2.11

Ein Rendezvous mit join() * ......................................................................................

84

2.2.12

Arbeit niederlegen und wieder aufnehmen * .....................................................

87

2.2.13

Priorität * ........................................................................................................................

87

Der Ausführer (Executor) kommt .............................................................................................

88

2.3.1

89

Die Schnittstelle Executor .........................................................................................

2.3.2

Die Thread-Pools ..........................................................................................................

91

2.3.3

Threads mit Rückgabe über Callable .....................................................................

92

2.3.4

Mehrere Callable abarbeiten ....................................................................................

96

2.3.5

ScheduledExecutorService für wiederholende Ausgaben und Zeitsteuerungen nutzen ............................................................................................

97

Synchronisation über kritische Abschnitte ...........................................................................

98

2.4.1

Gemeinsam genutzte Daten ....................................................................................

98

2.4.2

Probleme beim gemeinsamen Zugriff und kritische Abschnitte .................

98

2.4.3

Punkte parallel initialisieren ...................................................................................

100

2.4.4

i++ sieht atomar aus, ist es aber nicht * ................................................................

102

2.4.5

Kritische Abschnitte schützen .................................................................................

103

2.4.6

Kritische Abschnitte mit ReentrantLock schützen ...........................................

106

2.4.7

Synchronisieren mit synchronized .......................................................................

113

2.4.8

Synchronized-Methoden der Klasse StringBuffer * ..........................................

114

2.4.9

Mit synchronized synchronisierte Blöcke ...........................................................

115

2.4.10

Dann machen wir doch gleich alles synchronisiert! ........................................

117

2.4.11

Lock-Freigabe im Fall von Exceptions ...................................................................

118

2.4.12

Deadlocks .......................................................................................................................

119

2.4.13

Mit synchronized nachträglich synchronisieren * ............................................

121

2.4.14

Monitore sind reentrant – gut für die Geschwindigkeit * ...............................

122

2.4.15

Synchronisierte Methodenaufrufe zusammenfassen * ..................................

123

Synchronisation über Warten und Benachrichtigen ..........................................................

124

2.5.1

Die Schnittstelle Condition ......................................................................................

125

2.5.2

It’s Disco-Time * ...........................................................................................................

129

Inhalt

2.6

2.7

2.8

2.9

2.5.3

Warten mit wait() und Aufwecken mit notify() * ................................................

135

2.5.4

Falls der Lock fehlt: IllegalMonitorStateException * .........................................

136

Datensynchronisation durch besondere Concurrency-Klassen * .....................................

138

2.6.1

138

Semaphor ........................................................................................................................

2.6.2

Barrier und Austausch ................................................................................................

143

2.6.3

Stop and go mit Exchanger .......................................................................................

145

Atomare Operationen und frische Werte mit volatile * .....................................................

145

2.7.1

Der Modifizierer volatile bei Objekt-/Klassenvariablen ..................................

146

2.7.2

Das Paket java.util.concurrent.atomic ...................................................................

148

Teile und herrsche mit Fork und Join * ....................................................................................

149

2.8.1

Algorithmendesign per »teile und herrsche« .....................................................

149

2.8.2

Paralleles Lösen von D&C-Algorithmen ...............................................................

151

2.8.3

Fork und Join .................................................................................................................

152

Mit dem Thread verbundene Variablen * ...............................................................................

156

2.9.1

ThreadLocal ....................................................................................................................

156

2.9.2

InheritableThreadLocal ..............................................................................................

159

2.9.3

ThreadLocalRandom als Zufallszahlengenerator ...............................................

160

2.9.4

ThreadLocal bei der Performance-Optimierung ................................................

161

2.10 Threads in einer Thread-Gruppe * ............................................................................................

162

2.10.1

Aktive Threads in der Umgebung ...........................................................................

162

2.10.2

Etwas über die aktuelle Thread-Gruppe herausfinden .....................................

163

2.10.3

Threads in einer Thread-Gruppe anlegen .............................................................

166

2.10.4

Methoden von Thread und ThreadGroup im Vergleich ..................................

169

2.11 Zeitgesteuerte Abläufe ................................................................................................................

170

2.11.1

Die Typen Timer und TimerTask .............................................................................

171

2.11.2

Job-Scheduler Quartz ..................................................................................................

172

2.12 Einen Abbruch der virtuellen Maschine erkennen ...............................................................

173

2.13 Zum Weiterlesen ...........................................................................................................................

174

3

Datenstrukturen und Algorithmen

3.1

Datenstrukturen und die Collection-API ................................................................................

175

3.1.1

Designprinzip mit Schnittstellen, abstrakten und konkreten Klassen .......

176

3.1.2

Die Basis-Schnittstellen Collection und Map ......................................................

176

3.1.3

Die Utility-Klassen Collections und Arrays ..........................................................

177

7

Inhalt

3.2

3.3

3.4 3.5

3.6

8

3.1.4

Das erste Programm mit Container-Klassen ......................................................

177

3.1.5

Die Schnittstelle Collection und Kernkonzepte .................................................

178

3.1.6

Schnittstellen, die Collection erweitern, und Map ............................................

182

3.1.7

Konkrete Container-Klassen ....................................................................................

185

3.1.8

Generische Datentypen in der Collection-API ....................................................

187

3.1.9

Die Schnittstelle Iterable und das erweiterte for ...............................................

187

Listen ................................................................................................................................................

188

3.2.1

Erstes Listen-Beispiel ..................................................................................................

189

3.2.2

Auswahlkriterium ArrayList oder LinkedList .....................................................

190

3.2.3

Die Schnittstelle List ...................................................................................................

191

3.2.4

ArrayList .........................................................................................................................

197

3.2.5

LinkedList .......................................................................................................................

200

3.2.6

Der Feld-Adapter Arrays.asList() .............................................................................

201

3.2.7

ListIterator * ...................................................................................................................

202

3.2.8

toArray() von Collection verstehen – die Gefahr einer Falle erkennen ......

204

3.2.9

Primitive Elemente in Datenstrukturen verwalten ..........................................

207

Mengen (Sets) ................................................................................................................................

208

3.3.1

Ein erstes Mengen-Beispiel .......................................................................................

209

3.3.2

Methoden der Schnittstelle Set ...............................................................................

212

3.3.3

HashSet ...........................................................................................................................

213

3.3.4

TreeSet – die sortierte Menge ..................................................................................

214

3.3.5

Die Schnittstellen NavigableSet und SortedSet .................................................

216

3.3.6

LinkedHashSet ..............................................................................................................

218

Stack (Kellerspeicher, Stapel) ....................................................................................................

218

3.4.1

Die Methoden von Stack ...........................................................................................

219

Queues (Schlangen) und Deques ..............................................................................................

220

3.5.1

Queue-Klassen ..............................................................................................................

221

3.5.2

Deque-Klassen ..............................................................................................................

222

3.5.3

Blockierende Queues und Prioritätswarteschlangen .......................................

223

3.5.4

PriorityQueue ...............................................................................................................

223

Assoziative Speicher ....................................................................................................................

228

3.6.1

Die Klassen HashMap und TreeMap ......................................................................

228

3.6.2

Einfügen und Abfragen der Datenstruktur .........................................................

231

3.6.3

Über die Bedeutung von equals() und hashCode() ............................................

234

3.6.4

Eigene Objekte hashen ...............................................................................................

235

3.6.5

IdentityHashMap .........................................................................................................

237

3.6.6

Das Problem von veränderten Elementen ..........................................................

237

Inhalt

3.7

3.8

3.9

3.6.7

Aufzählungen und Ansichten des Assoziativspeichers ...................................

237

3.6.8

Die Arbeitsweise einer Hash-Tabelle * ...................................................................

241

Die Properties-Klasse ...................................................................................................................

243

3.7.1

Properties setzen und lesen ......................................................................................

243

3.7.2

Properties verketten ....................................................................................................

244

3.7.3

Hierarchische Eigenschaften ....................................................................................

245

3.7.4

Eigenschaften auf der Konsole ausgeben * ..........................................................

245

3.7.5

Properties laden und speichern ...............................................................................

246

3.7.6

Klassenbeziehungen: Properties und Hashtable * .............................................

248

Mit einem Iterator durch die Daten wandern .......................................................................

249

3.8.1

Die Schnittstelle Iterator ............................................................................................

249

3.8.2

Der Iterator kann (eventuell auch) löschen .........................................................

251

3.8.3

Einen Zufallszahleniterator schreiben ..................................................................

252

3.8.4

Iteratoren von Sammlungen, das erweiterte for und Iterable .......................

254

3.8.5

Fail-Fast-Iterator und die ConcurrentModificationException .......................

258

3.8.6

Die Schnittstelle Enumerator * ................................................................................

260

Algorithmen in Collections .........................................................................................................

261

3.9.1

263

Die Bedeutung von Ordnung mit Comparator und Comparable .................

3.9.2

Sortieren ..........................................................................................................................

264

3.9.3

Den größten und kleinsten Wert einer Collection finden ...............................

267

3.9.4

Nicht-änderbare Datenstrukturen, immutable oder nur-lesen? ...................

270

3.9.5

Null Object Pattern und leere Sammlungen/Iteratoren zurückgeben ........

274

3.9.6

Mit der Halbierungssuche nach Elementen fahnden .......................................

278

3.9.7

Ersetzen, Kopieren, Füllen, Umdrehen, Rotieren * ............................................

279

3.9.8

Listen durchwürfeln * .................................................................................................

281

3.9.9

Häufigkeit eines Elements * ......................................................................................

282

3.9.10

Singletons * .....................................................................................................................

282

3.9.11

nCopies() * .......................................................................................................................

283

3.10 Spezielle thread-sichere Datenstrukturen ............................................................................. 3.10.1

Wait-free-Algorithmen ...............................................................................................

3.10.2

Nebenläufiger Assoziativspeicher und die Schnittstelle

284 284

ConcurrentMap .............................................................................................................

284

3.10.3

ConcurrentLinkedQueue ...........................................................................................

285

3.10.4

CopyOnWriteArrayList und CopyOnWriteArraySet ..........................................

285

3.10.5

Wrapper zur Synchronisation ..................................................................................

286

3.10.6

Blockierende Warteschlangen ..................................................................................

287

9

Inhalt

3.10.7

ArrayBlockingQueue und LinkedBlockingQueue .............................................

287

3.10.8

PriorityBlockingQueue ..............................................................................................

290

3.11 Google Guava (aka Google Collections Library) ..................................................................

294

3.11.1

Beispiel Multi-Set und Multi-Map ..........................................................................

294

3.11.2

Datenstrukturen aus Guava .....................................................................................

295

3.11.3

Utility-Klassen von Guava ........................................................................................

297

3.11.4

Prädikate .........................................................................................................................

298

3.11.5

Transformationen .......................................................................................................

299

3.12 Die Klasse BitSet für Bitmengen * ............................................................................................

299

3.12.1

Ein BitSet anlegen, füllen und erfragen ................................................................

300

3.12.2

Mengenorientierte Operationen ............................................................................

301

3.12.3

Methodenübersicht ....................................................................................................

302

3.12.4

Primzahlen in einem BitSet verwalten .................................................................

304

3.13 Zum Weiterlesen ..........................................................................................................................

304

4

Raum und Zeit

4.1

Weltzeit * ........................................................................................................................................

305

4.2

Wichtige Datum-Klassen im Überblick ..................................................................................

306

4.3 4.4

4.5

4.6

10

4.2.1

Der 1.1.1970 ...................................................................................................................

307

4.2.2

System.currentTimeMillis() .....................................................................................

307

4.2.3

Einfache Zeitumrechnungen durch TimeUnit ...................................................

307

Sprachen der Länder ....................................................................................................................

308

4.3.1

Sprachen und Regionen über Locale-Objekte .....................................................

309

Internationalisierung und Lokalisierung ...............................................................................

312

4.4.1

ResourceBundle-Objekte und Ressource-Dateien .............................................

313

4.4.2

Ressource-Dateien zur Lokalisierung ....................................................................

314

4.4.3

Die Klasse ResourceBundle ......................................................................................

314

4.4.4

Ladestrategie für ResourceBundle-Objekte .........................................................

316

Die Klasse Date ..............................................................................................................................

317

4.5.1

Objekte erzeugen und Methoden nutzen ............................................................

317

4.5.2

Date-Objekte sind nicht immutable ......................................................................

319

Calendar und GregorianCalendar ............................................................................................

320

4.6.1

Die abstrakte Klasse Calendar ..................................................................................

320

4.6.2

Der gregorianische Kalender ...................................................................................

322

Inhalt

4.7

4.8

4.9

4.6.3

Calendar nach Date und Millisekunden fragen ..................................................

325

4.6.4

Ostertage * ......................................................................................................................

326

4.6.5

Abfragen und Setzen von Datumselementen über Feldbezeichner ............

328

4.6.6

Wie viele Tage hat der Monat, oder wie viele Monate hat ein Jahr? * ..........

333

4.6.7

Wann beginnt die Woche und wann die erste Woche im Jahr? * ..................

335

Zeitzonen in Java * ........................................................................................................................

336

4.7.1

Zeitzonen durch die Klasse TimeZone repräsentieren .....................................

337

4.7.2

SimpleTimeZone ..........................................................................................................

337

4.7.3

Methoden von TimeZone ..........................................................................................

339

Zeitdauern und der XML-Datentyp Duration * .....................................................................

340

4.8.1

DatatypeFactory als Fabrik ........................................................................................

341

4.8.2

Die Duration-Klasse und ihre Methoden ..............................................................

342

Formatieren und Parsen von Datumsangaben .....................................................................

345

4.9.1

Ausgaben mit printf() ..................................................................................................

345

4.9.2

Mit DateFormat und SimpleDateFormat formatieren .....................................

346

4.9.3

Parsen von Datumswerten ........................................................................................

352

4.10 Zum Weiterlesen ...........................................................................................................................

355

5

Dateien, Verzeichnisse und Dateizugriffe

5.1

Datei und Verzeichnis ..................................................................................................................

357

5.1.1

Dateien und Verzeichnisse mit der Klasse File ...................................................

358

5.1.2

Verzeichnis oder Datei? Existiert es? .....................................................................

361

5.1.3

Verzeichnis- und Dateieigenschaften/-attribute ...............................................

362

5.1.4

Umbenennen und Verzeichnisse anlegen ...........................................................

365

5.1.5

Verzeichnisse auflisten und Dateien filtern ........................................................

365

5.1.6

Dateien berühren, neue Dateien anlegen, temporäre Dateien ......................

369

5.1.7

Dateien und Verzeichnisse löschen ........................................................................

370

5.1.8

Verzeichnisse nach Dateien iterativ durchsuchen * ..........................................

372

5.1.9

Wurzelverzeichnis, Laufwerksnamen, Plattenspeicher * ................................

373

5.1.10

URL-, URI- und Path-Objekte aus einem File-Objekt ableiten * ......................

376

5.1.11

Mit Locking Dateien sperren * ..................................................................................

376

5.1.12

Sicherheitsprüfung * ...................................................................................................

377

5.1.13

Zugriff auf SMB-Server mit jCIFS * ..........................................................................

378

11

Inhalt

5.2

5.3

5.4

Dateien mit wahlfreiem Zugriff ...............................................................................................

378

5.2.1

Ein RandomAccessFile zum Lesen und Schreiben öffnen ..............................

379

5.2.2

Aus dem RandomAccessFile lesen .........................................................................

380

5.2.3

Schreiben mit RandomAccessFile ..........................................................................

383

5.2.4

Die Länge des RandomAccessFile ...........................................................................

384

5.2.5

Hin und her in der Datei ............................................................................................

384

Dateisysteme unter NIO.2 ..........................................................................................................

385

5.3.1

FileSystem und Path ...................................................................................................

386

5.3.2

Die Utility-Klasse Files ...............................................................................................

392

5.3.3

Dateien kopieren und verschieben ........................................................................

395 397

5.3.4

Dateiattribute * .............................................................................................................

5.3.5

Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen ...................................................................................................................

406

5.3.6

MIME-Typen testen * ..................................................................................................

408

5.3.7

Verzeichnislistings (DirectoryStream) und Filter * ...........................................

410

5.3.8

Rekursive Abläufe des Verzeichnisbaums (FileVisitor) * ................................

412

5.3.9

Dateisysteme und Dateisystemattribute * ..........................................................

416

5.3.10

Verzeichnisse im Dateisystem überwachen * .....................................................

420

Wahlfreier Zugriff mit SeekableByteChannel und ByteBuffer * .....................................

422

5.4.1

SeekableByteChannel .................................................................................................

422

5.4.2

ByteBuffer ......................................................................................................................

423

5.4.3

Beispiel mit Path + SeekableByteChannel + ByteBuffer ..................................

423

5.4.4

FileChannel ....................................................................................................................

424

5.5

Zum Weiterlesen ..........................................................................................................................

426

6

Datenströme

6.1

Stream-Klassen und Reader/Writer am Beispiel von Dateien .........................................

427

6.1.1

Mit dem FileWriter Texte in Dateien schreiben .................................................

428

6.1.2

Zeichen mit der Klasse FileReader lesen ..............................................................

430

6.1.3

Kopieren mit FileOutputStream und FileInputStream ...................................

431

6.2

12

6.1.4

Das FileDescriptor-Objekt * ......................................................................................

435

6.1.5

Datenströme über Files mit NIO.2 beziehen .......................................................

436

Basisklassen für die Ein-/Ausgabe ...........................................................................................

438

6.2.1

Die abstrakten Basisklassen .....................................................................................

438

6.2.2

Übersicht über Ein-/Ausgabeklassen ....................................................................

439

Inhalt

6.3

6.4

6.5

6.6

6.7

6.2.3

Die abstrakte Basisklasse OutputStream ..............................................................

441

6.2.4

Die Schnittstellen Closeable, AutoCloseable und Flushable ...........................

443

6.2.5

Ein Datenschlucker * ...................................................................................................

444

6.2.6

Die abstrakte Basisklasse InputStream .................................................................

445

6.2.7

Ressourcen aus dem Klassenpfad und aus Jar-Archiven laden .....................

446

6.2.8

Ströme mit SequenceInputStream zusammensetzen * ...................................

447

6.2.9

Die abstrakte Basisklasse Writer ..............................................................................

449

6.2.10

Die Schnittstelle Appendable * .................................................................................

451

6.2.11

Die abstrakte Basisklasse Reader .............................................................................

451

Formatierte Textausgaben .........................................................................................................

454

6.3.1

Die Klassen PrintWriter und PrintStream ............................................................

455

6.3.2

System.out, System.err und System.in ..................................................................

460

Schreiben und Lesen aus Strings und Byte-Feldern .............................................................

462

6.4.1

Mit dem StringWriter ein String-Objekt füllen ...................................................

463

6.4.2

CharArrayWriter ...........................................................................................................

464

6.4.3

StringReader und CharArrayReader .......................................................................

465

6.4.4

Mit ByteArrayOutputStream in ein Byte-Feld schreiben ................................

467

6.4.5

Mit ByteArrayInputStream aus einem Byte-Feld lesen ....................................

467

Datenströme filtern und verketten .........................................................................................

468

6.5.1

Streams als Filter verketten (verschachteln) .......................................................

469

6.5.2

Gepufferte Ausgaben mit BufferedWriter und BufferedOutputStream .....

469

6.5.3

Gepufferte Eingaben mit BufferedReader/BufferedInputStream ................

471

6.5.4

LineNumberReader zählt automatisch Zeilen mit * ..........................................

474

6.5.5

Daten mit der Klasse PushbackReader zurücklegen * .......................................

474

6.5.6

DataOutputStream/DataInputStream * ................................................................

478

6.5.7

Basisklassen für Filter * ...............................................................................................

478

6.5.8

Die Basisklasse FilterWriter * ....................................................................................

479

6.5.9

Ein LowerCaseWriter * ................................................................................................

480

6.5.10

Eingaben mit der Klasse FilterReader filtern * ....................................................

482

6.5.11

Anwendungen für FilterReader und FilterWriter * ............................................

483

Vermittler zwischen Byte-Streams und Unicode-Strömen ...............................................

491

6.6.1

Datenkonvertierung durch den OutputStreamWriter .....................................

491

6.6.2

Automatische Konvertierungen mit dem InputStreamReader ....................

492

Kommunikation zwischen Threads mit Pipes * ....................................................................

493

6.7.1

PipedOutputStream und PipedInputStream .......................................................

494

6.7.2

PipedWriter und PipedReader ..................................................................................

496

13

Inhalt

6.8

6.9

Prüfsummen ...................................................................................................................................

498

6.8.1

Die Schnittstelle Checksum ......................................................................................

498

6.8.2

Die Klasse CRC32 ..........................................................................................................

499

6.8.3

Die Adler32-Klasse .......................................................................................................

501

Persistente Objekte und Serialisierung ..................................................................................

502

6.9.1

Objekte mit der Standard-Serialisierung speichern und lesen .....................

503

6.9.2

Zwei einfache Anwendungen der Serialisierung * ............................................

506

6.9.3

Die Schnittstelle Serializable ....................................................................................

507

6.9.4

Nicht serialisierbare Attribute aussparen ............................................................

509

6.9.5

Das Abspeichern selbst in die Hand nehmen .....................................................

511

6.9.6

Tiefe Objektkopien * ...................................................................................................

515

6.9.7

Versionenverwaltung und die SUID ......................................................................

517

6.9.8

Wie die ArrayList serialisiert * .................................................................................

519

6.9.9

Probleme mit der Serialisierung .............................................................................

520

6.10 Alternative Datenaustauschformate ......................................................................................

522

6.10.1

Serialisieren in XML-Dateien ...................................................................................

522

6.10.2

XML-Serialisierung von JavaBeans mit JavaBeans Persistence * ..................

522

6.10.3

Die Open-Source-Bibliothek XStream * ................................................................

525

6.10.4

Binäre Serialisierung mit Google Protocol Buffers * ........................................

526

6.11 Tokenizer * ......................................................................................................................................

526

6.11.1

StreamTokenizer ..........................................................................................................

526

6.12 Zum Weiterlesen ..........................................................................................................................

529

7

Die eXtensible Markup Language (XML)

7.1

Auszeichnungssprachen .............................................................................................................

531

7.1.1

Die Standard Generalized Markup Language (SGML) ......................................

532

7.1.2

Extensible Markup Language (XML) ......................................................................

532

Eigenschaften von XML-Dokumenten ....................................................................................

533

7.2.1

Elemente und Attribute .............................................................................................

533

7.2.2

Beschreibungssprache für den Aufbau von XML-Dokumenten ...................

535

7.2.3

Schema – eine Alternative zu DTD .........................................................................

539

7.2.4

Namensraum (Namespace) ......................................................................................

542

7.2.5

XML-Applikationen * ..................................................................................................

543

7.2

14

Inhalt

7.3

7.4

7.5

7.6

7.7

Die Java-APIs für XML ..................................................................................................................

544

7.3.1

Das Document Object Model (DOM) ......................................................................

545

7.3.2

Simple API for XML Parsing (SAX) ...........................................................................

545

7.3.3

Pull-API StAX .................................................................................................................

545

7.3.4

Java Document Object Model (JDOM) ....................................................................

545

7.3.5

JAXP als Java-Schnittstelle zu XML .........................................................................

546

7.3.6

DOM-Bäume einlesen mit JAXP * ............................................................................

547

Java Architecture for XML Binding (JAXB) ..............................................................................

548

7.4.1

Bean für JAXB aufbauen .............................................................................................

548

7.4.2

JAXBContext und die Marshaller ............................................................................

549

7.4.3

Ganze Objektgraphen schreiben und lesen .........................................................

550

7.4.4

Validierung .....................................................................................................................

553

7.4.5

Weitere JAXB-Annotationen * ..................................................................................

557

7.4.6

Beans aus XML-Schema-Datei generieren ............................................................

565

Serielle Verarbeitung mit StAX .................................................................................................

570

7.5.1

Unterschiede der Verarbeitungsmodelle .............................................................

571

7.5.2

XML-Dateien mit dem Cursor-Verfahren lesen ..................................................

572

7.5.3

XML-Dateien mit dem Iterator-Verfahren verarbeiten * .................................

576

7.5.4

Mit Filtern arbeiten * ...................................................................................................

578

7.5.5

XML-Dokumente schreiben ......................................................................................

579

Serielle Verarbeitung von XML mit SAX * ..............................................................................

582

7.6.1

583

Schnittstellen von SAX ...............................................................................................

7.6.2

SAX-Parser erzeugen ...................................................................................................

584

7.6.3

Operationen der Schnittstelle ContentHandler .................................................

584

7.6.4

ErrorHandler und EntityResolver ...........................................................................

587

XML-Dateien mit JDOM verarbeiten .......................................................................................

588

7.7.1

JDOM beziehen ..............................................................................................................

588

7.7.2

Paketübersicht * ............................................................................................................

588

7.7.3

Die Document-Klasse ..................................................................................................

590

7.7.4

Eingaben aus der Datei lesen ....................................................................................

591

7.7.5

Das Dokument im XML-Format ausgeben ...........................................................

592

7.7.6

Der Dokumenttyp * ......................................................................................................

593

7.7.7

Elemente .........................................................................................................................

594

7.7.8

Zugriff auf Elementinhalte ........................................................................................

597

7.7.9

Liste mit Unterelementen erzeugen * ....................................................................

599

7.7.10

Neue Elemente einfügen und ändern ....................................................................

600

15

Inhalt

7.8

7.9

7.7.11

Attributinhalte lesen und ändern ..........................................................................

603

7.7.12

XPath ...............................................................................................................................

606

Transformationen mit XSLT * ....................................................................................................

610

7.8.1

Templates und XPath als Kernelemente von XSLT ...........................................

610

7.8.2

Umwandlung von XML-Dateien mit JDOM und JAXP .....................................

613

XML-Schema-Validierung * ........................................................................................................

614

7.9.1

SchemaFactory und Schema ....................................................................................

614

7.9.2

Validator .........................................................................................................................

614

7.9.3

Validierung unterschiedlicher Datenquellen durchführen ...........................

615

7.10 Zum Weiterlesen ..........................................................................................................................

615

8

Dateiformate

8.1

Einfache Dateiformate für strukturierte Daten ...................................................................

618

8.1.1

618

8.2

8.3

Property-Dateien .........................................................................................................

8.1.2

CSV-Dateien ...................................................................................................................

618

8.1.3

JSON-Serialisierung mit Jackson .............................................................................

619

Dokumentenformate ...................................................................................................................

620

8.2.1

(X)HTML ..........................................................................................................................

620

8.2.2

PDF-Dokumente ...........................................................................................................

622

8.2.3

Microsoft Office-Dokumente ...................................................................................

622

8.2.4

OASIS Open Document Format ...............................................................................

624

Datenkompression * ....................................................................................................................

624

8.3.1

Java-Unterstützung beim Komprimieren ............................................................

625

8.3.2

Daten packen und entpacken ..................................................................................

626

8.3.3

Datenströme komprimieren ....................................................................................

627

8.3.4

Zip-Archive ....................................................................................................................

632

8.3.5

Jar-Archive .....................................................................................................................

640

8.4

Bild-Formate ..................................................................................................................................

640

8.5

Audio-Dateien ...............................................................................................................................

640

8.5.1

Die Arbeit mit AudioClip ...........................................................................................

640

8.5.2

MIDI-Dateien abspielen .............................................................................................

641

16

Inhalt

9

Grafische Oberflächen mit Swing

9.1

Fenster zur Welt ............................................................................................................................

645

9.1.1

Swing-Fenster mit javax.swing.JFrame darstellen .............................................

645

9.1.2

Fenster schließbar machen – setDefaultCloseOperation() ..............................

647

9.1.3

Sichtbarkeit des Fensters ...........................................................................................

648

9.1.4

Größe und Position des Fensters verändern .......................................................

648

9.1.5

Fenster- und Dialog-Dekoration, Transparenz * .................................................

649

9.1.6

Die Klasse Toolkit * ......................................................................................................

650

9.1.7

Dynamisches Layout während einer Größenänderung * ................................

651

9.1.8

Zum Vergleich: AWT-Fenster darstellen * .............................................................

651

Beschriftungen (JLabel) ...............................................................................................................

653

9.2.1

Mehrzeiliger Text, HTML in der Darstellung .......................................................

656

Icon und ImageIcon für Bilder auf Swing-Komponenten ..................................................

657

9.3.1

Die Klasse ImageIcon ..................................................................................................

657

9.3.2

Die Schnittstelle Icon und eigene Icons zeichnen * ...........................................

659

Es tut sich was – Ereignisse beim AWT ...................................................................................

661

9.4.1

Die Ereignisquellen und Horcher (Listener) von Swing ...................................

662

9.4.2

Listener implementieren ...........................................................................................

663

9.4.3

Listener bei dem Ereignisauslöser anmelden/abmelden ................................

666

9.4.4

Adapterklassen nutzen ...............................................................................................

668

9.4.5

Innere Mitgliedsklassen und innere anonyme Klassen ...................................

670

9.4.6

Aufrufen der Listener im AWT-Event-Thread .....................................................

672

9.4.7

Ereignisse, etwas genauer betrachtet * ..................................................................

673

9.2 9.3

9.4

9.5

9.6

Schaltflächen ..................................................................................................................................

676

9.5.1

Normale Schaltflächen (JButton) .............................................................................

676

9.5.2

Der aufmerksame ActionListener ...........................................................................

678

9.5.3

Schaltflächen-Ereignisse vom Typ ActionEvent .................................................

679

9.5.4

Basisklasse AbstractButton .......................................................................................

680

9.5.5

Wechselknopf (JToggleButton) ................................................................................

682

Textkomponenten ........................................................................................................................

683

9.6.1

Text in einer Eingabezeile .........................................................................................

684

9.6.2

Die Oberklasse der Text-Komponenten (JTextComponent) ..........................

685

9.6.3

Geschützte Eingaben (JPasswordField) ..................................................................

686

9.6.4

Validierende Eingabefelder (JFormattedTextField) ...........................................

687

9.6.5

Einfache mehrzeilige Textfelder (JTextArea) .......................................................

688

9.6.6

Editor-Klasse (JEditorPane) * .....................................................................................

692

17

Inhalt

9.7

Swing Action * ...............................................................................................................................

694

9.8

JComponent und Component als Basis aller Komponenten ............................................

697

9.9

9.8.1

Hinzufügen von Komponenten ..............................................................................

697

9.8.2

Tooltips (Kurzhinweise) .............................................................................................

698

9.8.3

Rahmen (Border) * .......................................................................................................

699

9.8.4

Fokus und Navigation * ..............................................................................................

701

9.8.5

Ereignisse jeder Komponente * ...............................................................................

702

9.8.6

Die Größe und Position einer Komponente * .....................................................

706

9.8.7

Komponenten-Ereignisse * .......................................................................................

707

9.8.8

UI-Delegate – der wahre Zeichner * ........................................................................

708

9.8.9

Undurchsichtige (opake) Komponente * ..............................................................

711

9.8.10

Properties und Listener für Änderungen * ..........................................................

711

Container ........................................................................................................................................

711

9.9.1

Standardcontainer (JPanel) .......................................................................................

712

9.9.2

Bereich mit automatischen Rollbalken (JScrollPane) .......................................

713

9.9.3

Reiter (JTabbedPane) ...................................................................................................

714

9.9.4

Teilungskomponente (JSplitPane) ..........................................................................

715

9.10 Alles Auslegungssache: die Layoutmanager ........................................................................

716

9.10.1

Übersicht über Layoutmanager ..............................................................................

717

9.10.2

Zuweisen eines Layoutmanagers ...........................................................................

717

9.10.3

Im Fluss mit FlowLayout ...........................................................................................

718

9.10.4

BoxLayout ......................................................................................................................

720

9.10.5

Mit BorderLayout in alle Himmelsrichtungen ...................................................

721

9.10.6

Rasteranordnung mit GridLayout ..........................................................................

724

9.10.7

Der GridBagLayoutmanager * ..................................................................................

725

9.10.8

Null-Layout * .................................................................................................................

731

9.10.9

Weitere Layoutmanager ............................................................................................

732

9.11 Rollbalken und Schieberegler ...................................................................................................

733

9.11.1

Schieberegler (JSlider) ................................................................................................

733

9.11.2

Rollbalken (JScrollBar) * .............................................................................................

734

9.12 Kontrollfelder, Optionsfelder, Kontrollfeldgruppen .......................................................... Kontrollfelder (JCheckBox) .......................................................................................

740

9.12.2

ItemSelectable, ItemListener und das ItemEvent .............................................

741

9.12.3

Sich gegenseitig ausschließende Optionen (JRadioButton) ...........................

743

9.13 Fortschritte bei Operationen überwachen * .........................................................................

18

739

9.12.1

745

9.13.1

Fortschrittsbalken (JProgressBar) ...........................................................................

745

9.13.2

Dialog mit Fortschrittsanzeige (ProgressMonitor) ...........................................

748

Inhalt

9.14 Menüs und Symbolleisten .......................................................................................................... 9.14.1

Die Menüleisten und die Einträge ..........................................................................

748 748

9.14.2

Menüeinträge definieren ...........................................................................................

750

9.14.3

Einträge durch Action-Objekte beschreiben ........................................................

751

9.14.4

Mit der Tastatur: Mnemonics und Shortcut ........................................................

752

9.14.5

Der Tastatur-Shortcut (Accelerator) .......................................................................

753

9.14.6

Tastenkürzel (Mnemonics) ........................................................................................

755

9.14.7

Symbolleisten alias Toolbars ....................................................................................

756

9.14.8

Popup-Menüs ................................................................................................................

759

9.14.9

System-Tray nutzen * ..................................................................................................

764

9.15 Das Model-View-Controller-Konzept ......................................................................................

765

9.16 Auswahlmenüs, Listen und Spinner .........................................................................................

767

9.16.1

Listen (JList) ....................................................................................................................

767

9.16.2

Auswahlmenü (JComboBox) .....................................................................................

775

9.16.3

Drehfeld (JSpinner) * ....................................................................................................

787

9.16.4

Datumsauswahl * ..........................................................................................................

789

9.17 Tabellen (JTable) ............................................................................................................................ 9.17.1

789

Ein eigenes Tabellen-Model ......................................................................................

791

9.17.2

Basisklasse für eigene Modelle (AbstractTableModel) .....................................

792

9.17.3

Ein vorgefertigtes Standard-Modell (DefaultTableModel) ..............................

796

9.17.4

Ein eigener Renderer für Tabellen ..........................................................................

798

9.17.5

Zell-Editoren ..................................................................................................................

801

9.17.6

Größe und Umrandung der Zellen * .......................................................................

803

9.17.7

Spalteninformationen * .............................................................................................

803

9.17.8

Tabellenkopf von Swing-Tabellen * ........................................................................

804

9.17.9

Selektionen einer Tabelle * ........................................................................................

805

9.17.10 Automatisches Sortieren und Filtern mit RowSorter * ....................................

806

9.18 Bäume (JTree) .................................................................................................................................

809

9.18.1

JTree und sein TreeModel und TreeNode .............................................................

809

9.18.2

Selektionen bemerken ................................................................................................

811

9.18.3

Das TreeModel von JTree * .........................................................................................

811

9.19 JRootPane und JDesktopPane * .................................................................................................

815

9.19.1

Wurzelkomponente der Top-Level-Komponenten (JRootPane) ...................

815

9.19.2

JDesktopPane und die Kinder von JInternalFrame ...........................................

815

9.19.3

JLayeredPane .................................................................................................................

817

9.20 Dialoge und Window-Objekte ...................................................................................................

818

9.20.1

JWindow und JDialog ..................................................................................................

818

19

Inhalt

9.20.2

Modal oder nicht-modal? ..........................................................................................

819

9.20.3

Standarddialoge mit JOptionPane .........................................................................

820

9.20.4

Der Dateiauswahldialog ............................................................................................

823

9.20.5

Der Farbauswahldialog JColorChooser * ...............................................................

827

9.21 Flexibles Java-Look-and-Feel ....................................................................................................

830

9.21.1

Look and Feel global setzen ......................................................................................

830

9.21.2

UIManager .....................................................................................................................

830

9.21.3

Die Windows-Optik mit JGoodies Looks verbessern * .....................................

833

9.22 Swing-Komponenten neu erstellen oder verändern * .......................................................

833

9.22.1

Überlagerungen mit dem Swing-Komponenten-Dekorator JLayer ............

834

9.23 Die Zwischenablage (Clipboard) ...............................................................................................

836

9.23.1

Clipboard-Objekte .......................................................................................................

836

9.23.2

Mit Transferable auf den Inhalt zugreifen ...........................................................

837

9.23.3

DataFlavor ist das Format der Daten in der Zwischenablage ........................

838

9.23.4

Einfügungen in der Zwischenablage erkennen ..................................................

840

9.23.5

Drag & Drop ...................................................................................................................

841

9.24 Undo durchführen * .....................................................................................................................

842

9.25 AWT, Swing und die Threads .....................................................................................................

844

9.25.1

Ereignisschlange (EventQueue) und AWT-Event-Thread ................................

844

9.25.2

Swing ist nicht thread-sicher ...................................................................................

845

9.25.3

invokeLater() und invokeAndWait() ......................................................................

848

9.25.4

SwingWorker .................................................................................................................

851

9.25.5

Eigene Ereignisse in die Queue setzen * ...............................................................

853

9.25.6

Auf alle Ereignisse hören * ........................................................................................

854

9.26 Barrierefreiheit mit der Java Accessibility API .....................................................................

854

9.27 Zeitliches Ausführen mit dem javax.swing.Timer ..............................................................

855

9.28 Die Zusatzkomponentenbibliothek SwingX .........................................................................

856

9.28.1

Im Angebot: Erweiterte und neue Swing-Komponenten ...............................

857

9.28.2

Überblick über erweiterte Standard-Swing-Klassen .........................................

857

9.28.3

Neue Swing-Klassen ....................................................................................................

858

9.28.4

Weitere SwingX-Klassen ............................................................................................

859

9.28.5

SwingX-Installation ....................................................................................................

860

9.29 Alternativen zu programmierten Oberflächen, AWT und Swing * .................................

860

9.29.1

Deklarative Beschreibungen der Oberfläche: Swing JavaBuilder, Swixml

860

9.29.2

SWT (Standard Widget Toolkit) ...............................................................................

862

9.30 Zum Weiterlesen ..........................................................................................................................

864

20

Inhalt

10 Grafikprogrammierung 10.1 Grundlegendes zum Zeichnen .................................................................................................

865

10.1.1

Die paint()-Methode für das AWT-Frame ............................................................

865

10.1.2

Zeichnen von Inhalten auf ein JFrame ................................................................

867

10.1.3

Auffordern zum Neuzeichnen mit repaint() ......................................................

869

10.1.4

Java 2D-API ...................................................................................................................

869

10.2 Einfache Zeichenmethoden ......................................................................................................

870

10.2.1

Linien .............................................................................................................................

870

10.2.2

Rechtecke ......................................................................................................................

871

10.2.3

Ovale und Kreisbögen ...............................................................................................

872

10.2.4

Polygone und Polylines ............................................................................................

874

10.3 Zeichenketten schreiben und Fonts .......................................................................................

876

10.3.1

Zeichenfolgen schreiben ..........................................................................................

877

10.3.2

Die Font-Klasse ...........................................................................................................

878

10.3.3

Einen neuen Font aus einem gegebenen Font ableiten .................................

880

10.3.4

Zeichensätze des Systems ermitteln * .................................................................

880

10.3.5

Neue TrueType-Fonts in Java nutzen ...................................................................

881

10.3.6

Font-Metadaten durch FontMetrics * ..................................................................

882

10.4 Geometrische Objekte ...............................................................................................................

886

10.4.1

Die Schnittstelle Shape .............................................................................................

888

10.4.2

Kreisförmiges ..............................................................................................................

889

10.4.3

Kurviges * ......................................................................................................................

890

10.4.4

Area und die konstruktive Flächengeometrie * ................................................

890

10.4.5

Pfade * ............................................................................................................................

891

10.4.6

Punkt in einer Form, Schnitt von Linien, Abstand Punkt/Linie * ...............

895

10.5 Das Innere und Äußere einer Form ........................................................................................ 10.5.1

Farben und die Paint-Schnittstelle .......................................................................

896 896

10.5.2

Farben mit der Klasse Color ....................................................................................

897

10.5.3

Die Farben des Systems über SystemColor * .....................................................

903

10.5.4

Composite und Xor * .................................................................................................

907

10.5.5

Dicke und Art der Linien von Formen bestimmen über Stroke * ...............

908

10.6 Bilder ...............................................................................................................................................

913

10.6.1

Eine Übersicht über die Bilder-Bibliotheken .....................................................

914

10.6.2

Bilder mit ImageIO lesen .........................................................................................

915

10.6.3

Ein Bild zeichnen ........................................................................................................

917

10.6.4

Programm-Icon/Fenster-Icon setzen ...................................................................

921

21

Inhalt

10.6.5

Splash-Screen * ...........................................................................................................

922

10.6.6

Bilder im Speicher erzeugen * ................................................................................

922

10.6.7

Pixel für Pixel auslesen und schreiben * ............................................................

925

10.6.8

Bilder skalieren * ........................................................................................................

927

10.6.9

Schreiben mit ImageIO ............................................................................................

929

10.6.10 Asynchrones Laden mit getImage() und dem MediaTracker * ....................

934

10.7 Weitere Eigenschaften von Graphics * .................................................................................

935

10.7.1

Eine Kopie von Graphics erstellen .......................................................................

10.7.2

Koordinatensystem verschieben .........................................................................

936

10.7.3

Beschnitt (Clipping) ..................................................................................................

937

10.7.4

Zeichenhinweise durch RenderingHints ...........................................................

941

10.7.5

Transformationen mit einem AffineTransform-Objekt ...............................

941

10.8 Drucken * .......................................................................................................................................

935

944

10.8.1

Drucken der Inhalte ..................................................................................................

944

10.8.2

Bekannte Drucker ......................................................................................................

946

10.9 Benutzerinteraktionen automatisieren, Robot und Screenshots * ..............................

947

10.9.1

Der Roboter .................................................................................................................

947

10.9.2

Automatisch in die Tasten hauen ........................................................................

948

10.9.3

Automatisierte Mausoperationen .......................................................................

949

10.9.4

Methoden zur Zeitsteuerung .................................................................................

949

10.9.5

Bildschirmabzüge (Screenshots) ...........................................................................

950

10.9.6

Funktionsweise und Beschränkungen ................................................................

952

10.9.7

MouseInfo und PointerInfo ...................................................................................

952

10.10 Zum Weiterlesen ........................................................................................................................

953

11 Netzwerkprogrammierung 11.1 Grundlegende Begriffe .............................................................................................................

955

11.2 URI und URL ..................................................................................................................................

957

11.2.1

Die Klasse URI .............................................................................................................

11.2.2

Die Klasse URL ............................................................................................................

958

11.2.3

Informationen über eine URL * .............................................................................

961

11.2.4

Der Zugriff auf die Daten über die Klasse URL .................................................

963

11.3 Die Klasse URLConnection * .....................................................................................................

965

22

957

11.3.1

Methoden und Anwendung von URLConnection ...........................................

965

11.3.2

Protokoll- und Content-Handler ..........................................................................

967

Inhalt

11.3.3

Im Detail: vom URL zur URLConnection ............................................................

968

11.3.4

Der Protokoll-Handler für Jar-Dateien ................................................................

969

11.3.5

Basic Authentication und Proxy-Authentifizierung .......................................

971

11.4 Mit GET und POST Daten übergeben * ..................................................................................

973

11.4.1

Kodieren der Parameter für Serverprogramme ...............................................

974

11.4.2

Eine Suchmaschine mit GET-Request ansprechen ..........................................

975

11.4.3

POST-Request absenden ...........................................................................................

976

11.5 Host- und IP-Adressen ...............................................................................................................

977

11.5.1

Lebt der Rechner? .......................................................................................................

980

11.5.2

IP-Adresse des lokalen Hosts ..................................................................................

980

11.5.3

Das Netz ist klasse ......................................................................................................

981

11.5.4

NetworkInterface .......................................................................................................

981

11.6 Mit dem Socket zum Server ......................................................................................................

983

11.6.1

Das Netzwerk ist der Computer .............................................................................

983

11.6.2

Sockets ...........................................................................................................................

983

11.6.3

Eine Verbindung zum Server aufbauen ..............................................................

984

11.6.4

Server unter Spannung: die Ströme .....................................................................

985

11.6.5

Die Verbindung wieder abbauen ...........................................................................

986

11.6.6

Informationen über den Socket * ..........................................................................

986

11.6.7

Reine Verbindungsdaten über SocketAddress * ...............................................

988

11.7 Client-Server-Kommunikation ................................................................................................

989

11.7.1

Warten auf Verbindungen .......................................................................................

990

11.7.2

Ein Multiplikationsserver ........................................................................................

991

11.7.3

Blockierendes Lesen ..................................................................................................

995

11.7.4

Von außen erreichbar sein * ...................................................................................

996

11.8 Apache HttpComponents und Commons Net * ..................................................................

997

11.8.1

HttpComponents .......................................................................................................

997

11.8.2

Apache Commons Net ..............................................................................................

998

11.9 Arbeitsweise eines Webservers * ............................................................................................

998

11.9.1

Das Hypertext Transfer Protocol (HTTP) ............................................................

998

11.9.2

Anfragen an den Server ............................................................................................

999

11.9.3

Die Antworten vom Server ...................................................................................... 1002

11.9.4

Webserver mit com.sun.net.httpserver.HttpServer ........................................ 1006

11.10 Verbindungen durch einen Proxy-Server * .......................................................................... 1007 11.10.1 System-Properties ...................................................................................................... 1008 11.10.2 Verbindungen durch die Proxy-API ..................................................................... 1008

23

Inhalt

11.11 Datagram-Sockets * ...................................................................................................................

1010

11.11.1 Die Klasse DatagramSocket ....................................................................................

1012

11.11.2 Datagramme und die Klasse DatagramPacket .................................................

1013

11.11.3 Auf ein hereinkommendes Paket warten ..........................................................

1014

11.11.4 Ein Paket zum Senden vorbereiten ......................................................................

1015

11.11.5 Methoden der Klasse DatagramPacket ...............................................................

1016

11.11.6 Das Paket senden .......................................................................................................

1017

11.12 E-Mail * ..........................................................................................................................................

1019

11.12.1 Wie eine E-Mail um die Welt geht ........................................................................

1019

11.12.2 Das Simple Mail Transfer Protocol und RFC 822 .............................................

1019

11.12.3 POP (Post Office Protocol) .......................................................................................

1020

11.12.4 Die JavaMail API .........................................................................................................

1020

11.12.5 E-Mails mittels POP3 abrufen ................................................................................

1023

11.12.6 Multipart-Nachrichten verarbeiten .....................................................................

1025

11.12.7 E-Mails versenden .....................................................................................................

1027

11.12.8 Ereignisse und Suchen .............................................................................................

1030

11.13 Tiefer liegende Netzwerkeigenschaften * ...........................................................................

1031

11.13.1 Internet Control Message Protocol (ICMP) ........................................................

1031

11.13.2 MAC-Adresse ...............................................................................................................

1032

11.14 Zum Weiterlesen ........................................................................................................................

1033

12 Verteilte Programmierung mit RMI 12.1 Entfernte Objekte und Methoden .........................................................................................

1035

12.1.1

Stellvertreter helfen bei entfernten Methodenaufrufen ..............................

1035

12.1.2

Standards für entfernte Objekte ...........................................................................

1037

12.2 Java Remote Method Invocation ...........................................................................................

1038

12.2.1

Zusammenspiel von Server, Registry und Client ............................................

1038

12.2.2

Wie die Stellvertreter die Daten übertragen .....................................................

1038

12.2.3

Probleme mit entfernten Methoden ...................................................................

1039

12.2.4

Nutzen von RMI bei Middleware-Lösungen ......................................................

1040

12.2.5

Zentrale Klassen und Schnittstellen ....................................................................

1041

12.2.6

Entfernte und lokale Objekte im Vergleich .......................................................

1042

12.3 Auf der Serverseite .....................................................................................................................

1042

24

12.3.1

Entfernte Schnittstelle deklarieren ......................................................................

1042

12.3.2

Remote-Objekt-Implementierung .......................................................................

1043

Inhalt

12.3.3

Stellvertreterobjekte ................................................................................................. 1044

12.3.4

Der Namensdienst (Registry) .................................................................................. 1045

12.3.5

Remote-Objekt-Implementierung exportieren und beim Namensdienst anmelden ........................................................................................ 1047

12.3.6

Einfaches Logging ...................................................................................................... 1049

12.3.7

Aufräumen mit dem DGC * ..................................................................................... 1050

12.4 Auf der Clientseite ...................................................................................................................... 1051 12.5 Entfernte Objekte übergeben und laden ............................................................................. 1052 12.5.1

Klassen vom RMI-Klassenlader nachladen ........................................................ 1052

12.6 Weitere Eigenschaften von RMI .............................................................................................. 1053 12.6.1

RMI und CORBA .......................................................................................................... 1053

12.6.2

RMI über HTTP getunnelt ........................................................................................ 1054

12.6.3

Automatische Remote-Objekt-Aktivierung ....................................................... 1054

12.7 Java Message Service (JMS) ...................................................................................................... 1055 12.8 Zum Weiterlesen ......................................................................................................................... 1055

13 RESTful und SOAP Web-Services 13.1 Web-Services ................................................................................................................................ 1057 13.2 RESTful Web-Services ................................................................................................................. 1059 13.2.1

Aus Prinzip REST ......................................................................................................... 1059

13.2.2

Jersey .............................................................................................................................. 1060

13.2.3

JAX-RS-Annotationen für den ersten REST-Service ......................................... 1061

13.2.4

Test-Server starten ..................................................................................................... 1062

13.2.5

REST-Services konsumieren .................................................................................... 1063

13.2.6

Content-Hander, Marshaller und verschiedene MIME-Typen ..................... 1064

13.2.7

REST-Parameter .......................................................................................................... 1067

13.2.8

REST-Services mit Parametern über die Jersey-Client-API aufrufen .......... 1069

13.2.9

PUT-Anforderungen und das Senden von Daten ............................................. 1070

13.2.10 PUT/POST/DELETE-Sendungen mit der Jersey-Client-API absetzen .......... 1071

13.3 Daily Soap und das SOAP-Protokoll ....................................................................................... 1071 13.3.1

Die technische Realisierung .................................................................................... 1072

13.3.2

Web-Service-APIs und Implementierungen ...................................................... 1072

13.3.3

@WebService in Java 6 ............................................................................................. 1073

13.3.4

Einen Web-Service definieren ................................................................................ 1074

25

Inhalt

13.3.5

Web-Services veröffentlichen ................................................................................

1075

13.3.6

Einen JAX-WS-Client implementieren ................................................................

1076

13.4 Zum Weiterlesen ........................................................................................................................

1078

14 JavaServer Pages und Servlets 14.1 Dynamisch generierte Webseiten .........................................................................................

1079

14.1.1

Was sind Servlets? .....................................................................................................

1080

14.1.2

Was sind JavaServer Pages? ....................................................................................

1081

14.2 Servlets und JSPs mit Tomcat entwickeln ...........................................................................

1082

14.2.1

Servlet-Container .......................................................................................................

1082

14.2.2

Entwicklung der Servlet/JSP-Spezifikationen ...................................................

1083

14.2.3

Webserver mit Servlet-Funktionalität ................................................................

1083

14.2.4

Tomcat installieren ...................................................................................................

1084

14.2.5

Ablageort für eigene JSPs ........................................................................................

1086

14.2.6

Webapplikationen .....................................................................................................

1087

14.2.7

Zuordnung von Webapplikationen zu physikalischen Verzeichnissen ...

1088

14.2.8

Web-Projekt mit Eclipse IDE for Java EE Developers ......................................

1088

14.3 Statisches und Dynamisches ...................................................................................................

1090

14.3.1

Statischer Template-Code .......................................................................................

1090

14.3.2

Dynamische Inhalte ..................................................................................................

1090

14.3.3

Kommentare ...............................................................................................................

1091

14.4 Die Expression Language (EL) ..................................................................................................

1091

14.4.1

Operatoren der EL ......................................................................................................

1092

14.4.2

Literale ..........................................................................................................................

1092

14.4.3

Implizite EL-Objekte .................................................................................................

1093

14.5 Formulardaten .............................................................................................................................

1094

14.5.1

Einen Parameter auslesen ......................................................................................

1094

14.5.2

HTML-Formulare .......................................................................................................

1095

14.6 Auf Beans zurückgreifen ..........................................................................................................

1096

14.6.1

26

Beans in JSPs anlegen ...............................................................................................

1097

14.6.2

Properties einer Bean im EL-Ausdruck erfragen ..............................................

1097

14.6.3

Properties mit setzen ............................................................

1097

14.6.4

Bean-Klasse zum Testen von E-Mail-Adressen .................................................

1098

14.6.5

Parameterwerte in Bean übertragen ...................................................................

1100

Inhalt

14.7 JSP-Tag-Libraries .......................................................................................................................... 1101 14.7.1

Standard Tag Library (JSTL) ..................................................................................... 1101

14.8 Skripting-Elemente in JSPs ....................................................................................................... 1105 14.8.1

Scriptlets ....................................................................................................................... 1106

14.8.2

JSP-Ausdrücke ............................................................................................................. 1106

14.8.3

JSP-Deklarationen ...................................................................................................... 1107

14.8.4

Quoting .......................................................................................................................... 1107

14.8.5

Entsprechende XML-Tags ........................................................................................ 1108

14.8.6

Implizite Objekte für Scriptlets und JSP-Ausdrücke ........................................ 1108

14.9 Sitzungsverfolgung (Session Tracking) ................................................................................. 1109 14.9.1

Lösungen für die Sitzungsverfolgung .................................................................. 1109

14.9.2

Sitzungen in JSPs ........................................................................................................ 1110

14.9.3

Auf Session-Dateien zurückgreifen ...................................................................... 1111

14.10 Servlets ........................................................................................................................................... 1111 14.10.1 Servlets compilieren ................................................................................................. 1115 14.10.2 Servlet-Mapping ......................................................................................................... 1116 14.10.3 Der Lebenszyklus eines Servlets ............................................................................ 1117 14.10.4 Mehrere Anfragen beim Servlet und die Thread-Sicherheit ......................... 1118 14.10.5 Servlets und Sessions ................................................................................................ 1118 14.10.6 Weiterleiten und Einbinden von Servlet-Inhalten .......................................... 1119

14.11 Zum Weiterlesen ......................................................................................................................... 1120

15 Applets 15.1 Applets in der Wiege von Java ................................................................................................ 1123 15.1.1

Applets heute ............................................................................................................... 1123

15.1.2

(J)Applet und Applikationen ................................................................................... 1124

15.1.3

Das erste Hallo-Applet .............................................................................................. 1125

15.2 Die Applet-API .............................................................................................................................. 1127 15.2.1

Die Zyklen eines Applets .......................................................................................... 1127

15.2.2

Parameter an das Applet übergeben .................................................................... 1127

15.2.3

Wie das Applet den Browser-Inhalt ändern kann * .......................................... 1129

15.2.4

Den Ursprung des Applets erfragen ..................................................................... 1130

15.2.5

Datenaustausch zwischen Applets * ..................................................................... 1131

15.2.6

Was ein Applet alles darf * ....................................................................................... 1134

27

Inhalt

15.2.7

Ist Java im Browser aktiviert? * ..............................................................................

1135

15.2.8

Applet unter Firefox (Netscape) oder Microsoft Internet Explorer? * .......

1135

15.3 Webstart .......................................................................................................................................

1137

15.4 Zum Weiterlesen ........................................................................................................................

1137

16 Datenbankmanagement mit JDBC 16.1 Relationale Datenbanken ........................................................................................................ 16.1.1

1139

Das relationale Modell .............................................................................................

1139

16.2 Datenbanken und Tools ............................................................................................................

1140

16.2.1

HSQLDB .........................................................................................................................

1141

16.2.2

Weitere Datenbanken * ............................................................................................

1142

16.2.3

Eclipse-Plugins zum Durchschauen von Datenbanken .................................

1145

16.3 JDBC und Datenbanktreiber ....................................................................................................

1148

16.3.1

Treibertypen * .............................................................................................................

1149

16.3.2

JDBC-Versionen * .......................................................................................................

1151

16.4 Eine Beispielabfrage ..................................................................................................................

1152

16.4.1

Schritte zur Datenbankabfrage .............................................................................

1152

16.4.2

Ein Client für die HSQLDB-Datenbank ................................................................

1153

16.4.3

Datenbankbrowser und eine Beispielabfrage unter NetBeans ...................

1155

16.5 Mit Java an eine Datenbank andocken ................................................................................

1159

16.5.1

Der Treiber-Manager * ..............................................................................................

1160

16.5.2

Den Treiber laden ......................................................................................................

1160

16.5.3

Eine Aufzählung aller Treiber * ..............................................................................

1162

16.5.4

Log-Informationen * .................................................................................................

1162

16.5.5

Verbindung zur Datenbank auf- und abbauen ................................................

1163

16.6 Datenbankabfragen ...................................................................................................................

1167

28

16.6.1

Abfragen über das Statement-Objekt ..................................................................

1168

16.6.2

Ergebnisse einer Abfrage in ResultSet ................................................................

1169

16.6.3

Java und SQL-Datentypen .......................................................................................

1170

16.6.4

Date, Time und Timestamp ....................................................................................

1173

16.6.5

Unicode in der Spalte korrekt auslesen ..............................................................

1175

16.6.6

Eine SQL-NULL und wasNull() bei ResultSet ......................................................

1175

16.6.7

Wie viele Zeilen hat ein ResultSet? * ....................................................................

1176

Inhalt

16.7 Elemente einer Datenbank hinzufügen und aktualisieren ............................................. 1176 16.7.1

Batch-Updates ............................................................................................................. 1177

16.7.2

Die Ausnahmen bei JDBC, SQLException und Unterklassen ........................ 1179

16.8 ResultSet und RowSet * ............................................................................................................. 1182 16.8.1

Die Schnittstelle RowSet .......................................................................................... 1182

16.8.2

Implementierungen von RowSet .......................................................................... 1182

16.8.3

Der Typ CachedRowSet ............................................................................................. 1183

16.8.4

Der Typ WebRowSet .................................................................................................. 1184

16.9 Vorbereitete Anweisungen (Prepared Statements) .......................................................... 1187 16.9.1

PreparedStatement-Objekte vorbereiten ........................................................... 1187

16.9.2

Werte für die Platzhalter eines PreparedStatement ........................................ 1189

16.10 Transaktionen .............................................................................................................................. 1190 16.11 Metadaten * .................................................................................................................................. 1190 16.11.1 Metadaten über die Tabelle ..................................................................................... 1190 16.11.2 Informationen über die Datenbank ..................................................................... 1194

16.12 Vorbereitete Datenbankverbindungen ................................................................................ 1195 16.12.1 DataSource ................................................................................................................... 1195 16.12.2 Gepoolte Verbindungen ........................................................................................... 1198

16.13 JPA-Beispiel mit der NetBeans-IDE ......................................................................................... 1199 16.13.1 Entity-Beans generieren ........................................................................................... 1199 16.13.2 Die Quellen im Überblick ......................................................................................... 1201 16.13.3 Persistence Unit .......................................................................................................... 1203 16.13.4 Ein JPA-Beispielprogramm ...................................................................................... 1204

16.14 Zum Weiterlesen ......................................................................................................................... 1205

17 Technologien für die Infrastruktur 17.1 Property-Validierung durch Bean Validation ...................................................................... 1207 17.1.1

Technische Abhängigkeiten und POJOs .............................................................. 1218

17.2 Wie eine Implementierung an die richtige Stelle kommt ............................................... 1220 17.2.1

Arbeiten mit dem ServiceLoader ........................................................................... 1221

17.2.2

Die Utility-Klasse Lookup als ServiceLoader-Fassade ..................................... 1223

17.2.3

Contexts and Dependency Injection (CDI) aus dem JSR-299 ........................ 1224

17.3 Zum Weiterlesen ......................................................................................................................... 1226

29

Inhalt

18 Reflection und Annotationen 18.1 Metadaten .................................................................................................................................... 18.1.1

Metadaten durch JavaDoc-Tags .............................................................................

18.2 Metadaten der Klassen mit dem Class-Objekt ...................................................................

1227 1227 1228

18.2.1

An ein Class-Objekt kommen ................................................................................

1228

18.2.2

Was das Class-Objekt beschreibt * ........................................................................

1231

18.2.3

Der Name der Klasse .................................................................................................

1233

18.2.4

instanceof mit Class-Objekten * ............................................................................

1235

18.2.5

Oberklassen finden * .................................................................................................

1236

18.2.6

Implementierte Interfaces einer Klasse oder eines Interfaces * .................

1237

18.2.7

Modifizierer und die Klasse Modifier * ...............................................................

1238

18.2.8

Die Arbeit auf dem Feld * .........................................................................................

1240

18.3 Attribute, Methoden und Konstruktoren ............................................................................

1241

18.3.1

Reflections – Gespür für die Attribute einer Klasse ........................................

1243

18.3.2

Methoden einer Klasse erfragen ...........................................................................

1246

18.3.3

Properties einer Bean erfragen .............................................................................

1250

18.3.4

Konstruktoren einer Klasse ....................................................................................

1250

18.3.5

Annotationen ..............................................................................................................

1253

18.4 Objekte erzeugen und manipulieren ....................................................................................

1253

18.4.1

Objekte erzeugen .......................................................................................................

1253

18.4.2

Die Belegung der Variablen erfragen ..................................................................

1255

18.4.3

Eine generische eigene toString()-Methode * ...................................................

1257

18.4.4

Variablen setzen .........................................................................................................

1260

18.4.5

Bean-Zustände kopieren * .......................................................................................

1262

18.4.6

Private Attribute ändern .........................................................................................

1262

18.4.7

Methoden aufrufen ...................................................................................................

1263

18.4.8

Statische Methoden aufrufen ................................................................................

1265

18.4.9

Dynamische Methodenaufrufe bei festen Methoden beschleunigen * ....

1266

18.5 Eigene Annotationstypen * ......................................................................................................

1268

30

18.5.1

Annotationen zum Laden von Ressourcen .......................................................

1268

18.5.2

Neue Annotationen deklarieren ...........................................................................

1269

18.5.3

Annotationen mit genau einem Attribut ..........................................................

1270

18.5.4

Element-Werte-Paare (Attribute) hinzufügen ..................................................

1271

18.5.5

Annotationsattribute vom Typ einer Aufzählung ..........................................

1272

Inhalt

18.5.6

Felder von Annotationsattributen ....................................................................... 1273

18.5.7

Vorbelegte Attribute ................................................................................................. 1274

18.5.8

Annotieren von Annotationstypen ...................................................................... 1276

18.5.9

Deklarationen für unsere Ressourcen-Annotationen .................................... 1280

18.5.10 Annotierte Elemente auslesen ............................................................................... 1282 18.5.11 Auf die Annotationsattribute zugreifen ............................................................. 1283 18.5.12 Komplettbeispiel zum Initialisieren von Ressourcen .................................... 1284 18.5.13 Mögliche Nachteile von Annotationen ............................................................... 1287

18.6 Zum Weiterlesen ......................................................................................................................... 1288

19 Dynamische Übersetzung und Skriptsprachen 19.1 Codegenerierung ......................................................................................................................... 1290 19.1.1

Generierung von Java-Quellcode .......................................................................... 1291

19.1.2

Codetransformationen ............................................................................................. 1294

19.1.3

Erstellen von Java-Bytecode .................................................................................... 1294

19.2 Programme mit der Compiler API übersetzen .................................................................... 1295 19.2.1

Java Compiler API ....................................................................................................... 1295

19.2.2

Fehler-Diagnose .......................................................................................................... 1298

19.2.3

Eine im String angegebene Compiliationseinheit übersetzen .................... 1300

19.2.4

Wenn Quelle und Ziel der Speicher sind ............................................................. 1302

19.3 Ausführen von Skripten ............................................................................................................ 1306 19.3.1

JavaScript-Programme ausführen ........................................................................ 1306

19.3.2

Groovy ........................................................................................................................... 1308

19.4 Zum Weiterlesen ......................................................................................................................... 1314

20 Logging und Monitoring 20.1 Logging mit Java .......................................................................................................................... 1315 20.1.1

Logging-APIs ................................................................................................................ 1315

20.1.2

Logging mit java.util.logging .................................................................................. 1316

20.1.3

Logging mit log4j * ..................................................................................................... 1320

20.1.4

Die Simple Logging Facade ...................................................................................... 1323

20.2 Systemzustände überwachen .................................................................................................. 1324

31

Inhalt

20.3 MBean-Typen, MBean-Server und weitere Begriffe ......................................................... 20.3.1

1325

MXBeans des Systems ..............................................................................................

1325

20.4 Geschwätzige Programme und JConsole .............................................................................

1328

20.4.1

JConsole ........................................................................................................................

1328

20.5 Der MBeanServer ........................................................................................................................

1330

20.6 Eine eigene Standard-MBean ..................................................................................................

1331

20.6.1

Management-Schnittstelle .....................................................................................

1332

20.6.2

Implementierung der Managed-Ressource .......................................................

1332

20.6.3

Anmeldung beim Server .........................................................................................

1333

20.6.4

Eine eigene Bean in JConsole einbringen ..........................................................

1333

20.7 Zum Weiterlesen ........................................................................................................................

1336

21 Java Native Interface (JNI) 21.1 Java Native Interface und Invocation-API ........................................................................... 21.2 Eine C-Funktion in ein Java-Programm einbinden ............................................................ 21.2.1

Den Java-Code schreiben .........................................................................................

21.3 Dynamische Bibliotheken erzeugen ..................................................................................... 21.3.1

1337 1338 1338 1340

Die Header-Datei erzeugen .....................................................................................

1340

21.3.2

Implementierung der Funktion in C ...................................................................

1342

21.3.3

Die C-Programme übersetzen und die dynamische Bibliothek überzeugen ..................................................................................................................

1343

21.4 Nativ die Stringlänge ermitteln .............................................................................................

1345

21.5 Erweiterte JNI-Eigenschaften ..................................................................................................

1347

21.5.1

Klassendefinitionen .................................................................................................

1347

21.5.2

Zugriff auf Attribute .................................................................................................

1348

21.5.3

Methoden aufrufen ...................................................................................................

1350

21.5.4

Threads und Synchronisation ...............................................................................

1352

21.6 Einfache Anbindung von existierenden Bibliotheken .....................................................

1352

21.6.1

Generieren von JNI-Wrappern aus C++-Klassen und C-Headern ................

1352

21.6.2

COM-Schnittstellen anzapfen ................................................................................

1353

21.7 Invocation-API .............................................................................................................................

1353

21.8 Zum Weiterlesen ........................................................................................................................

1354

32

Inhalt

22 Sicherheitskonzepte 22.1 Zentrale Elemente der Java-Sicherheit ................................................................................. 1355 22.1.1

Security-API der Java SE ............................................................................................ 1355

22.1.2

Cryptographic Service Providers ........................................................................... 1356

22.2 Der Sandkasten (Sandbox) ....................................................................................................... 1357 22.3 Sicherheitsmanager (Security Manager) .............................................................................. 1358 22.3.1

Der Sicherheitsmanager bei Applets .................................................................... 1359

22.3.2

Sicherheitsmanager aktivieren .............................................................................. 1361

22.3.3

Rechte durch Policy-Dateien vergeben ............................................................... 1363

22.3.4

Erstellen von Rechtedateien mit dem grafischen Policy-Tool ..................... 1365

22.3.5

Kritik an den Policies ................................................................................................ 1366

22.4 Signierung ..................................................................................................................................... 1368 22.4.1

Warum signieren? ...................................................................................................... 1368

22.4.2

Digitale Ausweise und die Zertifizierungsstelle ............................................... 1368

22.4.3

Mit keytool Schlüssel erzeugen ............................................................................. 1369

22.4.4

Signieren mit jarsigner ............................................................................................. 1370

22.5 Digitale Unterschriften * ........................................................................................................... 1371 22.5.1

Die MDx-Reihe ............................................................................................................ 1371

22.5.2

Secure Hash Algorithm (SHA) ................................................................................ 1372

22.5.3

Mit der Security-API einen Fingerabdruck berechnen ................................... 1372

22.5.4

Die Klasse MessageDigest ........................................................................................ 1373

22.6 Verschlüsseln von Daten(-strömen) * .................................................................................... 1375 22.6.1

Den Schlüssel, bitte .................................................................................................... 1375

22.6.2

Verschlüsseln mit Cipher ......................................................................................... 1377

22.6.3

Verschlüsseln von Datenströmen ......................................................................... 1377

22.7 Zum Weiterlesen ......................................................................................................................... 1379

23 Dienstprogramme für die Java-Umgebung 23.1 Programme des JDK .................................................................................................................... 1381 23.2 Monitoringprogramme vom JDK ............................................................................................ 1381 23.2.1

jps .................................................................................................................................... 1381

23.2.2

jstat ................................................................................................................................. 1382

23.2.3

jmap ................................................................................................................................ 1382

33

Inhalt

23.2.4

jstack ..............................................................................................................................

1383

23.2.5

VisualVM ......................................................................................................................

1385

23.3 Programmieren mit der Tools-API .........................................................................................

1390

23.3.1

Eigene Doclets .............................................................................................................

23.4 Ant ..................................................................................................................................................

1390 1393

23.4.1

Bezug und Installation von Ant ............................................................................

1393

23.4.2

Das Build-Skript build.xml .....................................................................................

1394

23.4.3

Build den Build ...........................................................................................................

1394

23.4.4

Properties .....................................................................................................................

1395

23.4.5

Externe und vordefinierte Properties .................................................................

1396

23.4.6

Weitere Ant-Tasks ......................................................................................................

1397

23.5 Disassembler, Decompiler und Obfuscator .........................................................................

1398

23.5.1

Der Diassembler javap .............................................................................................

1399

23.5.2

Decompiler ..................................................................................................................

1404

23.5.3

Obfuscatoren ...............................................................................................................

1406

23.6 Weitere Dienstprogramme ......................................................................................................

1408

23.6.1

Sourcecode Beautifier ..............................................................................................

1408

23.6.2

Java-Programme als Systemdienst ausführen .................................................

1409

23.7 Zum Weiterlesen ........................................................................................................................

1410

Index ............................................................................................................................................................

1411

34

Vorwort »Alles, was einen Anfang hat, hat auch ein Ende, und meistens hat das, was ein Ende hat, auch eine Fortsetzung.« – Sprichwort

Willkommen im zweiten Teil der Insel. Während der erste Band in die Grundlagen von Java einführte und sich dabei insbesondere auf die objektorientierten Konzepte und die Syntax der Sprache Java konzentrierte, kümmert sich der zweite Band um die Bibliotheken aus der Java SE.

Organisation der Kapitel Den Einsteig ins Buch bildet Kapitel 1, »Neues in Java 7«, mit einer kompakten Darstellung der Neuerungen. Die Änderungen in den Bibliotheken fließen dabei in die jeweiligen Kapitel ein; so konzentriert sich das kurze Kapitel eher auf die neuen Spracheigenschaften. Kapitel 2 beschäftigt sich mit »Threads und nebenläufiger Programmierung«. Das Kapitel

umfasst auch die Koordination mehrerer kooperierender oder konkurrierender Threads. Seit Java 5 ist ein neues java.util.concurrent-Paket hinzugekommen, das die Programmierung nebenläufiger Programme gegenüber Java 1.0 sehr vereinfacht – dieses neue Paket bildet die Grundlage des Kapitels. Kapitel 3 beschäftigt sich mit den »Datenstrukturen und Algorithmen«, die die Standard-

bibliothek anbietet. Die wichtigsten Klassen wie Listen, Mengen, Stapel, Bitmengen und Assoziativspeicher werden vorgestellt, und dann werden unterschiedliche Aufgaben mit den jeweils passenden Datenstrukturen gelöst. Als Algorithmen kommen beispielsweise vorgefertigte Sortierverfahren zum Einsatz. Das Kapitel diskutiert ebenfalls für Nebenläufigkeit optimierte Datenstrukturen. Zeitzonen und unterschiedliche Ausgabeformate für Datumswerte sind Thema in Kapitel 4, »Raum und Zeit«. Darunter fallen auch Datumsberechnungen auf der Grundlage des gregorianischen Kalenders. Interessant sind Java-Möglichkeiten zur Internationalisierung, was sich durchweg durch alle APIs zieht, die in irgendeiner Weise Nachrichten zum Benutzer geben.

35

Vorwort

Das Kapitel 5, »Dateien, Verzeichnisse und Dateizugriffe«, setzt den Fokus auf Dateiverarbeitung. Zuerst zeigen wir, wie sich Attribute von Dateien und Verzeichnissen auslesen lassen, und dann, wie sich wahlfreier Zugriff auf eine Datei realisieren lässt. Das in Java 7 eingeführte NIO.2-Paket nimmt dabei einen großen Raum ein, da es sehr leistungsfähig ist. Nahtlos folgt in Kapitel 6, »Datenströme«, die Verarbeitung von Daten aus beliebigen Quellen und Senken. Während sich Kapitel 5 nur auf Dateien beschränkt, geht es in diesem Kapitel um allgemeinere Ein-/Ausgabe-Konzepte, die auch bei Datenströmen aus Netzwerken, Datenbanken oder Schnittstellen vorkommen. Die Datenströme können dabei durch Filter geschickt werden. Von Letzteren stellen wir einige vor, die zum Beispiel die Zeilennummer zählen, einen Datenstrom puffern oder ihn komprimieren. Eine elegante Möglichkeit ist das Serialisieren von Objekten. Dabei wird der Zustand eines Objekts ausgelesen und so in einen Datenstrom geschrieben, dass sich das Objekt später wiederherstellen lässt. Eine eigene Speicherroutine kann somit entfallen. Ein neues Thema spannt Kapitel 7 mit »Die eXtensible Markup Language (XML)« auf. Java als plattformunabhängige Programmiersprache und XML als dokumentenunabhängige Beschreibungssprache sind ein ideales Paar, und die Kombination dieser beiden Technologien ist der Renner der letzten Jahre. Das Kapitel beginnt auf der höchsten Abstraktionsebene, beschreibt, wie XML-Daten auf Objekte übertragen werden, und geht dann weiter zur elementaren Verarbeitung der XML-Dokumente, wie sie zum Beispiel als Objekt-Baum aufgebaut und modifiziert werden. Heutzutage ist die Speicherung in XML populär, und auch Office-Dokumente werden in diesem Stil gespeichert. Doch keiner würde auf die Idee kommen, die Dokumente im XML-Format auszulesen und dort die Absätze und Zeichen herauszuziehen. Das Gleiche gilt für Grafikdateien: Es ist zwar eine Byte-Datei, doch die wird nicht als Byte für Byte von uns eingelesen, sondern es gibt immer eine API, die sich darum kümmert, Zeichen oder Bytes in ein für uns komfortables Format zu bringen. Ein paar populäre Dateiformate stellt Kapitel Kapitel 8, »Dateiformate«, vor. Zu den beschriebenen Formaten zählen nicht nur Dokumentenformate, wie für Microsoft Office, sondern auch Formate für die Kompression. Kapitel 9, »Grafische Oberflächen mit Swing«, stellt die Swing-Komponenten zur Interaktion

vor, wie zum Beispiel Schaltflächen. Es geht auf die Behandlung von Ereignissen ein, die aus Benutzeraktionen resultieren, und beschreibt Container, die andere Komponenten aufnehmen und layouten. Das anschließende Kapitel 10 deckt die zweite Aufgabe der grafischen Oberflächen ab, indem es auf die »Grafikprogrammierung« eingeht. Das AWT (Abstract Window Toolkit) ist die JavaMöglichkeit, grafische Oberflächen zu gestalten. Dabei gliedert es sich in zwei große Teile: zum einen in die direkte Ausgabe von Grafik-Primitiven wie Linien, und zum anderen in

36

Vorwort

Komponenten für grafische Oberflächen. Das Kapitel behandelt die Themen Fenster, Zeichenketten und Zeichensätze, Farben und Bilder. Kapitel 11, »Netzwerkprogrammierung«, stellt vor, welche Informationen eine URL hat und

wie mit dieser URL Daten von Webservern bezogen werden können. Bei Webservern werden wir CGI-Programme ansprechen, um an gewünschte Inhalte zu kommen. Mithilfe von Sockets wollen wir eine eigene Client-Server-Kommunikation aufbauen. Außer auf die gesicherte Verbindung TCP gehen wir auch auf ungesicherte UDP-Verbindungen ein. »Verteilte Programmierung mit RMI« in Kapitel 12 zeigt auf, wie ein Java-Programm einfach Objekte und Methoden nutzen kann, die auf einem anderen Rechner gespeichert beziehungsweise ausgeführt werden. Dabei wird der Aufruf einer Methode über das Netzwerk übertragen, und für das aufrufende Programm sieht es so aus, als ob es sich um einen normalen Methodenaufruf für ein lokales Objekt handelt. Kapitel 13, »RESTful und SOAP Web-Services«, stellt mit REST einen neuen Trend in der Kom-

munikation zwischen Browser und Web-Server vor und mit SOAP einen Klassiker für den entfernten Methodenaufruf zwischen Systemen. SOAP ist Teil der Java SE, und für REST wird Jersey vorgestellt, die Referenzimplementierung der JAX-RS-API. Mit Kapitel 14, »JavaServer Pages und Servlets«, geht es dann in die Welt der dynamischen Webseiten. Java ist zurzeit auf der Serverseite sehr populär, und dort besonders beim sogenannten Enterprise-Computing. Mit JavaServer Pages ist es besonders einfach, dynamische Webinhalte zu formulieren und auf die vom Client mitgeschickten Informationen sehr einfach zuzugreifen. JSPs verfügen zudem über die gesamten Java-Möglichkeiten, insbesondere die Fähigkeit zur Datenbankanbindung. Grafische Oberflächen und Netzwerke verbinden das Kapitel 15 zum Thema »Applets«. Applets sind Java-Programme innerhalb eines Webbrowsers. Applets machten Java vor über 10 Jahren auf der Welt bekannt, und die ersten Java-Bücher erklärten alles im Zusammenhang mit Applets. Heutzutage spielen Applets keine große Rolle mehr – auch durch die Verbreitung von Flash – und sind schon fast zu einer Nischentechnologie geworden. »Datenbankmanagement mit JDBC« ist das Thema von Kapitel 16. Als freie, quelloffene Beispieldatenbank wird HSQLDB vorgestellt, da sie sehr leicht zu installieren und zu betreiben ist und praktischerweise Beispieldaten mitbringt. Die Java-Beispiele bauen eine Verbindung zu HSQLDB auf, setzen SQL-Anweisungen ab, holen die Ergebnisse herein und visualisieren sie. Viele der vorgestellen Technologien sind sehr speziell, doch Kapitel 17, »Technologien für die Infrastruktur«, geht auf den Aufbau großer Anwendungen ein und stellt Basistechnologien vor, die ein gutes Fundament für eigene Anwendungen bilden und in jeder Architektur zu finden sein dürften, ob es nun grafische Anwendungen sind oder reine Serveranwendungen.

37

Vorwort

Mit Kapitel 18 widmen wir uns einer Java-typischen Technik: »Reflection und Annotationen«. Java-Klassen liegen selbst wieder als Meta-Objekte, als Exemplare der speziellen Klasse Class, vor. Diese Class-Objekte geben Auskunft über die verfügbaren und definierten Variablen, Methoden und Konstruktoren. So lassen sich beispielsweise dynamisch bestimmte Methoden aufrufen oder die Werte von dynamisch ausgewählten Objektvariablen abfragen. Die Annotationen sind eine bedeutende Neuerung in Java 5. Am Anfang war die JVM fest mit der Programmiersprache Java verbunden, doch das änderte sich im Laufe der Jahre. Heute gibt es unterschiedliche Programmiersprachen, die auf einer JVM laufen – von ganz entwickelten Skriptsprachen bis zu portierten. Kapitel 19, »Dynamische Übersetzung und Skriptsprachen«, stellt Skriptsprachen vor und auch die Möglichkeit, wie eigene Klassen mit der Compiler-API übersetzt werden. Wie Java-Programme zu Testzwecken überwacht werden können, zeigt Kapitel 20, »Logging und Monitoring«. Mit der JMX-API lassen sich MBeans an einem MBean-Server anmelden, und das Dienstprogramm jconsole ermöglicht den Zugriff und die Steuerung der Komponenten. Java kann nicht alles plattformunabhängig lösen. An einer bestimmen Stelle muss plattformabhängiger Programmcode eingebaut werden. Um von Java auf Nicht-Java-Code wie C(++) zuzugreifen, definiert Oracle das in Kapitel 21 vorgestellte »Java Native Interface (JNI)«. Kapitel 22 zeigt kurz »Sicherheitskonzepte«, etwa das Sandkastenprinzip, und Sicherheitsma-

nager auf, zeigt aber auch, wie von Daten eine Prüfsumme gebildet werden kann und wie Daten mit DES oder RSA verschlüsselt werden. In Kapitel 23, »Dienstprogramme für die Java-Umgebung«, geht es um die zum JDK gehörigen Programme und um einige Extratools, die für Ihre Arbeit nützlich sind. Im Mittelpunkt stehen Compiler, Interpreter und die Handhabung von Jar-Archiven. Dieses Archivformat ist vergleichbar mit den bekannten Zip-Archiven und fasst mehrere Dateien zusammen. Mit den eingebetteten Dokumentationskommentaren in Java kann aus einer Quellcodedatei ganz einfach eine komplette HTML-Dokumentation der Klassen, Schnittstellen, Vererbungsbeziehungen und Eigenschaften inklusive Verlinkung erstellt werden. Unter den Programmen, die zu keiner Standardinstallation gehören, sind etwa Tools, die Java-Programme in C-Programme übersetzen, sie neu einrücken und formatieren und Bytecode wieder in lesbaren Java-Quellcode umwandeln.

Vorwort zur 1. Auflage Es ist unverkennbar, dass dieses Buch seinen Ursprung in »Java ist auch eine Insel« hat. Es enthält alle fortgeschrittenen Java-Konzepte der vorangegangenen Insel. Bei dieser Aufspaltung ist es natürlich nicht geblieben, denn sonst wäre das Buch ja auch nur 700 Seiten dick und nicht 1400. Es gab also Änderungen und Neuerungen gegenüber diesen Kapiteln in der alten Insel.

38

Vorwort

Zu den Neuerungen: Natürlich ist das Buch auf der Höhe der Zeit und beschreibt die aktuellen Java 7-Features. Das erste Kapitel fasst die bescheidenen Sprachänderungen zusammen. Aufseiten der Bibliothek gibt es zwei größere Zuwächse: NIO.2 und das kleinere Fork-And-JoinFramework. Swing bietet eine neue Klasse namens JLayer und bekommt kleine Updates, etwa durch eine generische Typvariable etwa bei ListModel oder JComboBox. Ein weiteres neues Kapitel behandelt dynamische Übersetzung mit dem Java-Compiler zur Laufzeit und Skriptsprachen, wobei es einige Beispiele zur beliebten JVM-Sprache Groovy gibt. Und da Web-Services nicht mehr nur durch SOAP realisiert werden, gibt es ein Unterkapitel zum REST-Prinzip und JAW-RS. Im Swing-Kapitel ist eine bildgeführte Anleitung zu finden, wie sich mit NetBeans einfach grafische Oberflächen entwickeln lassen. Am Ende des Swing-Kapitels befindet sich ein Ausblick auf die freie Komponentenbibliothek SwingX, mit der Entwickler die GUIs aufhübschen können. Das Datenbankkapitel hat einen Ausblick auf JPA bekommen, wobei die Beispiele hier auch wieder mit NetBeans realisiert werden. Zu den Aktualisierungen: Auch ohne die Anpassungen aus Java 7 gibt es Verbesserungen und Aktualisierungen. Im Tooling-Bereich ist der Decompiler Jad durch JD ersetzt worden. Gleichzeitig gibt das gleiche Kapitel eine Einführung in Java-Bytecode und das Tool javap. Weiterhin im Bereich Datenbanktools ist eine kleine Änderung nötig geworden, da der Datenbankbrowser in die Eclipse Data Tools Platform (DTP) gewandert ist und sich auch die grafische Oberfläche veränderte. Im Bereich Java ME ist das Plugin zu Eclipse gewandert und trägt jetzt den neuen Namen Eclipse Mobile Tools for Java (MTJ).

Christian Ullenboom

Sonsbeck, im Oktober 2011

39

Kapitel 1

1

Neues in Java 7 »Jede Lösung eines Problems ist ein neues Problem.« – Johann Wolfgang von Goethe (1749–1832)

1.1

Sprachänderungen

Die folgenden Abschnitte stellen Sprachänderungen aus Java 7 vor.

1.1.1

Zahlen im Binärsystem schreiben

Die Literale für Ganzzahlen lassen sich in vier unterschiedlichen Stellenwertsystemen angeben. Das natürlichste ist das Dezimalsystem (auch Zehnersystem genannt), bei dem die Literale aus den Ziffern »0« bis »9« bestehen. Zusätzlich existieren Oktal- und Hexadezimalsysteme, und seit Java 7 die Binärschreibweise. Bis auf Dezimalzahlen beginnen die Zahlen in anderen Formaten mit einem besonderen Präfix. Präfix

Stellenwertsystem

Basis

Darstellung von 1

0b oder 0B

Binär

2

0b1 oder 0B1

0

Oktal

8

01

Kein

Dezimal

10

1

0x oder 0X

Hexadezimal

16

0x1 oder 0X1

Tabelle 1.1: Die Stellenwertsysteme und ihre Schreibweise

1.1.2 Unterstriche bei Ganzzahlen Um eine Anzahl von Millisekunden in Tage zu konvertieren, muss einfach eine Division vorgenommen werden. Um Millisekunden in Sekunden umzurechnen, brauchen wir eine Division durch 1000, von Sekunden auf Minuten eine Division durch 60, von Minuten auf Stun-

41

1

Neues in Java 7

den eine Division durch 60, und die Stunden auf Tage bringt die letzte Division durch 24. Schreiben wir das auf: long millis = 10 * 24*60*60*1000L; long days = millis / 86400000L; System.out.println( days );

// 10

Eine Sache fällt bei der Zahl 86400000 auf: Besonders gut lesbar ist sie nicht. Die eine Lösung ist, es erst gar nicht zu so einer Zahl kommen zu lassen und sie wie in der ersten Zeile durch eine Reihe von Multiplikationen aufzubauen – mehr Laufzeit kostet das nicht, da dieser konstante Ausdruck zur Übersetzungszeit feststeht. Die zweite Variante ist eine neue Schreibweise, die Java 7 einführt: Unterstriche in Zahlen. Anstatt ein numerisches Literal als 86400000 zu schreiben, ist in Java 7 auch Folgendes erlaubt: long millis = 10 * 86_400_000L; long days = millis / 86_400_000L; System.out.println( days );

// 10

Die Unterstriche machen die 1000er-Blöcke gut sichtbar. Hilfreich ist die Schreibweise auch bei Literalen in Binär- und Hexdarstellung, da die Unterstriche hier ebenfalls Blöcke absetzen können.1

Beispiel Unterstriche verdeutlichen Blöcke bei Binär- und Hexadezimalzahlen. int i = 0b01101001_01001101_11100101_01011110; long l = 0x7fff_ffff_ffff_ffffL;

Der Unterstrich darf in jedem Literal stehen, zwei aufeinanderfolgende Unterstriche sind aber nicht erlaubt.

1.1.3 switch (String) Seit Java 7 sind switch-Anweisungen auf String-Objekten möglich. String input = javax.swing.JOptionPane.showInputDialog( "Eingabe" ); switch ( input.toLowerCase() ) 1

42

Bei Umrechnungen zwischen Stunden, Minuten und so weiter hilft auch die Klasse TimeUnit mit einigen statischen toXXX()-Methoden.

1.1

Sprachänderungen

{

1 case "kekse": System.out.println( "Ich mag Keeeekse" ); break; case "kuchen": System.out.println( "Ich mag Kuchen" ); break; case "scholokade": case "lakritze": System.out.println( "Hm. lecker" ); break; default: System.out.printf( "Kann man %s essen?", input );

}

Obwohl Zeichenkettenvergleiche nun möglich sind, fallen Überprüfungen auf reguläre Ausdrücke leider heraus, die insbesondere Skriptsprachen anbieten. Wie auch beim switch mit Ganzzahlen können die Zeichenketten beim String-case-Zweig aus finalen (also nicht änderbaren) Variablen stammen. Ist etwa String KEKSE = "kekse"; vordefiniert, ist case KEKSE: erlaubt.

1.1.4 Zusammenfassen gleicher catch-Blöcke mit dem multi-catch Greift ein Programm auf Teile zurück, die scheitern können, so ergeben sich in komplexeren Abläufen schnell Situationen, in denen unterschiedliche Ausnahmen auftreten können. Entwickler sollten versuchen, den Programmcode in einem try-Block durchzuschreiben, und dann in catch-Blöcken auf alle möglichen Fehler zu reagieren, die den Block vom korrekten Durchlaufen abgehalten haben. Oftmals kommt es zu dem Phänomen, dass die aufgerufenen Programmteile unterschiedliche Ausnahmetypen auslösen, aber die Behandlung der Fehler gleich aussieht. Um Quellcodeduplizierung zu vermeiden, sollte der Programmcode zusammengefasst werden. Nehmen wir an, die Behandlung der Ausnahmen SQLException und IOException soll gleich sein. Wir haben schon gesehen, dass ein catch(Exception e) keine gute Lösung ist und nie im Programmcode vorkommen sollte, denn dann würden auch andere Ausnahmen mitgefangen. Zum Glück gibt es in Java 7 eine elegante Lösung.

43

1

Neues in Java 7

Multi-catch Java 7 führt eine neue Schreibweise für catch-Anweisungen ein, um mehrere Ausnahmen auf einmal aufzufangen; sie heißt multi-catch. In der abgewandelten Variante von catch steht dann nicht mehr nur eine Ausnahme, sondern eine Sammlung von Ausnahmen, die ein »|« trennt. Der Schrägstrich ist schon als Oder-Operator bekannt und wurde daher auch hier eingesetzt, denn die Ausnahmen sind ja auch als eine Oder-Verknüpfung zu verstehen. Die allgemeine Syntax ist: try { ... } catch ( E1 | E2 | ... | En exception )

Die Variable exception ist implizit final. Um das multi-catch zu demonstrieren, nehmen wir ein Programm an, das eine Farbtabelle einliest. Die Datei besteht aus mehreren Zeilen, wobei in jeder Zeile die erste Zahl einen Index repräsentiert und die zweite Zahl den hexadezimalen RGB-Farbwert. Listing 1.1: basiscolors.txt 0 000000 1 ff0000 8 00ff00 9 ffff00

Eine eigene Methode readColorTable() soll die Datei einlesen und ein int-Feld der Größe 256 als Rückgabe liefern, wobei an den in der Datei angegebeen Positionen jeweils die Farbwerte eingetragen sind. Nicht belegte Positionen bleiben 0. Gibt es einen Ladefehler, soll die Rückgabe null sein und die Methode eine Meldung auf dem Fehlerausgabekanal ausgeben. Das Einlesen soll die Scanner-Klasse übernehmen. Bei der Verarbeitung der Daten und der Füllung des Feldes sind diverse Fehler denkbar: Þ IOException: Die Datei ist nicht vorhanden, oder während des Einlesens kommt es zu Pro-

blemen. Þ InputMismatchException: Der Index oder die Hexadezimalzahl sind keine Zahlen (einmal zur

Basis 10, und dann zur Basis 16). Den Fehlertyp löst der Scanner aus. Þ ArrayIndexOutOfBoundsException: Der Index liegt nicht im Bereich von 0 bis 255.

Während der erste Fehler beim Dateisystem zu suchen ist, sind die zwei unteren Fehler – unabhängig davon, dass sie ungeprüfte Ausnahmen sind – auf ein fehlerhaftes Format zurück-

44

1.1

Sprachänderungen

zuführen. Die Behandlung soll immer gleich aussehen und kann daher gut in einem multicatch zusammengefasst werden. Daraus folgt: Listing 1.2: ReadColorTable.java import java.io.*; import java.util.Scanner; public class ReadColorTable { private static int[] readColorTable( String filename ) { Scanner input; int[] colors = new int[ 256 ]; try { input = new Scanner( new File(filename) ); while ( input.hasNextLine() ) { int index = input.nextInt(); int rgb

= input.nextInt( 16 );

colors[ index ] = rgb; } return colors; } catch ( IOException e ) { System.err.printf( "Dateioperationen fehlgeschlagen%n%s%n", e ); } catch ( InputMismatchException | ArrayIndexOutOfBoundsException e ) { System.err.printf( "Datenformat falsch%n%s%n", e ); } finally { input.close(); } return null; }

45

1

1

Neues in Java 7

public static void main( String[] args ) { readColorTable( "basiscolors.txt" ); } }

Multi-catch-Blöcke sind also eine Abkürzung, und der Bytecode sieht genauso aus wie mehrere gesetzte catch-Blöcke, also wie: catch ( InputMismatchException e ) { System.err.printf( "Datenformat falsch%n%s%n", e ); } catch ( ArrayIndexOutOfBoundsException e ) { System.err.printf( "Datenformat falsch%n%s%n", e ); }

Multi-catch-Blöcke sind nur eine Abkürzung, daher teilen sie auch die Eigenschaften der normalen catch-Blöcke. Der Compiler führt die gleichen Prüfungen wie bisher durch, also ob etwa die genannten Ausnahmen im try-Block überhaupt ausgelöst werden können. Nur das, was in der durch »|« getrennten Liste aufgezählt ist, wird behandelt; unser Programm fängt zum Beispiel nicht generisch alle RuntimeExceptions ab. Und genauso dürfen die in catch oder multicatch genannten Ausnahmen nicht in einem anderen (multi)-catch auftauchen. Neben den Standard-Tests kommen neue Überprüfungen hinzu, ob etwa die exakt gleiche Exception zweimal in der Liste ist oder ob es Widersprüche durch Mengenbeziehungen gibt.

Hinweis Der folgende multi-catch ist falsch: try { new RandomAccessFile("", ""); } catch ( FileNotFoundException | IOException | Exception e ) { }

Der javac-Compiler meldet einen Fehler der Art »Alternatives in a multi-catch statement cannot be related by subclassing« und bricht ab.

46

1.1

Sprachänderungen

1

Hinweis (Forts.) Mengenprüfungen führt der Compiler auch ohne multi-catch durch, und Folgendes ist ebenfalls falsch: try { new RandomAccessFile("", ""); } catch ( Exception e ) { } catch ( IOException e ) { } catch ( FileNotFoundException e ) { }

Während allerdings eine Umsortierung der Zeilen die Fehler korrigiert, spielt die Reihenfolge bei multi-catch keine Rolle.

1.1.5 Präzisiertes rethrows Die Notwendigkeit, Ausnahmen über einen Basistyp zu fangen, ist mit dem Einzug vom multi-catch gesunken. Doch für gewisse Programmteile ist es immer noch praktisch, alle Fehler eines gewissen Typs aufzufangen. Wir können auch so weit in der Ausnahmehierarchie nach oben laufen, um alle Fehler aufzufangen – dann haben wir es mit einem try { ... } catch(Throwable t){ ... } zu tun. Ein multi-catch ist für geprüfte Ausnahmen besonders gut, aber bei ungeprüften Ausnahmen ist eben nicht immer klar, was als Fehler denn so ausgelöst wird, und ein catch(Throwable t) hat den Vorteil, dass es alles wegfischt.

Problemstellung Werden Ausnahmen über einen Basistyp gefangen und wird diese Ausnahme mit throw weitergeleitet, dann ist es naheliegend, dass der aufgefangene Typ genau der Typ ist, der auch bei throws in der Methodensignatur stehen muss. Stellen wir uns vor, ein Programmblock nimmt einen Screenshot und speichert ihn in einer Datei. Kommt es beim Abspeichern zu einem Fehler, soll das, was vielleicht schon in die Datei geschrieben wurde, gelöscht werden; die Regel ist also: Entweder steht der Screenshot komplett in der Datei oder es gibt gar keine Datei. Die Methode kann so aussehen, wobei die Ausnahmen an den Aufrufer weitergegeben werden sollen: public static void saveScreenshot( String filename ) throws AWTException, IOException { try { Rectangle r = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );

47

1

Neues in Java 7

BufferedImage screenshot = new Robot().createScreenCapture( r ); ImageIO.write( screenshot, "png", new File( filename ) ); } catch ( AWTException e ) { throw e; } catch ( IOException e ) { new File( filename ).delete(); throw e; } }

Mit den beiden catch-Blöcken sind wir genau auf die Ausnahmen eingegangen, die createScreenCapture() und write() auslösen. Das ist richtig, aber löschen wir wirklich immer die Dateireste, wenn es Probleme beim Schreiben gibt? Richtig ist, dass wir immer dann die Datei löschen, wenn es zu einer IOException kommt. Aber was passiert, wenn die Implementierung eine RuntimeException auslöst? Dann wird die Datei nicht gelöscht, aber das ist gefragt! Das scheint einfach gefixt, denn statt catch ( IOException e ) { new File( filename ).delete(); throw e; }

schreiben wir: catch ( Throwable e ) { new File( filename ).delete(); throw e; }

Doch können wir das Problem so lösen? Der Typ Throwable passt doch gar nicht mehr mit dem deklarierten Typ IOException in der Methodensignatur zusammen: public static void saveScreenshot( String filename ) throws AWTException /*1*/, IOException /*2*/ {

48

1.1

Sprachänderungen

...

1

catch ( AWTException /*1*/ e ) { throw e; } catch ( Throwable /*?*/ e ) { new File( filename ).delete(); throw e; } }

Die erste catch-Klausel fängt AWTException und leitet es weiter. Damit wird saveScreenshot() zum möglichen Auslöser von AWTException und die Ausnahme muss mit throws an die Signatur. Wenn nun ein catch-Block jedes Throwable auffängt und diesen Throwable-Fehler weiterleitet, ist zu erwarten, dass an der Signatur auch Throwable stehen muss und IOException nicht reicht. Das war auch bis Java 6 so, aber in Java 7 kam eine Anpassung.

Neu seit Java 7: eine präzisere Typprüfung In Java 7 hat der Compiler eine kleine Veränderung erfahren, von einer unpräziseren zu einer präziseren Typanalyse: Immer dann, wenn in einem catch-Block ein throw stattfindet, ermittelt der Compiler die im try-Block tatsächlich aufgetretenen geprüften Exception-Typen und schenkt dem im catch genannten Typ für das rethrow im Prinzip keine Beachtung. Statt dem gefangenen Typ wird der Compiler den durch die Codeanalyse gefundenen Typ beim rethrow melden. Der Compiler erlaubt nur dann das präzise rethrow, wenn die catch-Variable nicht verändert wird. Zwar ist eine Veränderung einer nicht-finalen catch-Variablen wie auch unter Java 1.0 erlaubt, doch wenn die Variable belegt wird, schaltet der Compiler von der präzisen in die unpräzise Erkennung zurück. Führen wir etwa die folgende Zuweisung ein, so funktioniert das Ganze schon nicht mehr: catch ( Throwable e ) { new File( filename ).delete(); e = new IllegalStateException(); throw e; }

49

1

Neues in Java 7

Die Zuweisung führt zu dem Compilerhinweis, dass jetzt auch Throwable mit in die throwsKlausel muss. 2

Stilfrage Die catch-Variable kann für die präzisere Typprüfung den Modifizierer final tragen, muss das aber nicht tun. Immer dann, wenn es keine Veränderung an der Variablen gibt, wird der Compiler sie als final betrachten und eine präzisere Typprüfung durchführen – daher nennt sich das auch effektiv final. Die Java Language Specification rät vom final-Modifizierer aus Stilgründen ab. Ab Java 7 ist es das Standardverhalten, und daher ist es Quatsch, überall ein final dazuzuschreiben, um die präzisere Typprüfung zu dokumentieren.

Migrationsdetail Da der Compiler nun mehr Typwissen hat, stellt sich die Frage, ob alter Programmode mit dem neuen präziseren Verhalten vielleicht ungültig werden könnte. Theoretisch ist das möglich, aber die Sprachdesigner haben in über 9 Millionen Zeilen Code2 von unterschiedlichen Projekten keine Probleme gefunden. Prinzipiell könnte der Compiler jetzt unerreichbaren Code finden, der vorher versteckt blieb. Ein kleines Beispiel, was vor Java 7 compiliert, aber ab Java 7 nicht mehr: try { throw new FileNotFoundException(); } catch ( IOException e ) { try { throw e;

// e ist für den Compiler vom Typ FileNotFoundException

} catch ( MalformedURLException f ) { } }

Die Variable e in catch (IOException e) ist effektiv final, und der Compiler führt die präzisere Typerkennung durch. Er findet heraus, dass der wahre rethrow-Typ nicht IOException, sondern FileNotFoundException ist. Wenn dieser Typ dann mit throw e weitergeleitet wird, kann ihn catch(MalformedURLException) nicht auffangen.

2

50

Die Zahl stammt aus der FOSDEM 2011-Präsentation »Project Coin: Language Evolution in the Open«.

1.1

Sprachänderungen

1

Migrationsdetail (Forts.) Unter Java 6 war das etwas anders, denn hier wusste der Compiler nur, dass e irgendeine IOException ist, und es hätte ja durchaus die IOException-Unterklasse MalformedURLException sein können. (Warum MalformedURLException aber eine Unterklasse von IOException ist, steht auf einem ganz anderen Blatt.)

1.1.6 Automatisches Ressourcen-Management (try mit Ressourcen) Java hat eine automatische Garbage-Collection (GC), sodass nicht mehr referenzierte Objekte erkannt und ihr Speicher automatisch freigegeben wird. Nun bezieht sich die GC aber ausschließlich auf Speicher, doch es gibt viele weitere Ressourcen: Þ Dateisystem-Ressourcen von Dateien Þ Netzwerkressourcen wie Socket-Verbindungen Þ Datenbankverbindungen Þ nativ gebundene Ressourcen vom Grafiksubsystem Þ Synchronisationsobjekte

Auch hier gilt es, nach getaner Arbeit aufzuräumen und Ressourcen freizugeben, etwa Dateien und Datenbankverbindungen zu schließen. Mit dem try-catch-finally-Konstrukt haben wir gesehen, wie Ressourcen freizugeben sind. Doch es lässt sich auch ablesen, dass relativ viel Quellcode geschrieben werden muss und der try-catch-finally drei Unfeinheiten hat: 1. Sollte eine Variable in finally zugänglich sein, muss sie außerhalb des try-Blocks deklariert werden, was ihr eine höhere Sichtbarkeit als nötig gibt. 2. Das Schließen der Ressourcen bringt oft ein zusätzliches try-catch mit sich. 3. Eine im finally ausgelöste Ausnahme (etwa beim close()) überdeckt die im try-Block ausgelöste Ausnahme.

1.1.7 try mit Ressourcen Um das Schließen von Ressourcen zu vereinfachen, wurde in Java 7 eine besondere Form der try-Anweisung eingeführt, die try mit Ressourcen genannt werden. Mit ihm lassen sich Ressource-Typen, die die Schnittstelle java.lang.AutoCloseable implementieren, automatisch schließen. Ein-/Ausgabeklassen wie Scanner, InputStream und Writer, implementieren diese Schnittstelle und können direkt verwendet werden. Weil try mit Ressourcen dem Automatic Resource Management dient, heißt der spezielle try-Block auch ARM-Block.

51

1

Neues in Java 7

Gehen wir in die Praxis: Aus einer Datei soll mit einem Scanner die erste Zeile gelesen und ausgegeben werden. Nach dem Lesen soll der Scanner geschlossen werden. Die linke Seite der folgenden Tabelle nutzt die spezielle Syntax, die rechte Seite ist im Prinzip die Übersetzung, allerdings noch etwas vereinfacht, wie wir später genauer sehen werden. try mit Ressourcen

Vereinfachte ausgeschriebene Implementierung

InputStream in =

InputStream in =

ClassLoader.getSystemResourceAsStream(

ClassLoader.getSystemResourceAsStream( "EastOfJava.txt" );

"EastOfJava.txt" ); try ( Scanner res =

{ final Scanner res =

new Scanner( in ) )

new Scanner( file );

{ try

System.out.println(

{

res.nextLine() );

System.out.println(

}

res.nextLine() ); } finally { res.close(); } } Tabelle 1.2: Die main()-Methode von TryWithResources1 und ihre prinzipielle Umsetzung

Üblicherweise folgt nach dem Schlüsselwort try ein Block, doch try-mit-Ressourcen nutzt eine eigene spezielle Syntax. 1. Nach dem try folgt statt dem direkten {}-Block eine Anweisung in runden Klammern, und dann erst der {}-Block, also try (...) {...} statt try {...}. 2. In den runden Klammern findet man eine lokale Variablendeklaration (die Variable ist automatisch final) mit einer Zuweisung. Die Ressourcenvariable muss vom Typ AutoCloseable sein. Rechts vom Gleichheitszeichen steht ein Ausdruck, etwa ein Konstruktor- oder Methodenaufruf. Der Typ des Ausdrucks muss natürlich auch intanceof AutoCloseable sein. Die in dem try deklarierte lokale AutoCloseable-Variable ist nur in dem Block gültig und wird automatisch freigegeben, gleichgültig ob der ARM-Block korrekt durchlaufen wurde oder ob es bei der Abarbeitung zu einem Fehler kam. Der Compiler fügt alle nötigen Prüfungen ein.

52

1.1

Sprachänderungen

1.1.8 Ausnahmen vom close() bleiben bestehen

1

Unser Scanner-Beispiel hat eine Besonderheit, denn keine der Methoden löst eine geprüfte Ausnahme aus – weder getSystemResourceAsStream(), new Scanner(InputStream), nextLine() noch das close(), was try-mit-Ressourcen automatisch aufruft. Anders ist es, wenn die Ressource ein InputStream ist, denn dort deklariert die close()-Methode eine IOException. Die muss daher auch behandelt werden, wie es das folgende Beispiel zeigt: Listing 1.3: TryWithResourcesReadsLine, readFirstLine() static String readFirstLine( File file ) { try ( BufferedReader br = new BufferedReader(new FileReader(file) ) ) { return br.readLine(); } catch ( IOException e ) { e.printStackTrace(); return null; } }

Wenn try-mit-Ressourcen verwendet wird, bleibt die Ausnahme bestehen; es zaubert die Ausnahmen beim catch also nicht weg.

Hinweis Löst close() eine geprüfte Ausnahme aus, und wird diese nicht behandelt, so kommt es zum Compilerfehler. Die close()-Methode vom BufferedReader löst zum Beispiel eine IOException aus, sodass sich die folgende Methode nicht übersetzen lässt: void no() { try ( Reader r = new BufferedReader(null) ) { }

//  Compilerfehler

}

Der Ausdruck new BufferedReader(null) benötigt keine Behandlung, denn der Konstruktor löst keine Ausnahme aus. Einzig der nicht behandelte Fehler von close() führt zu »exception thrown from implicit call to close() on resource variable 'r'«.

1.1.9 Die Schnittstelle AutoCloseable Die ARM-Anweisung schließt Ressourcen vom Typ AutoCloseable. Daher wird es Zeit, sich diese Schnittstelle etwas genauer anzuschauen:

53

1

Neues in Java 7

package java.lang; public interface AutoCloseable { void close() throws Exception; }

Anders als das übliche close() ist die Ausnahme deutlich allgemeiner mit Exception angegeben; die Ein-/Ausgabe-Klassen lösen beim Misslingen immer eine IOException aus, aber jede Klasse hat eigene Ausnahmetypen: Typ

Signatur

java.io.Scanner

close() // ohne Ausnahme

javax.sound.sampled.Line

close() // ohne Ausnahme

java.io.FileInputStream

close() throws IOException

java.sql.Connection

close() throws SQLException

Tabelle 1.3: Einige Typen, die AutoCloseable implementieren

Eine Unterklasse darf die Ausnahme ja auch weglassen, das machen Klassen wie der Scanner, der keine Ausnahme weiterleitet, sondern sie intern schluckt – wenn es Ausnahmen gab, liefert sie die Scanner-Methode ioException().

AutoCloseable und Closeable Auf den ersten Blick einleuchtend wäre es, die schon existierende Schnittstelle Closeable als Typ zu nutzen. Doch das hätte Nachteile: Die close()-Methode ist mit einem throws IOException deklariert, was bei einer allgemeinen automatischen Ressourcen-Freigabe unpassend ist, wenn etwa ein Grafikobjekt bei der Freigabe eine IOException auslöst. Vielmehr ist der Weg anders herum: Closeable erweitert AutoCloseable, denn das Schließen von Ein-/Ausgabe-Ressourcen ist eine besondere Art, allgemeine Ressourcen zu schließen. package java.io; import java.io.IOException; public interface Closeable extends AutoCloseable { void close() throws IOException; }

54

1.1

Sprachänderungen

Wer ist AutoCloseable?

1

Da alle Klassen, die Closeable implementieren, auch automatisch vom Typ AutoCloseable sind, kommen schon einige Typen zusammen. Im Wesentlichen sind es aber Klassen aus dem java.io-Paket, wie Channel-, Reader-, Writer-Implementierungen, FileLock, XMLDecoder und noch ein paar Exoten wie URLClassLoader, ImageOutputStream. Auch Typen aus dem java.sql-Paket gehören zu den Nutznießern. Klassen aus dem Bereich Threading, wo etwa ein Lock wieder freigeben werden könnte, oder Grafik-Anwendungen, bei denen der Grafik-Kontext wieder freigegeben werden muss, gehören nicht dazu.

1.1.10

Mehrere Ressourcen nutzen

Unsere beiden Beispiele zeigten die Nutzung eines Ressource-Typs. Es sind aber auch mehrere Typen möglich, die dann mit einem Semikolon getrennt werden: try ( InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dest) ) { ... }

Hinweis Die Trennung erledigt ein Semikolon, und jedes Segment kann einen unterschiedlichen Typ deklarieren, etwa InputStream/OutputStream. Die Ressourcen-Typen müssen also nicht gleich sein, und auch wenn sie es sind, muss der Typ immer neu geschrieben werden, also etwa: try ( InputStream in1 = ...; InputStream in2 = ... )

Es ist ungültig, Folgendes zu schreiben: try ( InputStream in1 = ..., in2 = ... )

// Compilerfehler

Wenn es beim Anlegen in der Kette zu einem Fehler kommt, wird nur das geschlossen, was auch aufgemacht wurde. Wenn es also bei der ersten Initialisierung von in1 schon zu einer Ausnahme kommt, wird die Belegung von in2 erst gar nicht begonnen und daher auch nicht geschlossen. (Intern setzt der Compiler das als geschachtelte try-catch-finally-Blöcke um.)

Beispiel Am Schluss der Ressourcensammlung kann – muss aber nicht – ein Semikolon stehen, so wie auch bei Feldinitialisierungen zum Schluss ein Komma stehen kann: int[] array = { 1, 2, }; //

^ Komma optional

55

1

Neues in Java 7

Beispiel (Forts.) try ( InputStream in = new FileInputStream(src); ) { ... } //

^ Semikolon optional

try ( InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dest); ) { ... } //

^ Semikolon optional

Ob das stilvoll ist, muss jeder selbst entscheiden; in der Insel steht kein unnützes Zeichen.

1.1.11

Unterdrückte Ausnahmen *

Aufmerksame Leser haben bestimmt schon ein Detail wahrgenommen: Im Text steht »vereinfachte ausgeschriebene Implementierung«, was vermuten lässt, dass es ganz so einfach doch nicht ist. Das stimmt, denn es können zwei Ausnahmen auftauchen, die einiges an Sonderbehandlung benötigen: Þ Ausnahme im try-Block. An sich unproblematisch. Þ Ausnahme beim close(). Auch an sich unproblematisch. Aber es gibt mehrere close()-Auf-

rufe, wenn nicht nur eine Ressource verwendet wurde. Ungünstig. Þ Die Steigerung: Ausnahme im try-Block und dann auch noch Ausnahme(n) beim close().

Das ist ein echtes Problem! Eine Ausnahme alleine ist kein Problem, aber zwei Ausnahmen auf einmal bilden ein großes Problem, da ein Programmblock nur genau eine Ausnahme melden kann und nicht eine Sequenz von Ausnahmen. Daher sind verschiedene Fragen zu klären, falls der try-Block und close() beide eine Ausnahme auslösen: Þ Welche Ausnahme ist wichtiger? Die Ausnahme im try-Block oder die vom close()? Þ Wenn es zu zwei Ausnahmen kommt: Soll die von close() vielleicht immer verdeckt wer-

den und immer nur die vom try-Block zum Anwender kommen? Þ Wenn beide Ausnahmen wichtig sind, wie sollen sie gemeldet werden?

Wie haben sich die Java-Ingenieure entschieden? Eine Ausnahme bei close() darf bei einem gleichzeitigen Auftreten einer Exception im try-Block auf keinen Fall verschwinden.3 Wie also beide Ausnahmen melden? Hier gibt es einen Trick: Da die Ausnahme im try-Block wichtiger ist, ist sie die »Haupt-Ausnahme«, und die close()-Ausnahme kommt Huckepack als ExtraInformation mit oben drauf.

3

56

In einem frühen Prototyp war dies tatsächlich der Fall – die Ausnahme wurde komplett geschluckt.

1.1

Sprachänderungen

Dieses Verhalten soll das nächste Beispiel zeigen. Um die Ausnahmen besser steuern zu können, soll eine eigene AutoCloseable-Implementierung eine Ausnahme in close() auslösen. Listing 1.4: SuppressedClosed.java public class SuppressedClosed { public static void main( String[] args ) { class NotCloseable implements AutoCloseable { @Override public void close() { throw new UnsupportedOperationException( "close() mag ich nicht" ); } } try ( NotCloseable res = new NotCloseable() ) { throw new NullPointerException(); } } }

Das Programm löst also im close() und im try-Block eine Ausnahme aus. Das Resultat ist: Exception in thread "main" java.lang.NullPointerException at SuppressedClosed.main(SuppressedClosed.java:14) Suppressed: java.lang.UnsupportedOperationException: close() mag ich nicht at SuppressedClosed$1NotCloseable.close(SuppressedClosed.java:9) at SuppressedClosed.main(SuppressedClosed.java:15)

Die interessante Zeile beginnt mit »Suppressed:«, denn dort ist die close()-Ausnahme referenziert. An den Aufrufer kommt die spannende Ausnahme vom misslungenen try-Block aber nicht direkt von close(), sondern verpackt in der Hauptausnahme und muss extra erfragt werden. Zum Vergleich: Kommentieren wir throw new NullPointerException() aus, gibt es nur noch die close()-Ausnahme und es folgt auf der Konsole:

57

1

1

Neues in Java 7

Exception in thread "main" java.lang.UnsupportedOperationException: close() mag ich nicht at SuppressedClosed$1NotCloseable.close(SuppressedClosed.java:9) at SuppressedClosed.main(SuppressedClosed.java:15)

Die Ausnahme ist also nicht irgendwo anders untergebracht, sondern die »Hauptausnahme«. Eine Steigerung ist, dass es mehr als eine Ausnahme beim Schließen geben kann. Simulieren wir auch dies wieder an einem Beispiel, indem wir unser Beispiel um eine Zeile ergänzen: try ( NotCloseable res1 = new NotCloseable(); NotCloseable res2 = new NotCloseable() ) { throw new NullPointerException(); }

Aufgerufen führt dies zu: Exception in thread "main" java.lang.NullPointerException at SuppressedClosed.main(SuppressedClosed.java:15) Suppressed: java.lang.UnsupportedOperationException: close() mag ich nicht at SuppressedClosed$1NotCloseable.close(SuppressedClosed.java:9) at SuppressedClosed.main(SuppressedClosed.java:16) Suppressed: java.lang.UnsupportedOperationException: close() mag ich nicht at SuppressedClosed$1NotCloseable.close(SuppressedClosed.java:9) at SuppressedClosed.main(SuppressedClosed.java:16)

Jede unterdrückte close()-Ausnahme taucht auf.

Umsetzung Im Kapitel über finally wurde das Verhalten vorgestellt, dass eine Ausnahme im finally eine Ausnahme im try-Block unterdrückt. Der Compiler setzt bei der Umsetzung vom try-mit-Ressourcen das close() in einen finally-Block. Ausnahmen im finally-Block sollen eine mögliche Hauptausnahme aber nicht schlucken. Daher fängt die Umsetzung vom Compiler jede mögliche Ausnahme im try-Block ab sowie die close()-Ausnahme und hängt diese SchließAusnahme, falls vorhanden, an die Hauptausnahme.

Spezielle Methoden in Throwable * Damit eine normale Exception die unterdrückten close()-Ausnahmen Huckepack nehmen kann, sind in der Basisklasse Throwable seit Java 7 zwei Methoden hinzugekommen:

58

1.2

JVM-Änderungen

final class java.lang.Throwable

1

Þ final Throwable[] getSuppressed()

Liefert alle unterdrückten Ausnahmen. Die printStackTrace()-Methode zeigt alle unterdrückten Ausnahmen und greift auf getSuppressed() zurück. Für Anwender wird es selten Anwendungsfälle für diese Methode geben. Þ final void addSuppressed(Throwable exception)

Fügt eine neue unterdrückte Ausnahme hinzu. In der Regel ruft der finally-Block vom trymit-Ressourcen die Methode auf, doch wir können auch selbst die Methode nutzen, wenn wir mehr als eine Ausnahme melden wollen. Die Java-Bibliothek selbst nutzt das bisher nur an sehr wenigen Stellen. Neben den beiden Methoden gibt es einen protected-Konstruktor, der bestimmt, ob es überhaupt unterdrückte Ausnahmen geben soll oder ob sie nicht vielleicht komplett geschluckt werden. Wenn, dann zeigt sie auch printStackTrace() nicht mehr an.

Blick über den Tellerrand In C++ gibt es Dekonstruktoren, die beliebige Anweisungen ausführen, wenn ein Objekt freigegeben wird. Hier lässt sich auch das Schließen von Ressourcen realisieren. C# nutzt statt try das spezielle Schlüsselwort using, mit Typen, die die Schnittstelle IDisposable implementieren, mit einer Methode Dispose() statt close(). (In Java sollte die Schnittstelle ursprünglich auch Disposable statt nun AutoCloseable heißen.) In Python 2.5 wurde ein context management protocol mit dem Schlüsselwort with realisiert, sodass Python automatisch bei Betreten eines Blockes __enter__() aufruft und beim Verlassen die Methode __exit__(). Das ist insofern interessant, als dass hier zwei Methoden zur Verfügung stehen. Bei Java ist es nur close() beim Verlassen des Blockes, aber es gibt keine Methode zum Betreten eines Blockes; so etwas muss beim Anlegen der Ressource erledigt werden.

1.2 JVM-Änderungen Wenn es um Java geht, müssen wir immer unterscheiden, ob es um die Sprache selbst geht, um die Bibliotheken, um die JVM oder um die Implementierung von Oracle, die das JDK darstellt. Eine wichtige Änderung am Bytecode, und damit an der JVM selbst, ist ein neuer Bytecode zum Beschleunigen von Methoden, deren Signatur nicht bekannt ist.

59

1

Neues in Java 7

In der Implementierung von Oracle gibt es zwei große Änderungen an der JVM selbst: Þ einen neuen Garbage-Collector G1 (für Garbage Frist) Þ komprimierte 64-Bit-Objekt-Pointer, sodass sie in 32 Bit Platz finden.

Bei Java-Programmen mit DTrace (wie Solaris) lassen sich die Zustände erfragen und die Abarbeitung überwachen.

1.2.1 invokedynamic Seit Java vor über 10 Jahren auf dem Markt erschien, hat sich vieles geändert (und auch vieles ist gleich geblieben). Auffällig ist eine starke Zunahme von Skriptsprachen – spielten sie vor 10 Jahren kaum eine Rolle, sind sie heute unübersehbar. Möglicherweise ist ein Grund dafür, dass vor 10–20 Jahren compilierte Sprache einfach nicht die nötige Performance brachten, aber heute auch durch leistungsfähige Maschinen und intelligente Ausführung die Leistung einfach da ist.

Skriptsprachen auf der JVM Eine unübersehbare Tatsache ist, dass viele Skriptsprachen heute in einer virtuellen Maschine laufen. Besondere Rolle nehmen die Java-Plattform (mit der JVM) und die .NET-Plattform mit CLR (einer virtuellen Maschine für .NET) ein. Zwei Trends zeigen sich: Zum einen werden existierende Skriptsprachen auf die JVM/CLR übertragen und zum anderen Sprachen explizit für die virtuellen Maschinen entworfen. Skriptsprache

Für JVM

Für CLR

Python

Jython

IronPython

Ruby

JRuby

IronRuby

Lua

Jill, Kahlua

LuaCLR

JavaScript

Rhino

IronJS

PHP

Quercus

Tcl

Jacl Groovy JavaFX Boo

Tabelle 1.4: Skriptsprachen auf der JVM (Java) und CLR (.NET)

60

1.2

JVM-Änderungen

Umsetzung der Skriptsprachen auf der JVM

1

Eine Skriptsprache, die explizit für eine VM entworfen wurde, berücksichtigt natürlich Einschränkungen. Existierende Programmiersprachen sind eine ganz andere Herausforderung, da sie Sprach- und Laufzeitkonstrukte bieten können, die auf der JVM vielleicht nicht unterstützt werden. Das ist ein Problem, und drei Strategien zur Lösung bieten sich an: Þ Ignorieren der Eigenschaften: Python ermöglicht Mehrfachvererbung, Java nicht. Daher

kann das Verhalten von Python unter Java nicht perfekt nachgebildet werden, obwohl Jython sein Bestes gibt. Weiterhin kann Python Interrupts auffangen, aber so etwas gibt es unter Java nicht, also auch nicht in Jython. (Das ist aber eher eine Bibliotheks- und weniger eine Spracheigenschaft.) Þ Nachbilden der Eigenschaften: In Sprachen wie JavaScript, Python oder Ruby gibt es das so-

genannte Duck Typing. Zur Laufzeit wird erst das Vorhandensein von Methoden geprüft, und die Typen müssen nicht im Quellcode stehen. In JavaScript ist zum Beispiel function add(x, y){return x + y;} oder function isEmpty(s){return s == null || s.length() == 0;} erlaubt, und erst später beim Aufruf stellt sich heraus, was x und y überhaupt ist und ob ein Plus-Operator/length()-Methode definiert ist. Deklarationen und Aufrufe dieser Art sind nur mit vielen Tricks auf der JVM umzusetzen. Þ Ändern der JVM zur Unterstützung der Eigenschaften: Die Spracheigenschaften zu ignorie-

ren ist natürlich keine schöne Sache. Glücklicherweise kommt das selten vor, denn die JVM macht vieles mit. Ruby erlaubt etwa Methodennamen wie ==, , +, und 1.+2 ist gültig. Die Bezeichner sind in Java zwar nicht möglich, aber die JVM hat grundsätzlich mit den Bezeichnen keine Probleme. Es ist interessant zu sehen, wie unterschiedlich Java als Sprache und die JVM sind, denn vieles in Java gibt es in der JVM gar nicht. Enums zum Beispiel sind nur einfache Klassen. Oder dass eine Klasse ohne einen vom Entwickler explizit geschriebenen Konstruktor automatisch einen Standardkonstruktor bekommt, ist nur etwas, was der Compiler generiert. Oder ein super() als ersten Aufruf im Konstruktor – alles das sind Compilereigenschaften, und von Generics ganz zu schweigen. Wenn die Besonderheiten einer Skriptsprache über Tricks und intelligentes Übersetzen in Bytecode realisiert werden können, stellt sich die Frage, ob an der JVM überhaupt Änderungen nötig sind. Wie bei vielen Diskussionen kommt dann ein Argument auf, das Änderungen oft rechtfertigt: Performance. Wenn eine Änderung in der JVM die Abarbeitung bestimmter Konstrukte massiv beschleunigt, ist das ein Grund für die Änderung. Und somit sind wir beim neuen Bytecode invokedynamic angelangt.

Umsetzung von Duck Typing Reine Java-Programme werden den Bytecode invokedynamic nie benötigen, er ist einzig und allein in die JVM eingeführt worden, um Methodenaufrufe der Skriptsprachen auf der JVM zu

61

1

Neues in Java 7

beschleunigen. Die bisherigen Bytecodes von Methodenaufrufen trugen alle nötigen Typinformationen, so wie es bei Java üblich ist. Nehmen wir: System.out.prinln( 1 );

Der Compiler macht aus dem Programm ein invokevirtual, wobei genau bei dem Aufruf steht, dass ein println() gemeint ist, was ein int annimmt. Die Typinformationen im Bytecode sind: Þ Der Empfänger ist PrintStream, und seine Methode ist println. Þ Der Parametertyp ist ein int. Þ Die »Rückgabe« ist void.

Exakt diese Typen müssen vom Compiler in Bytecode gegossen werden, sonst kann die JVM den Aufruf nicht durchführen. Skriptsprachen hingegen sind da lascher. Nehmen wir noch einmal unser Beispiel aus JavaScript: function isEmpty(s) { return s == null || s.length() == 0; }

Die Typen wären zwar zur Laufzeit bekannt, aber eben nicht zur Compilezeit, in der der Bytecode erstellt würde. Ein Aufruf der Methode wird schwierig, denn es fehlen ganz offensichtlich der Rückgabe- und der Parametertyp. Natürlich könnten sie über einen Trick zu Object ergänzt werden. Spielen wir kurz die Konsequenzen durch: Object isEmpty( Object s )

Das sieht gut aus. Aber jetzt wird es haarig bei length(), das keine Methode von Object ist. Das Problem kann ein Compiler auf zwei Arten lösen. Zunächst kann der Aufruf an das unbekannte Objekt, das ja zur Laufzeit üblicherweise etwas anderes als String ist, über Reflection gelöst werden. Da Reflection-Aufrufe aber nicht so performant sind wie richtige Methodenaufrufe, ist diese Lösung nicht so effizient. Eine zweite Lösung besteht in der Einführung einer Schnittstelle, die genau die fehlenden Operationen deklariert: interface I { int length(); } I isEmpty( I s ) { return s == null || s.length() == 0; }

Ja, das geht – irgendwie. Das ist performanter als Reflection, aber auf diese Weise kommen eine riesige Anzahl neuer Schnittstellen bzw. Klassen in die JVM, deren Behandlung recht speicherintensiv ist. Außerdem können die Deklarationen von Methoden in Skriptsprachen auch außerhalb von Klassen vorkommen, sodass für Java ein Fake-Methoden-Empfänger erzeugt werden müsste.

62

1.3

Neue Pakete und Klassen

invokedynamic und Hilfsklassen

1

Um die sonderbare Methodendeklaration in Java performant umzusetzen, hat der JSR-292, »Supporting Dynamically Typed Languages on the Java Platform«, den neuen Bytecode invokedynamic zusammen mit einigen Hilfsklassen im Paket java.lang.invoke definiert. Der neue Bytecode muss von einer Java 7-JVM unterstützt werden und wird von dynamischen Skriptsprachen generiert, um die Aufrufe schnell von der JVM ausführen zu lassen. Normale Entwickler werden den neuen Bytecode kaum brauchen, und im Quellcode ist er eh nicht sichtbar. Weitere Details und ein Programmbeispiel finden Leser unter http://java.sun.com/developer/ technicalArticles/DynTypeLang/. Sehr tiefe Detailinformationen liefert http://blogs.oracle. com/jrose/resource/pres/200910-VMIL.pdf.

1.3 Neue Pakete und Klassen Auf einige Neuerungen geht das Buch noch gezielter ein, etwa auf NIO.2 in Kapitel 5, »Dateien, Verzeichnisse und Dateizugriffe«, oder auf die API für nebenläufige Programmierung, das Fork-und-Join-Framework in Kapitel 2, »Threads und nebenläufige Programmierung«. Da die APIs alle bibliotheksbezogen sind und dieses Buch die Java-Bibliothek in den Mittelpunkt rückt, sind die Änderungen zu Java 7 dort vermerkt – mal ist es hier eine Methode, mal da; große Überraschungen gibt es nicht. An Typen im Paket java.lang gibt es ebenfalls kleine Änderungen. Java 7 unterstützt Unicode 6, was bedeutet, dass Java 7 Konstanten für die neuen Zeichenbereiche einführt und Methoden wie isLetter() aktualisiert hat, um auch dieses neue Zeichen korrekt zu berücksichtigen. Die Klasse Character hat für die Unicode-Blöcke Konstanten bekommen. Über den Unicode 6Standard gibt http://unicode.org/versions/Unicode6.0.0/ Auskunft. Für »normale« Entwickler ist das wenig relevant. Für die üblichen Unicode-Kodierungen gibt es in StandardCharsets Konstanten.

1.3.1 Weitere Änderungen Im GIU-Bereich, das heißt bei AWT und Swing, haben sich Kleinigkeiten geändert. Fenster können auch nicht eckig und transparent sein – das ging zwar auch schon seit einer späteren Java 6-Version, aber nur inoffiziell – das heißt, mit keiner Klasse im »erlaubten« Paket. Zwar war ein Datumsauswahldialog für Java 7 in Planung, aber er verschiebt sich wohl noch bis Java 8.

63

1

Neues in Java 7

Neuerungen in Kürze: Þ Alle Wrapper-Klassen bieten eine statische compare()-Methode, die zwei primitive Werte

vergleicht. Die Methode ist eine Alternative zur compareTo()-Methode, die alle WrapperKlassen bieten, die Comparable implementieren. Þ Der ProcessBuilder ist eine Klasse in Java, um externe Prozesse aufzubauen. Neu sind

Methoden, um die Datenströme umzuleiten. Þ Klassenlader lassen sich mit close() wieder schließen. Þ kleine Änderungen an den Kalender-Klassen Þ Unterstützung für SCTP (Stream Control Transmission Protocol) und SDP (Sockets Direct

Protocol). Das Tutorial gibt unter http://download.oracle.com/javase/tutorial/sdp/sockets/ eine Einführung in die sehr spezielle Technologie. Þ neue Klassen für asynchrone Datei- und Socket-Operationen Þ Neu in Java 7 ist JDBC 4.1, was nicht nur auf AutoCloseable eingeht, sondern auch Standard-

Implementierungen für sogenannte Row-Sets mitbringt. Weiterhin können die JDBC-Klassen Pseudo-Spalten nutzen, und bei einer Connection lässt sich das Datenbank-Schema erfragen und setzen.

64

Kapitel 2 2

Threads und nebenläufige Programmierung »Es ist nicht zu wenig Zeit, die wir haben, sondern es ist zu viel Zeit, die wir nicht nutzen.« – Lucius Annaeus Seneca (ca. 4 v. Chr.– 65 n. Chr.), römischer Philosoph und Staatsmann

2.1 Threads erzeugen Die folgenden Abschnitte verdeutlichen, wie der nebenläufige Programmcode in einen Runnable verpackt und dem Thread zur Ausführung vorgelegt wird.

2.1.1 Threads über die Schnittstelle Runnable implementieren Damit der Thread weiß, was er ausführen soll, müssen wir ihm Anweisungsfolgen geben. Diese werden in einem Befehlsobjekt vom Typ Runnable verpackt und dem Thread übergeben. Wird der Thread gestartet, arbeitet er die Programmzeilen aus dem Befehlsobjekt parallel zum restlichen Programmcode ab. Die Schnittstelle Runnable ist schmal und schreibt nur eine run()-Methode vor. interface java.lang.Runnable Þ void run()

Implementierende Klassen realisieren die Operation und setzen dort den parallel auszuführenden Programmcode ein.

Abbildung 2.1: UML-Diagramm der einfachen Schnittstelle Runnable

65

2

Threads und nebenläufige Programmierung

Wir wollen zwei Threads angeben, wobei einer zwanzigmal das aktuelle Datum und die Uhrzeit ausgibt und der andere einfach eine Zahl: Listing 2.1: com/tutego/insel/thread/DateCommand.java package com.tutego.insel.thread; public class DateCommand implements Runnable { @Override public void run() { for ( int i = 0; i < 20; i++ ) System.out.println( new java.util.Date() ); } } Listing 2.2: com/tutego/insel/thread/CounterCommand.java package com.tutego.insel.thread; class CounterCommand implements Runnable { @Override public void run() { for ( int i = 0; i < 20; i++ ) System.out.println( i ); } }

Unser parallel auszuführender Programmcode in run() besteht aus einer Schleife, die in einem Fall ein aktuelles Date-Objekt ausgibt und im anderen Fall einen Schleifenzähler.

2.1.2 Thread mit Runnable starten Nun reicht es nicht aus, einfach die run()-Methode einer Klasse direkt aufzurufen, denn dann wäre nichts nebenläufig, sondern wir würden einfach eine Methode sequenziell ausführen. Damit der Programmcode parallel zur Applikation läuft, müssen wir ein Thread-Objekt mit dem Runnable verbinden und dann den Thread explizit starten. Dazu übergeben wir dem Konstruktor der Klasse Thread eine Referenz auf das Runnable-Objekt und rufen start() auf. Nach-

66

2.1

Threads erzeugen

dem start() für den Thread eine Ablaufumgebung geschaffen hat, ruft es intern selbstständig die Methode run() genau einmal auf. Läuft der Thread schon, so löst ein zweiter Aufruf der start()-Methode eine IllegalThreadStateException aus: Listing 2.3: com/tutego/insel/thread/FirstThread.java, main() Thread t1 = new Thread( new DateCommand() ); t1.start(); Thread t2 = new Thread( new CounterCommand() ); t2.start();

Beim Starten des Programms erfolgt eine Ausgabe auf dem Bildschirm, die in etwa so aussehen kann: Tue Aug 21 16:59:58 CEST 2007 0 1 2 3 4 5 6 7 8 9 Tue Aug 21 16:59:58 CEST 2007 10 ...

Deutlich ist die Verzahnung der beiden Threads zu erkennen. Was allerdings auf den ersten Blick etwas merkwürdig wirkt, ist die erste Zeile des Datum-Threads und viele weitere Zeilen des Zähl-Threads. Dies hat jedoch nichts zu bedeuten und zeigt deutlich den Nichtdeterminismus1 bei Threads. Interpretiert werden kann dies jedoch durch die unterschiedlichen Laufzeiten, die für die Datums- und Zeitausgabe nötig sind.

1

Nicht vorhersehbar, bedeutet hier: Wann der Scheduler den Kontextwechsel vornimmt, ist unbekannt.

67

2

2

Threads und nebenläufige Programmierung

class java.lang.Thread implements Runnable Þ Thread(Runnable target)

Erzeugt einen neuen Thread mit einem Runnable, das den parallel auszuführenden Programmcode vorgibt. Þ void start()

Ein neuer Thread – neben dem die Methode aufrufenden Thread – wird gestartet. Der neue Thread führt die run()-Methode nebenläufig aus. Jeder Thread kann nur einmal gestartet werden.

Hinweis Wenn ein Thread im Konstruktor einer Runnable-Implementierung gestartet wird, sollte die Arbeitsweise bei der Vererbung beachtet werden. Nehmen wir an, eine Klasse leitet von einer anderen Klasse ab, die im Konstruktor einen Thread startet. Bildet die Applikation ein Exemplar der Unterklasse, so werden bei der Bildung des Objekts immer erst die Konstruktoren der Oberklasse aufgerufen. Dies hat zur Folge, dass der Thread schon läuft, auch wenn das Objekt noch nicht ganz gebaut ist. Die Erzeugung ist erst abgeschlossen, wenn nach dem Aufruf der Konstruktoren der Oberklassen der eigene Konstruktor vollständig abgearbeitet wurde.

2.1.3 Die Klasse Thread erweitern Da die Klasse Thread selbst die Schnittstelle Runnable implementiert und die run()-Methode mit leerem Programmcode bereitstellt, können wir auch Thread erweitern, wenn wir eigene parallele Aktivitäten programmieren wollen: Listing 2.4: com/tutego/insel/thread/DateThread.java, DateThread public class DateThread extends Thread { @Override public void run() { for ( int i = 0; i < 20; i++ ) System.out.println( new Date() ); } }

Dann müssen wir kein Runnable-Exemplar mehr in den Konstruktor einfügen, denn wenn unsere Klasse eine Unterklasse von Thread ist, reicht ein Aufruf der geerbten Methode start().

68

2.1

Threads erzeugen

2

Abbildung 2.2: UML-Diagramm der Klasse Thread, die Runnable implementiert

Danach arbeitet das Programm direkt weiter, führt also kurze Zeit später die nächste Anweisung hinter start() aus:

69

2

Threads und nebenläufige Programmierung

Listing 2.5: com/tutego/insel/thread/DateThreadUser, main() Thread t = new DateThread(); t.start(); new DateThread().start();

// (*)

Die (*)-Zeile zeigt, dass das Starten sehr kompakt auch ohne Zwischenspeicherung der Objektreferenz möglich ist. class java.lang.Thread implements Runnable Þ void run()

Diese Methode in Thread hat einen leeren Rumpf. Unterklassen überschreiben run(), sodass sie den parallel auszuführenden Programmcode enthält.

Überschreiben von start() und Selbststarter Die Methode start() kann von uns auch überschrieben werden, was aber nur selten sinnvoll beziehungsweise nötig ist. Wir müssen dann darauf achten, super.start() aufzurufen, damit der Thread wirklich startet. Damit wir als Thread-Benutzer nicht erst die start()-Methode aufrufen müssen, kann ein Thread sich auch selbst starten. Der Konstruktor ruft dazu einfach die eigene start()Methode auf: class DateThread extends Thread { DateThread() { start(); } // ... der Rest bleibt ... }

run( ) wurde statt start( ) aufgerufen: Ja, wo laufen sie denn? Ein Programmierfehler, der Anfängern schnell unterläuft, ist folgender: Statt start() rufen sie aus Versehen run() auf dem Thread auf. Was geschieht? Fast genau das Gleiche wie bei start(), nur mit dem Unterschied, dass die Objektmethode run() nicht parallel zum übrigen Programm abgearbeitet wird. Der aktuelle Thread bearbeitet die run()-Methode sequenziell, bis

70

2.2

Thread-Eigenschaften und -Zustände

sie zu Ende ist und die Anweisungen nach dem Aufruf an die Reihe kommen. Der Fehler fällt nicht immer direkt auf, denn die Aktionen in run() finden ja statt – nur eben nicht nebenläufig.

Erweitern von Thread oder Implementieren von Runnable? Die beste Idee wäre, Runnable-Objekte zu bauen, die dann dem Thread übergeben werden. Befehlsobjekte dieser Art sind recht flexibel, da die einfachen Runnable-Objekte leicht übergeben und sogar von Threads aus einem Thread-Pool ausgeführt werden können. Ein Nachteil der Thread-Erweiterung ist, dass die Einfachvererbung störend sein kann; erbt eine Klasse von Thread, ist die Erweiterung schon »aufgebraucht«. Doch, egal ob eine Klasse Runnable implementiert oder Thread erweitert, eines bleibt: eine neue Klasse.

2.2 Thread-Eigenschaften und -Zustände Ein Thread hat eine ganze Reihe von Zuständen, wie einen Namen und eine Priorität, die sich erfragen und setzen lassen. Nicht jede Eigenschaft ist nach dem Start änderbar, doch welche das sind, zeigen die folgenden Abschnitte.

2.2.1 Der Name eines Threads Ein Thread hat eine ganze Menge Eigenschaften – wie einen Zustand, eine Priorität und auch einen Namen. Dieser kann mit setName() gesetzt und mit getName() erfragt werden. class java.lang.Thread implements Runnable Þ Thread(String name)

Erzeugt ein neues Thread-Objekt und setzt den Namen. Sinnvoll bei Unterklassen, die den Konstruktor über super(name) aufrufen. Þ Thread(Runnable target, String name)

Erzeugt ein neues Thread-Objekt mit einem Runnable und setzt den Namen. Þ final String getName()

Liefert den Namen des Threads. Der Name wird im Konstruktor angegeben oder mit setName() zugewiesen. Standardmäßig ist der Name »Thread-x«, wobei x eine eindeutige Nummer ist. Þ final void setName(String name)

Ändert den Namen des Threads.

71

2

2

Threads und nebenläufige Programmierung

2.2.2 Wer bin ich? Eine Erweiterung der Klasse Thread hat den Vorteil, dass geerbte Methoden wie getName() sofort genutzt werden können. Wenn wir Runnable implementieren, genießen wir diesen Vorteil nicht. Die Klasse Thread liefert mit der statischen Methode currentThread() die Objektreferenz für das Thread-Exemplar, das diese Anweisung gerade ausführt. Auf diese Weise lassen sich nichtstatische Thread-Methoden wie getName() verwenden.

Beispiel Gib die aktuelle Priorität des laufenden Threads und den Namen aus: System.out.println( Thread.currentThread().getPriority() ); // z. B. 5 System.out.println( Thread.currentThread().getName() );

// z. B. main

Falls es in einer Schleife wiederholten Zugriff auf Thread.currentThread() gibt, sollte das Ergebnis zwischengespeichert werden, denn der Aufruf ist nicht ganz billig. class java.lang.Thread implements Runnable Þ static Thread currentThread()

Liefert den Thread, der das laufende Programmstück ausführt.

2.2.3 Die Zustände eines Threads * Bei einem Thread-Exemplar können wir einige Zustände feststellen: 1. Nicht erzeugt: Der Lebenslauf eines Thread-Objekts beginnt mit new, doch befindet er sich damit noch nicht im Zustand ausführend. 2. Laufend (vom Scheduler berücksichtigt) und nicht laufend (vom Scheduler nicht berücksichtigt): Durch start() gelangt der Thread in den Zustand »ausführbar« beziehungsweise »laufend«. Der Zustand kann sich ändern, wenn ein anderer Thread zur Ausführung gelangt und dann dem aktuellen Thread den Prozessor entzieht. Der vormals laufende Thread kommt in den Zustand nicht laufend, bis der Scheduler ihm wieder Rechenzeit zuordnet. 3. Wartend: Dieser Zustand wird mittels spezieller Synchronisationstechniken oder Ein-/Ausgabefunktionen erreicht – der Thread verweilt in einem Wartezustand. 4. Beendet: Nachdem die Aktivität des Thread-Objekts beendet wurde, kann es nicht mehr aktiviert werden und ist tot, also beendet.

72

2.2

Thread-Eigenschaften und -Zustände

Zustand über Thread.State In welchem Zustand ein Thread gerade ist, zeigt die Methode getState(). Sie liefert ein Objekt vom Typ der Aufzählung Thread.State (die einzige Aufzählung im Paket java.lang), die Folgendes deklariert: Zustand

Erläuterung

NEW

neuer Thread, noch nicht gestartet

RUNNABLE

Läuft in der JVM.

BLOCKED

Wartet auf einen MonitorLock, wenn er etwa einen synchronized-Block betreten möchte.

WAITING

Wartet etwa auf ein notify().

TIMED_WAITING

Wartet etwa in einem sleep().

TERMINATED

Ausführung ist beendet.

Tabelle 2.1: Zustände eines Threads

Zudem lässt sich die Methode isAlive() verwenden, die erfragt, ob der Thread gestartet wurde, aber noch nicht tot ist.

2.2.4 Schläfer gesucht Manchmal ist es notwendig, einen Thread eine bestimmte Zeit lang anzuhalten. Dazu lassen sich Methoden zweier Klassen nutzen: Þ die überladene statische Methode Thread.sleep(): Etwas erstaunlich ist sicherlich, dass sie

keine Objektmethode von einem Thread-Objekt ist, sondern eine statische Methode. Ein Grund wäre, dass dadurch verhindert wird, externe Threads zu beeinflussen. Es ist nicht möglich, einen fremden Thread, über dessen Referenz wir verfügen, einfach einige Sekunden lang schlafen zu legen und ihn so von der Ausführung abzuhalten. Þ die Objektmethode sleep() auf einem TimeUnit-Objekt: Auch sie bezieht sich immer auf

den ausführenden Thread. Der Vorteil gegenüber sleep() ist, dass hier die Zeiteinheiten besser sichtbar sind.

73

2

2

Threads und nebenläufige Programmierung

Beispiel Der ausführende Thread soll zwei Sekunden lang schlafen. Einmal mit Thread.sleep(): try { Thread.sleep( 2000 ); } catch ( InterruptedException e ) { }

Dann mit TimeUnit: try { TimeUnit.SECONDS.sleep( 2 ); } catch ( InterruptedException e ) { }

Der Schlaf kann durch eine InterruptedException unterbrochen werden, etwa durch interrupt(). Die Ausnahme muss behandelt werden, da sie keine RuntimeException ist.

Praktisch wird das Erweitern der Klasse Thread bei inneren anonymen Klassen. Die folgende Anweisung gibt nach zwei Sekunden Schlafzeit eine Meldung auf dem Bildschirm aus: Listing 2.6: com/tutego/insel/thread/SleepInInnerClass.java, main() new Thread() { @Override public void run() { try { Thread.sleep( 2000 ); System.out.println( "Zeit ist um." ); } catch ( InterruptedException e ) { e.printStackTrace(); } } }.start();

Da new Thread(){...} ein Exemplar der anonymen Unterklasse ergibt, lässt die auf dem Ausdruck aufgerufene Objektmethode start() den Thread gleich loslaufen. Aufgaben dieser Art lösen auch die Timer gut. class java.lang.Thread implements Runnable Þ static void sleep(long millis) throws InterruptedException

Der aktuell ausgeführte Thread wird mindestens millis Millisekunden schlafen gelegt. Unterbricht ein anderer Thread den schlafenden, wird vorzeitig eine InterruptedException ausgelöst.

74

2.2

Thread-Eigenschaften und -Zustände

Þ static void sleep(long millis, int nanos) throws InterruptedException

Der aktuell ausgeführte Thread wird mindestens millis Millisekunden und zusätzlich nanos Nanosekunden schlafen gelegt. Im Gegensatz zu sleep(long) wird bei einer negativen Millisekundenanzahl eine IllegalArgumentException ausgelöst; auch wird diese Exception ausgelöst, wenn die Nanosekundenanzahl nicht zwischen 0 und 999.999 liegt. enum java.util.concurrent.TimeUnit extends Enum implements Serializable, Comparable Þ NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS

Aufzählungselemente von TimeUnit. Þ void sleep(long timeout) throws InterruptedException

Führt ein Thread.sleep() für die Zeiteinheit aus. Eine überladene Methode Thread.sleep(long, TimeUnit) wäre nett, gibt es aber nicht.

Abbildung 2.3: UML-Diagramme für Timer und TimerTask

2.2.5 Mit yield() auf Rechenzeit verzichten Neben sleep() gibt es eine weitere Methode, um kooperative Threads zu programmieren: die Methode yield(). Sie funktioniert etwas anders als sleep(), da hier nicht nach Ablauf der genannten Millisekunden zum Thread zurückgekehrt wird, sondern yield() den Thread bezüglich seiner Priorität wieder in die Thread-Warteschlange des Systems einordnet. Einfach ausgedrückt, sagt yield() der Thread-Verwaltung: »Ich setze diese Runde aus und mache weiter, wenn ich das nächste Mal dran bin.«

75

2

2

Threads und nebenläufige Programmierung

class java.lang.Thread implements Runnable Þ static void yield()

Der laufende Thread gibt freiwillig seine Rechenzeit ab. Die Methode ist für Implementierungen der JVM nicht verbindlich.

2.2.6 Der Thread als Dämon Ein Server reagiert oft in einer Endlosschleife auf eingehende Aufträge vom Netzwerk und führt die gewünschte Aufgabe aus. In unseren bisherigen Programmen haben wir oft Endlosschleifen eingesetzt, sodass ein gestarteter Thread nie beendet wird. Wenn also run() wie in den vorangehenden Beispielen nie abbricht (Informatiker sprechen hier von terminiert), läuft der Thread immer weiter, auch wenn die Hauptapplikation beendet ist. Dies ist nicht immer beabsichtigt, da vielleicht Server-Funktionalität nach dem Beenden der Applikation nicht mehr gefragt ist. Dann sollte auch der endlos laufende Thread beendet werden. Um dies auszudrücken, erhält ein im Hintergrund arbeitender Thread eine spezielle Kennung: Der Thread wird als Dämon2 gekennzeichnet. Standardmäßig ist ein aufgebauter Thread kein Dämon.

Ein Dämon ist wie ein Heinzelmännchen im Hintergrund mit einer Aufgabe beschäftigt. Wenn das Hauptprogramm beendet ist und die Laufzeitumgebung erkennt, dass kein normaler Thread mehr läuft, sondern nur Dämonen, dann ist das Ende der Dämonen eingeläutet, und die JVM kommt zum Ende. Denn Dämonen-Threads sind Zulieferer: Gibt es keine Klienten mehr, werden auch sie nicht mehr gebraucht. Das ist wie bei den Göttern der Scheibenwelt: Glaubt keiner an sie, hören sie auf zu existieren. Wir müssen uns also um das Ende des Dämons nicht kümmern. Gleichzeitig heißt das aber auch, dass ein Dämonen-Thread vorsichtig mit Ein-/Ausgabeoperationen sein muss, denn er kann jederzeit – auch etwa während einer Schreiboperation auf die Festplatte – abgebrochen werden, was zu beschädigten Daten führen kann.

2

76

Das griechische dalx (engl. daemon) bezeichnet allerlei Wesen zwischen Gott und Teufel. Eine gute Einleitung gibt http://de.wikipedia.org/wiki/D%C3 %A4mon.

2.2

Thread-Eigenschaften und -Zustände

Hinweis Der Garbage-Collector (GC) ist ein gutes Beispiel für einen Dämon. Nur, wenn es andere Threads gibt, muss der Speicher aufgeräumt werden. Gibt es keine anderen Threads mehr, kann auch die JVM mit beendet werden, was auch die Dämonen-Threads beendet.

2

Wie ein Thread in Java zum Dämon wird Einen Thread in Java als Dämon zu kennzeichnen, heißt, die Methode setDaemon() mit dem Argument true aufzurufen. Die Methode ist nur vor dem Starten des Threads erlaubt. Danach kann der Status nicht wieder vom Dämon in den normalen Benutzer-Thread umgesetzt werden. Die Auswirkungen von setDaemon(true) können wir am folgenden Programm ablesen: Listing 2.7: com/tutego/insel/thread/DaemonThread.java package com.tutego.insel.thread; class DaemonThread extends Thread { DaemonThread() { setDaemon( true ); } @Override public void run() { while ( true ) System.out.println( "Lauf, Thread, lauf" ); } public static void main( String[] args ) { new DaemonThread().start(); } }

In diesem Programm wird der Thread gestartet, und danach ist die Anwendung sofort beendet. Vor dem Ende kann der neue Thread aber schon einige Zeilen auf der Konsole ausgeben.

77

2

Threads und nebenläufige Programmierung

Klammern wir die Anweisung mit setDaemon(true) aus, läuft das Programm ewig, da die Laufzeitumgebung auf das natürliche Ende der Thread-Aktivität wartet. class java.lang.Thread implements Runnable Þ final void setDaemon(boolean on)

Markiert den Thread als Dämon oder normalen Thread. Die Methode muss aufgerufen werden, bevor der Thread gestartet wurde, andernfalls folgt eine IllegalThreadStateException. Mit anderen Worten: Nachträglich kann ein existierender Thread nicht mehr zu einem Dämon gemacht werden, und ihm kann auch nicht die Dämonenhaftigkeit genommen werden, so er sie hat. Þ final boolean isDaemon()

Testet, ob der Thread ein Dämon-Thread ist.

AWT und Dämonen * Obwohl Dämonen für Hintergrundaufgaben eine gute Einrichtung sind, ist der AWT-Thread kein Dämon. Unterschiedliche AWT-Threads sind normale Benutzer-Threads; dazu gehören AWT-Input, AWT-Motif oder Screen_Updater. Dies bedeutet, dass bei einmaliger Nutzung einer AWT-Methode ein spezieller Nicht-Dämon-Thread gestartet wird, sodass die Applikation nicht automatisch beendet wird, wenn das Hauptprogramm endet. Daher muss die Applikation in vielen Fällen hart mit System.exit() beendet werden.

2.2.7 Das Ende eines Threads Es gibt Threads, die dauernd laufen, weil sie zum Beispiel Serverfunktionen implementieren. Andere Threads führen einmalig eine Operation aus und sind danach beendet. Allgemein ist ein Thread beendet, wenn eine der folgenden Bedingungen zutrifft: Þ Die run()-Methode wurde ohne Fehler beendet. Wenn wir eine Endlosschleife programmie-

ren, würde diese potenziell einen nie endenden Thread bilden. Þ In der run()-Methode tritt eine RuntimeException auf, die die Methode beendet. Das beendet

weder die anderen Threads noch die JVM als Ganzes. Þ Der Thread wurde von außen abgebrochen. Dazu dient die prinzipbedingt problematische

Methode stop(), von deren Verwendung abgeraten wird und die auch veraltet ist. Þ Die virtuelle Maschine wird beendet und nimmt alle Threads mit ins Grab.

78

2.2

Thread-Eigenschaften und -Zustände

Wenn der Thread einen Fehler melden soll Da ein Thread nebenläufig arbeitet, kann die run()-Methode synchron schlecht Exceptions melden oder einen Rückgabewert liefern. Wer sollte auch an welcher Stelle darauf hören? Eine Lösung für das Problem ist ein Listener, der sich beim Thread anmeldet und darüber informiert wird, ob der Thread seine Arbeit machen konnte oder nicht. Eine andere Lösung gibt Callable, mit dem ein spezieller Fehlercode zurückgegeben oder eine Exception angezeigt werden kann. Speziell für ungeprüfte Ausnahmen kann ein UncaughtExceptionHandler weiterhelfen.

2.2.8 Einen Thread höflich mit Interrupt beenden Der Thread ist in der Regel zu Ende, wenn die run()-Methode ordentlich bis zum Ende ausgeführt wurde. Enthält eine run()-Methode jedoch eine Endlosschleife – wie etwa bei einem Server, der auf eingehende Anfragen wartet –, so muss der Thread von außen zur Kapitulation gezwungen werden. Die naheliegende Möglichkeit, mit der Thread-Methode stop() einen Thread abzuwürgen, wollen wir in Abschnitt 2.2.10, »Der stop() von außen und die Rettung mit ThreadDeath«, diskutieren. Wenn wir den Thread schon nicht von außen beenden wollen, können wir ihn immerhin bitten, seine Arbeit aufzugeben. Periodisch müsste er dann nur überprüfen, ob jemand von außen den Abbruchswunsch geäußert hat.

Die Methoden interrupt() und isInterrupted() Die Methode interrupt() setzt von außen in einem Thread-Objekt ein internes Flag, das dann in der run()-Methode durch isInterrupted() periodisch abgefragt werden kann. Das folgende Programm soll jede halbe Sekunde eine Meldung auf dem Bildschirm ausgeben. Nach zwei Sekunden wird der Unterbrechungswunsch mit interrupt() gemeldet. Auf dieses Signal achtet die sonst unendlich laufende Schleife und bricht ab: Listing 2.8: com/tutego/insel/thread/ThreadusInterruptus.java, main() Thread t = new Thread() { @Override public void run() { System.out.println( "Es gibt ein Leben vor dem Tod. " ); while ( ! isInterrupted() )

79

2

2

Threads und nebenläufige Programmierung

{ System.out.println( "Und er läuft und er läuft und er läuft" ); try { Thread.sleep( 500 ); } catch ( InterruptedException e ) { interrupt(); System.out.println( "Unterbrechung in sleep()" ); } } System.out.println( "Das Ende" ); } }; t.start(); Thread.sleep( 2000 ); t.interrupt();

Die Ausgabe zeigt hübsch die Ablaufsequenz: Es gibt ein Leben vor dem Tod. Und er läuft und er läuft und er läuft Und er läuft und er läuft und er läuft Und er läuft und er läuft und er läuft Und er läuft und er läuft und er läuft Unterbrechung in sleep() Das Ende

Die run()-Methode im Thread ist so implementiert, dass die Schleife genau dann verlassen wird, wenn isInterrupted() den Wert true ergibt, also von außen die interrupt()-Methode für dieses Thread-Exemplar aufgerufen wurde. Genau dies geschieht in der main()-Methode. Auf den ersten Blick ist das Programm leicht verständlich, doch vermutlich erzeugt das interrupt() im catch-Block die Aufmerksamkeit. Stünde diese Zeile dort nicht, würde das Programm aller Wahrscheinlichkeit nach nicht funktionieren. Das Geheimnis ist folgendes: Wenn die Ausgabe nur jede halbe Sekunde stattfindet, befindet sich der Thread fast die gesamte Zeit über in der Schlafmethode sleep(). Also wird vermutlich der interrupt() den

80

2.2

Thread-Eigenschaften und -Zustände

Thread gerade beim Schlafen stören. Genau dann wird sleep() durch InterruptedException unterbrochen, und der catch-Behandler fängt die Ausnahme ein. Jetzt passiert aber etwas Unerwartetes: Durch die Unterbrechung wird das interne Flag zurückgesetzt, sodass isInterrupted() meint, die Unterbrechung habe gar nicht stattgefunden. Daher muss interrupt() erneut aufgerufen werden, da das Abbruch-Flag neu gesetzt werden muss und isInterrupted() das Ende bestimmen kann. Wenn wir mit der Objektmethode isInterrupted() arbeiten, müssen wir beachten, dass neben sleep() auch die Methoden join() und wait() durch die InterruptedException das Flag löschen.

Hinweis Die Methoden sleep(), wait() und join() lösen alle eine InterruptedException aus, wenn sie durch die Methode interrupt() unterbrochen werden. Das heißt, interrupt() beendet diese Methoden mit der Ausnahme.

Zusammenfassung: interrupted(), isInterrupted() und interrupt() Die Methodennamen sind verwirrend gewählt, sodass wir die Aufgaben noch einmal zusammenfassen wollen: Die Objektmethode interrupt() setzt in einem (anderen) Thread-Objekt ein Flag, dass es einen Antrag gab, den Thread zu beenden. Sie beendet aber den Thread nicht, obwohl es der Methodenname nahelegt. Dieses Flag lässt sich mit der Objektmethode isInterrupted() abfragen. In der Regel wird dies innerhalb einer Schleife geschehen, die darüber bestimmt, ob die Aktivität des Threads fortgesetzt werden soll. Die statische Methode interrupted() ist zwar auch eine Anfragemethode und testet das entsprechende Flag des aktuell laufenden Threads, wie Thread.currentThread().isInterrupted(), aber zusätzlich löscht es den Interrupt-Status auch, was isInterrupted() nicht tut. Zwei aufeinanderfolgende Aufrufe von interrupted() führen daher zu einem false, es sei denn, in der Zwischenzeit erfolgt eine weitere Unterbrechung.

2.2.9 UncaughtExceptionHandler für unbehandelte Ausnahmen Einer der Gründe für das Ende eines Threads ist eine unbehandelte Ausnahme, etwa von einer nicht aufgefangenen RuntimeException. Um in diesem Fall einen kontrollierten Abgang zu ermöglichen, lässt sich an den Thread ein UncaughtExceptionHandler hängen, der immer dann benachrichtigt wird, wenn der Thread wegen einer nicht behandelten Ausnahme endet. UncaughtExceptionHandler ist eine in Thread deklarierte innere Schnittstelle, die eine Operation void uncaughtException(Thread t, Throwable e) vorschreibt. Eine Implementierung der Schnitt-

stelle lässt sich entweder einem individuellen Thread oder allen Threads anhängen, sodass im

81

2

2

Threads und nebenläufige Programmierung

Fall des Abbruchs durch unbehandelte Ausnahmen die JVM die Methode uncaughtException() aufruft. Auf diese Weise kann die Applikation im letzten Atemzug noch den Fehler loggen, den die JVM über das Throwable e übergibt. class java.lang.Thread implements Runnable Þ void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

Setzt den UncaughtExceptionHandler für den Thread. Þ Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()

Liefert den aktuellen UncaughtExceptionHandler. Þ Static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

Setzt den UncaughtExceptionHandler für alle Threads. Þ static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()

Liefert den zugewiesenen UncaughtExceptionHandler aller Threads. Ein mit setUncaughtExceptionHandler() lokal gesetzter UncaughtExceptionHandler überschreibt den Eintrag für den setDefaultUncaughtExceptionHandler(). Zwischen dem mit dem Thread assoziierten Handler und dem globalen gibt es noch einen Handler-Typ für Thread-Gruppen, der jedoch seltener verwendet wird.

2.2.10

Der stop() von außen und die Rettung mit ThreadDeath *

Wenn ein Thread nicht auf interrupt() hört, aber aus irgendwelchen Gründen dringend beendet werden muss, müssen wir wohl oder übel die veraltete Methode stop() einsetzen.

Abbildung 2.4: Dass die Methode stop() veraltet ist, zeigen in Eclipse eine unterschlängelte Linie und ein Symbol am linken Rand an. Steht der Cursor auf der problematischen Zeile, weist eine Fehlermeldung ebenfalls auf das Problem hin.

deprecated gibt uns schon einen guten Hinweis darauf, stop() besser nicht zu benutzen (leider gibt es hier, im Gegensatz zu den meisten anderen veralteten Methoden, keinen einfachen, empfohlenen Ersatz). Überschreiben können wir stop() auch nicht, da es final ist. Wenn wir einen Thread von außen beenden, geben wir ihm keine Chance mehr, seinen Zustand

82

2.2

Thread-Eigenschaften und -Zustände

konsistent zu verlassen. Zudem kann die Unterbrechung an beliebiger Stelle erfolgen, sodass angeforderte Ressourcen frei in der Luft hängen können.

2 class java.lang.Thread implements Runnable Þ final void stop()

Wurde der Thread gar nicht gestartet oder ist er bereits abgearbeitet beziehungsweise beendet, kehrt die Methode sofort zurück. Andernfalls wird über checkAccess() geprüft, ob wir überhaupt das Recht haben, den Thread abzuwürgen. Dann wird der Thread beendet, egal was er zuvor unternommen hat; jetzt kann er nur noch sein Testament in Form eines ThreadDeathObjekts als Exception anzeigen.

Das ThreadDeath-Objekt So unmöglich ist das Reagieren auf ein stop() auch nicht. Immer dann, wenn ein Thread mit stop() zum Ende kommen soll, löst die JVM eine ThreadDeath-Ausnahme aus, die letztendlich den Thread beendet. ThreadDeath ist eine Unterklasse von Error, das wiederum von Throwable abgeleitet ist, sodass ThreadDeath mit einem try-Block abgefangen werden kann. Die Java-Entwickler haben ThreadDeath nicht zu einer Unterklasse von Exception gemacht, weil sie nicht wollten, dass ThreadDeath bei einer allgemeinen Exception-Behandlung über catch(Exception e) abgefangen wird.3 Wenn wir ThreadDeath auffangen, können wir noch auf den Tod reagieren und Aufräumarbeiten erlauben. Wir sollten aber nicht vergessen, anschließend das aufgefangene ThreadDeathObjekt wieder auszulösen, weil der Thread sonst nicht beendet wird: Listing 2.9: com/tutego/insel/thread/ThreadStopRecovery.java, main() Thread t = new Thread() { @Override public void run() { try { while ( true ) System.out.println( "I Like To Move It." ); } catch ( ThreadDeath td )

3

Dass wir die Klasse überhaupt nutzen können, ist einem Fehler von Sun zuzuschreiben; die Klasse sollte eigentlich nicht sichtbar sein.

83

2

Threads und nebenläufige Programmierung

{ System.out.println( "Das Leben ist nicht totzukriegen." ); throw td; } } }; t.start(); try { Thread.sleep( 1 ); } catch ( Exception e ) { } t.stop(); ThreadDeath bietet eine extravagante Möglichkeit, um das aktuell laufende Programm zu be-

enden: throw new ThreadDeath(). Die Anweisung System.exit() ist aber weniger Aufsehen erregend.

2.2.11

Ein Rendezvous mit join() *

Wollen wir Aufgaben auf mehrere Threads verteilen, kommt der Zeitpunkt, an dem die Ergebnisse eingesammelt werden. Die Resultate können allerdings erst dann zusammengebracht werden, wenn alle Threads mit ihrer Ausführung fertig sind. Da sie sich zu einem bestimmten Zeitpunkt treffen, heißt das auch Rendezvous. Zum Warten gibt es mehrere Strategien. Zunächst lässt sich mit Callable arbeiten, um dann mit get() synchron auf das Ende zu warten. Arbeiten wir mit Runnable, so kann ein Thread keine direkten Ergebnisse wie eine Methode nach außen geben, weil die run()-Methode den Ergebnistyp void hat. Da ein nebenläufiger Thread zudem asynchron arbeitet, wissen wir nicht einmal, wann wir das Ergebnis erwarten können. Die Übertragung von Werten ist kein Problem. Hier können Klassenvariablen und auch Objektvariablen helfen, denn über sie können wir kommunizieren. Jetzt fehlt nur noch, dass wir auf das Ende der Aktivität eines Threads warten können. Das funktioniert mit der Methode join(). In unserem folgenden Beispiel legt ein Thread t in der Variable result ein Ergebnis ab. Wir können die Auswirkungen von join() sehen, wenn wir die auskommentierte Zeile hineinnehmen: Listing 2.10: com/tutego/insel/thread/JoinTheThread.java package com.tutego.insel.thread; class JoinTheThread

84

2.2

Thread-Eigenschaften und -Zustände

{ static class JoinerThread extends Thread

2

{ public int result; @Override public void run() { result = 1; } } public static void main( String[] args ) throws InterruptedException { JoinerThread t = new JoinerThread(); t.start(); //

t.join(); System.out.println( t.result ); }

}

Ohne den Aufruf von join() wird als Ergebnis 0 ausgegeben, denn das Starten des Threads kostet etwas Zeit. In dieser Zeit geben wir aber die automatisch auf 0 initialisierte Klassenvariable aus. Nehmen wir join() hinein, wird die run()-Methode zu Ende ausgeführt, und der Thread setzt die Variable result auf 1. Das sehen wir dann auf dem Bildschirm. class java.lang.Thread implements Runnable Þ final void join() throws InterruptedException

Der aktuell ausgeführte Thread wartet auf den Thread, für den die Methode aufgerufen wird, bis dieser beendet ist. Þ final void join(long millis) throws InterruptedException

Wie join(), doch wartet diese Variante höchstens millis Millisekunden. Wurde der Thread bis dahin nicht vollständig beendet, fährt das Programm fort. Auf diese Weise kann versucht werden, innerhalb einer bestimmten Zeitspanne auf den Thread zu warten, sonst aber weiterzumachen. Ist millis gleich 0, so hat dies die gleiche Wirkung wie join(). Þ final void join (long millis, int nanos) throws InterruptedException

Wie join(long), jedoch mit potenziell genauerer Angabe der maximalen Wartezeit.

85

2

Threads und nebenläufige Programmierung

Nach einem thread.join(long) ist mitunter die thread.isAlive()-Methode nützlich, denn sie sagt aus, ob thread noch aktiv arbeitet oder beendet ist. In TimeUnit gibt es mit timedJoin() eine Hilfsmethode, um mit der Dauer schöner zu arbeiten. class java.lang.TimeUnit implements Runnable Þ voidtimedJoin(Thread thread, long timeout) throws InterruptedException

Berechnet aus der TimeUnit und dem timeout Millisekunden (ms) und Nanosekunden (ns) und führt ein join(ms, ns) auf dem thread aus.

Warten auf den Langsamsten Große Probleme lassen sich in mehrere Teile zerlegen, und jedes Teilproblem kann dann von einem Thread gelöst werden. Dies ist insbesondere bei Mehrprozessorsystemen eine lohnenswerte Investition. Zum Schluss müssen wir nur noch darauf warten, dass die Threads zum Ende gekommen sind, und das Ergebnis einsammeln. Dazu eignet sich join() gut.

Beispiel Zwei Threads arbeiten an einem Problem. Anschließend wird gewartet, bis beide ihre Aufgabe erledigt haben. Dann könnte etwa ein anderer Thread die von a und b verwendeten Ressourcen wieder nutzen: Thread a = new AThread(); Thread b = new BThread(); a.start(); b.start(); a.join(); b.join();

Es ist unerheblich, wessen join() wir zuerst aufrufen, da wir ohnehin auf den langsamsten Thread warten müssen. Wenn ein Thread schon beendet ist, kehrt join() sofort zurück. Eine andere Lösung für zusammenlaufende Threads besteht darin, diese in einer ThreadGruppe zusammenzufassen. Dann können sie zusammen behandelt werden, sodass nur das Ende der Thread-Gruppe beobachtet wird.

86

2.2

2.2.12

Thread-Eigenschaften und -Zustände

Arbeit niederlegen und wieder aufnehmen *

Wollen wir erreichen, dass ein Thread für eine bestimmte Zeit die Arbeit niederlegt und ein anderer den schlafenden Thread wieder aufwecken kann, müssten wir das selbst implementieren. Zwar gibt es mit suspend() und resume() zwei Methoden, doch diese Start-Stopp-Technik ist nicht erwünscht, da sie ähnlich problematisch ist wie stop(). class java.lang.Thread implements Runnable Þ final void suspend()

Lebt der Thread, wird er so lange eingefroren (schlafen gelegt), bis resume() aufgerufen wird. Þ final void resume()

Weckt einen durch suspend() lahmgelegten Thread wieder auf, der dann wieder arbeiten kann.

2.2.13

Priorität *

Jeder Thread verfügt über eine Priorität, die aussagt, wie viel Rechenzeit ein Thread relativ zu anderen Threads erhält. Die Priorität ist eine Zahl zwischen Thread.MIN_PRIORITY (1) und Thread.MAX_PRIORITY (10). Durch den Wert kann der Scheduler erkennen, welchem Thread er den Vorzug geben soll, wenn mehrere Threads auf Rechenzeit warten. Bei seiner Initialisierung bekommt jeder Thread die Priorität des erzeugenden Threads. Normalerweise ist es die Priorität Thread.NORM_PRIORITY (5). Das Betriebssystem (oder die JVM) nimmt die Threads immer entsprechend der Priorität aus der Warteschlange heraus (daher Prioritätswarteschlange). Ein Thread mit der Priorität N wird vor allen Threads mit der Wichtigkeit kleiner N, aber hinter denen der Priorität größer gleich N gesetzt. Ruft nun ein kooperativer Thread mit der Priorität N die Methode yield() auf, bekommt ein Thread mit der Priorität

Java(TM) 2 Runtime Environment, Standard Edition Sun Microsystems Inc. http://java.sun.com/ ; ... windows pentium i486 i386

Die Methode loadFromXML() liest aus einem InputStream und löst im Fall eines fehlerhaften Dateiformats eine InvalidPropertiesFormatException aus. Beim Speichern kann so ein Fehler natürlich nicht auftreten. Und genauso, wie bei store() ein OutputStream mit einem Kommentar gespeichert wird, macht das auch storeToXML(). Die Methode ist mit einem zusätzlichen Parameter überladen, der eine XML-Kodierung erlaubt. Ist der Wert nicht gesetzt, so ist die Standardkodierung UTF-8.

247

3

Datenstrukturen und Algorithmen

class java.util.Properties extends Hashtable Þ void store(OutputStream out, String header)

Speichert die Properties-Liste mithilfe des Ausgabestroms ab. Am Kopf der Datei wird eine Kennung geschrieben, die im zweiten Argument steht. Die Kennung darf null sein. Þ void load(InputStream inStream)

Lädt eine Properties-Liste aus einem Eingabestrom. Þ void storeToXML(OutputStream os, String comment, String encoding) throws IOException

Speichert die Properties im XML-Format. comment kann null sein, wenn ein Kommentar erwünschst ist. encoding steht für die Zeichenkodierung, etwa »Latin-1« oder »UTF-8«. Þ void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException

Liest Properites im XML-Format von einem Eingabestrom ein.

3.7.6 Klassenbeziehungen: Properties und Hashtable * Die Properties-Klasse ist eine Erweiterung von Hashtable, weil die Speicherung der Einstellungsdaten in dieser Datenstruktur erfolgt. Ein Properties-Objekt erweitert die Hash-Tabelle um die Möglichkeit, die Schlüssel-Werte-Paare in einem festgelegten Format aus einer Textdatei beziehungsweise einem Datenstrom zu laden und wieder zu speichern. Doch gerade weil Hashtable erweitert wird, sind auch alle Methoden der Klasse Hashtable auf ein PropertiesObjekt anwendbar. Das ergibt nicht für alle Methoden Sinn und ist auch nicht in jedem Fall problemlos. Dass Properties eine Unterklasse von Hashtable ist, ist ähnlich fragwürdig wie die Vererbungsbeziehung von Stack als Unterklasse von Vector. So ist ein Augenmerk auf die put()-Methode zu legen. Sie gibt es in der Klasse Properties nicht, denn put() wird von Hashtable geerbt. Wir sollten sie auch nicht verwenden, da es über sie möglich ist, Objekte einzufügen, die nicht vom Typ String sind. Das gleiche Argument könnte für get() gelten, doch sprechen zwei Dinge dagegen: zum einen, dass wir beim get() aus einem Hashtable-Objekt immer ein Object-Objekt bekommen und daher meistens eine Typanpassung benötigen; und zum anderen durchsucht diese Methode lediglich den Inhalt des angesprochenen PropertiesExemplars. getProperties() arbeitet da etwas anders. Nicht nur ist der Rückgabewert automatisch ein String, sondern getProperties() durchsucht auch übergeordnete Properties-Objekte mit, die zum Beispiel Standardwerte speichern.

248

3.8

Mit einem Iterator durch die Daten wandern

3.8 Mit einem Iterator durch die Daten wandern Wenn wir mit einer ArrayList oder LinkedList arbeiten, so haben wir zumindest eine gemeinsame Schnittstelle List, um an die Daten zu kommen. Doch was vereinigt eine Menge (Set) und eine Liste, sodass sich die Elemente der Sammlungen mit gleichem Programmcode erfragen lassen? Listen geben als Sequenz den Elementen zwar Positionen, aber in einer Menge hat kein Element eine Position. Hier bieten sich Iteratoren beziehungsweise Enumeratoren an, die unabhängig von der Datenstruktur alle Elemente auslesen – wir sagen dann, dass sie »über die Datenstruktur iterieren«. Und nicht nur eine Datenstruktur kann Daten liefern; eine Dateioperation könnte genauso gut Datengeber für alle Zeilen sein. In Java gibt es für Iteratoren zum einen die Schnittstelle java.util.Iterator und zum anderen den älteren java.util.Enumeration. Der Enumerator ist nicht mehr aktuell, daher konzentrieren wir uns zunächst auf den Iterator.

Abbildung 3.10: Iterator und Enumeration

3.8.1 Die Schnittstelle Iterator Ein Iterator ist ein Datengeber, der über eine Methode verfügen muss, um das nächste Element zu liefern. Dann muss es eine zweite Methode geben, die Auskunft darüber gibt, ob der Datengeber noch weitere Elemente zur Verfügung stellt. Zwei Operationen der Schnittstelle Iterator sind daher:

Iterator

Hast du mehr?

Gib mir das Nächste!

hasNext()

next()

Tabelle 3.4: Zwei zentrale Methoden des Iterators

Die Methode hasNext() ermittelt, ob es überhaupt ein nächstes Element gibt, und wenn ja, ob next() das nächste Element erfragen darf. Bei jedem Aufruf von next() erhalten wir ein weite-

res Element der Datenstruktur. So kann der Iterator einen Datengeber (in der Regel eine Datenstruktur) Element für Element ablaufen. Übergehen wir ein false von hasNext() und fragen trotzdem mit next() nach dem nächsten Element, bestraft uns eine NoSuchElementException.

249

3

3

Datenstrukturen und Algorithmen

Prinzipiell könnte die Methode, die das nächste Element liefert, auch per Definition null zurückgeben und so anzeigen, dass es keine weiteren Elemente mehr gibt. Allerdings kann null dann kein gültiger Iterator-Wert sein, und das wäre ungünstig. interface java.util.Iterator Þ boolean hasNext()

Liefert true, falls die Iteration weitere Elemente bietet. Þ E next()

Liefert das nächste Element in der Aufzählung oder NoSuchElementException, wenn keine weiteren Elemente mehr vorhanden sind. Þ void remove()

Die Schnittstelle Iterator erweitert selbst keine weitere Schnittstelle.11 Die Deklaration ist generisch, da das, was der Iterator liefert, immer von einem bekannten Typ ist.

Beispiel Die Aufzählung erfolgt meistens über einen Zweizeiler. Da jede Collection eine Methode iterator() besitzt, lassen sich alle Elemente wie folgt auf dem Bildschirm ausgeben: Collection set = new TreeSet(); Collections.addAll( set, "Horst", "Schlämmer", "Hape" , "Kerkeling" ); for ( Iterator iter = set.iterator(); iter.hasNext(); ) System.out.println( iter.next() );

Das erweiterte for macht das Ablaufen aber noch einfacher, und der gleiche Iterator steckt dahinter.

Beim Iterator geht es immer nur vorwärts Im Gegensatz zum Index eines Felds können wir beim Iterator ein Objekt nicht noch einmal auslesen (next() geht automatisch zum nächsten Element), nicht vorspringen beziehungsweise hin und her springen. Ein Iterator gleicht anschaulich einem Datenstrom; wollten wir ein Element zweimal besuchen, zum Beispiel eine Datenstruktur von rechts nach links noch einmal durchwandern, dann müssen wir wieder ein neues Iterator-Objekt erzeugen oder uns die Elemente zwischendurch merken. Nur bei Listen und sortierten Datenstrukturen ist die Reihenfolge der Elemente vorhersehbar.

11

Konkrete Enumeratoren (und Iteratoren) können nicht automatisch serialisiert werden; die realisierenden Klassen müssen hierzu die Schnittstelle Serializable implementieren.

250

3.8

Mit einem Iterator durch die Daten wandern

Hinweis In Java steht der Iterator nicht auf einem Element, sondern zwischen Elementen.

3 3.8.2 Der Iterator kann (eventuell auch) löschen Die Schnittstelle Iterator bietet prinzipiell die Möglichkeit, das zuletzt aufgezählte Element aus dem zugrunde liegenden Container mit remove() zu entfernen. Vor dem Aufruf muss also next() das zu löschende Element als Ergebnis geliefert haben. Eine Enumeration kann die aufgezählte Datenstruktur grundsätzlich nicht verändern.

Beispiel Ein LinkedHashSet ist eine auf dem HashSet basierende Datenstruktur, die sich aber zusätzlich die Einfügereihenfolge merkt. Ein Programm soll die ältesten Einträge löschen und nur noch die neusten zwei Elemente behalten: LinkedHashSet set = new LinkedHashSet(); set.addAll( Arrays.asList( 3, 2, 1, 6, 5, 4 ) ); System.out.println( set );

// [3, 2, 1, 6, 5, 4]

for ( Iterator iter = set.iterator(); iter.hasNext(); ) { iter.next(); if ( set.size() > 2 ) iter.remove(); } System.out.println( set ); // [5, 4]

interface java.util.Iterator Þ boolean hasNext() Þ E next() Þ void remove()

Entfernt das Element, das der Iterator zuletzt bei next() geliefert hat. Kann ein Iterator keine Elemente löschen, so löst er eine UnsupportedOperationException aus. In der Dokumentation ist die Methode remove() als optional gekennzeichnet. Das heißt, dass ein konkreter Iterator kein remove() können muss – auch eine UnsupportedOperationException ist möglich. Das ist etwa dann der Fall, wenn ein Iterator von einer unveränderbaren Datenstruktur kommt.

251

3

Datenstrukturen und Algorithmen

Hinweis Warum es die Methode remove() im Iterator gibt, ist eine interessante Frage. Die Erklärung dafür: Der Iterator kennt die Stelle, an der sich die Daten befinden (eine Art Cursor). Darum können die Daten dort auch effizient und direkt gelöscht werden. Das erklärt jedoch nicht unbedingt, warum es keine Einfüge-Methode gibt. Ein allgemeiner Grund mag sein, dass bei vielen Container-Typen das Einfügen an einer bestimmten Stelle keinen Sinn ergibt, etwa bei einem sortierten NavigableSet oder NavigableMap. Dort ist die Einfügeposition durch die Sortierung vorgegeben oder belanglos (beziehungsweise bei HashSet durch die interne Realisierung bestimmt), also kein Fall für einen Iterator. Dazu wirft das Einfügen weitere Fragen auf: vor oder nach dem zuletzt per next() gelieferten Element? Soll das neue Element mit aufgezählt werden oder nicht? Soll es auch dann nicht aufgezählt werden, wenn es in der Sortierung erst später an die Reihe käme? Eine Löschen-Methode ist problemloser und universell anwendbar.

3.8.3 Einen Zufallszahleniterator schreiben Zur Übung wollen wir einen Iterator schreiben, der Zufallszahlen liefert. Der Konstruktor soll dazu die maximale Zufallszahl entgegennehmen (exklusiv), die Methode next() liefert anschließend immer die Zufallszahl und hasNext() immer true. Da auch remove() von der Schnittstelle Iterator vorgeschrieben ist, müssen wir die Methode implementieren, lösen jedoch eine Ausnahme aus, da es nichts zu löschen gibt. Listing 3.23: com/tutego/insel/util/RandomIterator.java package com.tutego.insel.util; import java.util.*; /** * Iterator for pseudorandom numbers. */ public class RandomIterator implements Iterator { private final Random random = new Random(); private final int bound; /** * Initializes this iterator with a maximum value (exclusive) for * pseudorandom numbers.

252

3.8

Mit einem Iterator durch die Daten wandern

* @param bound Maximum (exclusive) pseudorandom */ public RandomIterator( int bound ) {

3 this.bound = bound;

} /** * Always true. * @return {@code true}. */ @Override public boolean hasNext() { return true; } /** * Returns a pseudorandom, uniformly distributed {@code Integer} value * between 0 (inclusive) and the specified value (exclusive). * @return Next pseudorandom. */ @Override public Integer next() { return random.nextInt( bound ); } /** * Not supported. * @throws UnsupportedOperationException */ @Override public void remove() { throw new UnsupportedOperationException(); } }

253

3

Datenstrukturen und Algorithmen

Ein Beispiel: Listing 3.24: com/tutego/insel/util/RandomIteratorDemo.java, main() Iterator random = new RandomIterator( 6 ); int dice1 = random.next(); int dice2 = random.next(); System.out.println( dice1 ); System.out.println( dice2 );

Als Übung kann jeder den Iterator so verändern, dass auch ein Startwert definiert werden kann (jetzt ist er 0).

3.8.4 Iteratoren von Sammlungen, das erweiterte for und Iterable Jede Collection wie ArrayList oder HashSet liefert mit iterator() einen Iterator. interface java.util.Collection extends Iterable Þ Iterator iterator()

Liefert den Iterator der Datenstruktur. Collection erweitert eine Schnittstelle Iterable, die unter Java 5 eingeführt wurde; sie schreibt die Methode vor, die einen Iterator liefert. interface java.util.Iterable Þ Iterator iterator()

Liefert den Iterator für eine Sammlung.

Der typisierte Iterator Von einer typisierten Collection liefert iterator() ebenfalls einen typisierten Iterator. Das heißt, die Datenstruktur überträgt den generischen Typ auf den Iterator. Nehmen wir eine mit String typisierte Sammlung an: Collection c = new LinkedList();

Ein Aufruf von c.iterator() liefert nun Iterator, und beim Durchlaufen über den Iterator kann die explizite Typanpassung beim next() entfallen:

254

3.8

Mit einem Iterator durch die Daten wandern

for ( Iterator i = c.iterator(); i.hasNext(); ) { String s = i.next();

3

... }

Iterator und erweitertes for Stößt der Compiler auf ein erweitertes for, und erkennt er rechts vom Doppelpunkt den Typ Iterable, so erzeugt er Bytecode für eine Schleife, die den Iterator und seine bekannten Methoden hasNext() und next() nutzt. Nehmen wir eine statische Methode totalStringLength(List) an, die ermitteln soll, wie viele Zeichen alle Strings zusammen besitzen. Aus static int totalStringLength( List strings ) { int result = 0; for ( String s : strings ) result += s.length(); return result; }

erzeugt der Compiler selbstständig: static int totalStringLength( List strings ) { int result = 0; for ( Iterator iter = strings.iterator(); iter.hasNext(); ) result += iter.next().length(); return result; }

Da die erweiterte Schleife das Ablaufen einer Datenstruktur vereinfacht, wird ein explizit ausprogrammierter Iterator selten benötigt. Doch der Iterator kann ein Element über die remove()-Methode des Iterators löschen, was über das erweiterte for nicht möglich ist.

255

3

Datenstrukturen und Algorithmen

Einen eigenen Iterator und Iterable-Implementierung Die Konzepte Iterator und Iterable müssen sauber getrennt werden: ein Iterator ist das Objekt, das durch eine Datensammlung läuft, während ein Iterable das Objekt ist, das einen anderen Iterator liefert. Eine Klasse kann beide Schnittstellen implementieren, doch oft kommt das nicht vor. Für unser nächstes Beispiel ergibt das aber Sinn – es nutzt einem Iterator, der durch eine Sammlung läuft und immer dann, wenn er an das Ende kommt, wieder von vorne beginnt. Zunächst der Einsatz: Listing 3.25: com/tutego/insel/util/RotatingIteratorDemo.java, main int i = 0; for ( String s : new RotatingIterator( "Bohnen", "Eintopf" ) ) { System.out.println( "Toll, heute gibt es " + s ); if ( i++ == 7 ) break; }

Zur Implementierung: Listing 3.26: com/tutego/insel/util/RotatingIterator.java package com.tutego.insel.util; import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; /** * An {@link Iterator} that goes over a given {@link Collection} * but rolls back to the start when it reaches the end. * If the underling {@link Collection} contains no elements the method * {@link #hasNext()} will return {@code false}. Removing elements is * supported if the Iterator of the underlying Collection * supports {@link Iterator#remove()}. Iterating over the underling * Collection and modifying it at the same time will probably result in a * {@link ConcurrentModificationException}. */

256

3.8

Mit einem Iterator durch die Daten wandern

public class RotatingIterator implements Iterator, Iterable { private final Collection

Dann müssen alle JSP-Tags (für Scriptlets, Ausdrücke ...) in äquivalente XML-Anweisungen umgesetzt werden: Normale Syntax

XML-Syntax

expression

scriptlet

declaration





Tabelle 14.7: XML-Entsprechungen für JSP-Tags

14.8.6 Implizite Objekte für Scriptlets und JSP-Ausdrücke Der Programmcode der Scriptlets wird in die service()-Methode eines Servlets gestellt. Dort haben wir Zugriff auf einige vordefinierte Variablen, die so in der JSP-Spezifikation beschrieben sind. Sie heißen implizite Objekte. Ihr Name kann nicht verändert werden, und eigene Variablen dürfen nicht so benannt werden. Implizites Objekt

Benutzt, um ...

Typ

request

Anfragen zu verarbeiten und Eingabewerte

HttpServletRequest

wie Parameter zu lesen. response

etwas an den Client zu übermitteln, wie

HttpServletResponse

beispielsweise Header. out

in den Ausgabestrom zu schreiben.

JspWriter

application

Daten aller Anwendungen zu speichern.

ServletContext

session

Sitzungsinformationen zu speichern.

HttpSession

pageContext

Kontextdaten für eine Seite zu speichern.

PageContext

Page

ein Exemplar des Servlets anzusprechen.

Object (this)

Tabelle 14.8: Implizite JSP-Objekte

1108

14.9

Sitzungsverfolgung (Session Tracking)

Wenn wir programmtechnisch eine Ausgabe machen, schreiben wir:

Das ist gleichwertig mit . Natürlich können wir immer das out-Objekt zur Ausgabe einsetzen, doch hat dies wieder den Nachteil, dass die Visualisierung nicht vom Programmcode getrennt ist. In Scriptlet-Code kann auf diese Weise auf die Ausgabe zugegriffen werden, sodass die Scriptlets nicht immer durch JSP-Ausdrücke unterbrochen werden müssen.

14.9 Sitzungsverfolgung (Session Tracking) Jeder Auftrag an den Webserver wird unabhängig von anderen Aufträgen verwaltet. Wenn wir beispielsweise eine Seite neu laden oder einen Verweis verfolgen, weiß der Server nicht (beziehungsweise interessiert sich nicht dafür), dass die Anfrage von uns kam. Was an diesem Verhalten deutlich wird, ist das Fehlen eines Zustands. Es fehlt also die Möglichkeit, dass ein Client vom Server identifiziert wird und einem aktuellen Zustand des bidirektionalen Kommunikationsverlaufes zugeordnet werden kann. Der Zustand bezieht sich hier auf eine nichtexistente serverseitige Information. Aus diesem Grund wird HTTP auch als zustandsloses Protokoll bezeichnet. Dass dies aber nicht immer wünschenswert ist und sogar einen Nachteil darstellen kann, sehen wir an unterschiedlichen Anforderungen: Þ Ein Warenkorb für den Einkauf: In Online-Systemen wird ein Einkaufswagen gefüllt, und

unterschiedliche Webseiten informieren Kaufwillige über die Produkte. Wenn der Server die Seitenanfrage einem Client nicht zuordnen kann, ist es nicht möglich, den Warenkorb individuell zu füllen. Þ Individualisierung: Benutzer können auf sie persönlich zugeschnittene Webseiten sehen

und etwa das Wetter auf Bali auf der Startseite auswählen sowie die Fußballergebnisse von Schalke 04. Þ Demoskopie: Das System eignet sich auch für die Benutzerüberwachung. Besucht ein Be-

nutzer eine Seite mehrmals, kann der Betreiber dies erkennen und diese Information mit einem »Ist-beliebt-Faktor« verbinden. Diese Information lässt sich natürlich kommerziell gut nutzen.

14.9.1

Lösungen für die Sitzungsverfolgung

Es ist also ein System gesucht, das es dem Server erlaubt, den Client zu identifizieren, auch wenn HTTP ein zustandsloses Protokoll ist. Als Lösungen bieten sich an:

1109

14

14

JavaServer Pages und Servlets

Þ Cookies: Ein Cookie speichert eine Kennung, sodass der Server den Client erkennt und die

Informationen für ihn speziell aufbereitet. Obwohl dies in Java durch die Cookie-Klasse einfach möglich ist, hat dieser Ansatz noch einige Schwächen. Dem Servlet fällt die Aufgabe zu, aus der Cookie-Kennung die entsprechende Sitzung herauszusuchen und die Daten zu holen. Ein weiteres Problem ergibt sich dadurch, dass Cookies zwar möglich sind, aber vom Benutzer abgelehnt werden können, da dieser seine Anonymität aufs Spiel gesetzt sieht. Schaltet der Benutzer in seinem Lieblingsbrowser die Cookies aus, können wir nichts machen. Doch auch wenn Cookies verwendet werden, bleibt die Frage, wie lange der Cookie gültig sein soll. Hier ist zu überlegen, ob die Voreinstellung, dass der »Keks« nur eine Sitzung übersteht, sinnvoll ist. Þ URL-Rewriting: Da ein Servlet vom Aufrufer Parameter bekommen kann, ist es eine nette

Idee, an die URL einen Wert anzuhängen, der die aktuelle Sitzung kennzeichnet. Diese Kennung entspricht dann genau dem Wert des Cookies. Die Lösung ist simpel und funktioniert bei allen Browsern. Der Nachteil auf der Serverseite ist wiederum, dass uns die Aufgabe zufällt, der Kennung die Sitzung zuzuordnen. Zudem ist Vorsicht geboten, da diese Kennung bei jedem Verweis wieder angehängt wird. Außerdem ist es für den Benutzer sehr unschön, diese Kennungen zu sehen, zumal sie in die Bookmarks übernommen werden. Dies führt zu dem Problem, dass eine Sitzung angesprochen werden kann, die gar nicht mehr existiert. Dies ist ein sehr schwerwiegendes Problem, da die Anhängsel ja nicht wie Cookies automatisch veralten. Þ Versteckte Felder (engl. hidden fields): In HTML-Seiten lassen sich versteckte Informatio-

nen in Formularen anlegen, die beim Versenden automatisch mitgeschickt werden. Dies sieht etwa so aus:

Diese versteckten Informationen können auch genutzt werden, um eine Sitzungs-ID mitzuschicken. Der Vorteil ist, dass wir wieder keine Cookies benötigen und die URL nicht länger wird; der Nachteil ist, dass die Information immer dynamisch mit eingebaut werden muss.

14.9.2 Sitzungen in JSPs Verbindet sich ein Browser zum ersten Mal mit einer JSP, so erzeugt der Servlet-Container automatisch eine neue Sitzung und sendet standardmäßig einen Cookie zurück. Client und Server tauschen dann bei allen weiteren Anfragen diesen Cookie aus, sodass der Server den Client wiedererkennen kann. Wenn der Benutzer Cookies ablehnt, müssen wir eine zweite Implementierung anbieten, die Sitzungsinformationen an die URL anhängt, aber das soll jetzt kein Thema sein.

1110

14.10

Servlets

14.9.3 Auf Session-Dateien zurückgreifen Der Cookie, den der Server automatisch generiert, enthält eine ID, und diese ID ist mit einem Assoziativspeicher verbunden. In diesen Assoziativspeicher können wir Schlüssel/WertePaare setzen oder Werte über die Schlüssel erfragen. Für den Assoziativspeicher gibt es in der EL die implizite Variable sessionScope – sie bietet alle Daten einer Sitzung. Zusammenfassend lässt sich festhalten, dass die unterschiedlichen impliziten EL-Objekte pageScope, requestScope, sessionScope und applicationScope alle jeweils Zugriffe auf Daten in unterschiedlichen Gültigkeitsbereichen ermöglichen.

Beispiel Lege eine Variable über ein Scriptlet in den Session-Scope, und lies sie mit der EL wieder aus:

Die URL in der Session was: ${sessionScope.url}.

Die erste Zeile macht deutlich, dass für das Setzen (das Gleiche gilt für das Löschen) auf Scriptlets zurückgegriffen werden muss oder auf die JSTL, mit der das Setzen so aussehen würde: . Die Variable session ist eine JSPVariable vom Typ HttpSession und bietet Methoden wie setAttribute(), removeAttribute(). In realistischen Programmen wird eine JSP keine Daten verändern, sondern nur Servlets bzw. ein Framework.

Beispiel Beende eine Session:

Beenden wir die Sitzung – etwa nach einem Logout – nicht selbst, kommt ein Timeout und sie wird automatisch beendet.

14.10

Servlets

Wir kommen nun noch einmal auf unser Eingangsbeispiel für ein Servlet zurück, das eine einfache Ausgabe erzeugt: Listing 14.18: com/tutego/web/servlet/SchnarchServlet.java package com.tutego.web.servlet; import java.io.IOException;

1111

14

14

JavaServer Pages und Servlets

import javax.servlet.http.*; public class SchnarchServlet extends HttpServlet { @Override protected void doGet( HttpServletRequest req, HttpServletResponse res ) throws IOException { res.getWriter().println( "'Chr! Schnarch! Razong! Chr! Chr! Rapüh!'" ); res.getWriter().println( "(Disneys beste Comics, Band 5, S. 218)" ); } }

Alle Servlets implementieren die Schnittstelle javax.servlet.Servlet beziehungsweise erweitern die Klasse GenericServlet oder HttpServlet. Ein HttpServlet enthält wichtige Arbeitsmethoden für das Protokoll HTTP. Eine Erweiterung von GenericServlet ist eher unüblich, es sei denn, Nicht-HTTP-Protokolle wie FTP werden angeboten: abstrat class javax.servlet.http.HttpServlet extends GenericServlet implements Serializable Þ protected void service(HttpServletRequest req, HttpServletResponse resp)

Empfängt alle HTTP-Requests und leitet sie aufgrund der unterschiedlichen HTTP-Methoden auf die jeweiligen doXXX()-Methoden weiter. Þ protected void doDelete(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-DELETE kommt. Þ protected void doGet(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-GET kommt. Þ protected void doOptions(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-OPTIONS kommt. Þ protected void doPost(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-POST kommt. Þ Protected void doPut(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-PUT kommt. Þ protected void doTrace(HttpServletRequest req, HttpServletResponse resp)

Wird von der service()-Methode aufgerufen, wenn ein HTTP-TRACE kommt.

1112

14.10

Servlets

Lebenszyklus Spricht der Client am Webserver ein Servlet an, so bildet der Servlet-Container ein Exemplar der Servlet-Klasse (in unserem Fall SchnarchServlet) und ruft nach der Initialisierung auf dem Objekt die Methode service() auf. Die service()-Methode delegiert beim HttpServlet je nach HTTP-Methode zu den doXXX()-Methoden, also etwa doGet() bei einer GET-Anfrage. Alle doXXX()-Methoden haben zwei Parameter: Þ HttpServletRequest: Repräsentiert die Anfrage. Es lassen sich zum Beispiel Parameter oder

Header erfragen, die der Client zum Server schickt. Þ HttpServletResponse: Repräsentiert das Ergebnis. Es lassen sich zum Beispiel Daten zurück

zum Client schicken, genauso Antwort-Header (etwa Content-Type) setzen. Am wichtigsten ist die Methode getWriter(), die uns eine Referenz auf ein Writer-Objekt liefert, damit wir die HTML-Elemente für die Seite abschicken. Für Binärdaten können wir uns auch einen normalen OutputStream besorgen, damit wir zum Beispiel Bilder schicken können.

Header und Content-Typ Mit setHeader() lassen sich weitere Header setzen.

14

response.setHeader( "Content-Type", "text/html");

Die Methode erhält als Argument zwei Zeichenketten: den Header und den dazugehörigen Wert. Da der Header Content-Type jedoch so häufig benötigt wird, bietet die Schnittstelle HttpServletResponse dafür die eigene Methode setContentType() an: response.setContentType( "text/html" );

Um reine (Nur-)Textausgaben zu erzeugen, setzen wir den Content-Header mit text/plain.

Zentrale Methoden von HttpServletRequest Die interessanteren Methoden der Schnittstelle HttpServletRequest sind: interface javax.servlet.http.HttpServletRequest extends ServletRequest Þ String getHeader(String name) Þ Enumeration getHeaderNames() Þ Enumeration getHeaders(String name) Þ int getIntHeader(String name) Þ long getDateHeader(String name)

1113

14

JavaServer Pages und Servlets

Þ String getPathInfo() Þ String getPathTranslated() Þ String getQueryString() Þ String getRequestURI() Þ String getServletPath() Þ HttpSession getSession() Þ HttpSession getSession(boolean create) Þ Cookie[] getCookies()

Die pfadorientierten Methoden sind am besten an einem Beispiel erklärt: Anfrage auf /geo/new/welcome.jsp/update bei dem Kontextpfad geo getRequestURI()

/geo/new/welcome.jsp/udate

getContextPath()

/geo

getServletPath()

/new/welcome.jsp

getPathInfo()

/update

Tabelle 14.9: Pfad-Methoden und was sie liefern

Das HttpServletRequest von ServletRequest ist zentral, denn bei ServletRequest sind die wichtigen Methoden zur Erfragen der Parameter: interface javax.servlet.ServletRequest Þ String getParameter(String name) Þ Map getParameterMap() Þ Enumeration getParameterNames() Þ String[] getParameterValues(String name)

Zentrale Methoden bei HttpServletResponse Daten, die vom Servlet zurück zum Client gehen, werden über das HttpServletResponse-Objekt gesetzt. Die interessanten Methoden sind: interface javax.servlet.http.HttpServletResponse extends ServletResponse Þ void addHeader(String name, String value) Þ void addDateHeader(String name, long date)

1114

14.10

Servlets

Þ void addIntHeader(String name, int value) Þ void addCookie(Cookie cookie) Þ String encodeRedirectURL(String name) Þ void sendError(int sc) Þ void sendError(int sc, String msg) Þ void sendRedirect(String location) Þ void setDateHeader(String name, long date) Þ void setHeader(String name, String value) Þ void setIntHeader(String name, int value) Þ void setStatus(int sc)

Die zentrale Methode, um an den Ausgabestrom zu kommen, stammt aus dem Basistyp ServletResponse. Die wichtigen Methoden sind: interface javax.servlet.ServletResponse Þ ServletOutputStream getOutputStream()

14

Þ PrintWriter getWriter() Þ void setCharacterEncoding(String charset) Þ void setContentLength(int len) Þ void setContentType(String type) Þ void setLocale(Locale locale) Þ void flushBuffer() Þ void reset()

14.10.1

Servlets compilieren

Um Servlets zu übersetzen, muss das Jar-Archiv servlet.jar im Pfad sein. Dazu können wir entweder den CLASSPATH anpassen oder das Archiv einfach in das jre/lib/ext-Verzeichnis der Java SE kopieren; das Archiv liegt Tomcat im Ordner lib bei. Bei der Enterprise-Version von Java (Java EE) ist die Bibliothek schon im Pfad eingebunden. Wir erinnern uns: Eine Webapplikation besteht aus einem Verzeichnis WEB-INF mit den optionalen Verzeichnissen classes und lib. Die übersetzten Klassen müssen in das Verzeichnis classes. Falls das Servlet in einem Paket liegt, muss diese Paketstruktur natürlich auch auf die Verzeichnisstruktur abgebildet werden.

1115

14

JavaServer Pages und Servlets

Die Java EE Version von Eclipse bindet das Archiv selbstständig ein und übersetzt die Klasse automatisch im richtigen Verzeichnis. Wir legen einfach das Servlet im Quellcodeordner ab, und es wird somit automatisch unter WEB-INF/classes compiliert.

14.10.2

Servlet-Mapping

Um nun den Server zur Ausführung unserer Servlets zu bewegen, gibt es zwei Möglichkeiten: Þ Deployment-Descriptor web.xml im Verzeichnis WEB-INF jeder Webapplikation Þ Standard-Servlet-Mapping unter /servlet/

Für ein Servlet, das über File 폷 New 폷 Other... 폷 Web 폷 Servlet angelegt wird, stehen die Einträge automatisch im Deployment-Descriptor web.xml.

Der Deployment-Descriptor web.xml Soll der Invoker nicht zum Einsatz kommen, müssen wir für jede Webapplikation eine Datei web.xml im Verzeichnis WEB-INF anlegen. Sie dient dazu, die Webapplikation zu vervollständigen. Der Deployment-Descriptor zählt die Servlets auf und weist ihnen Pfade zu. web.xml ist eine klassische XML-Datei, die validiert wird. Im Wurzelelement finden sich jetzt die spannenden Einträge, wie für unser SchnarchServlet: Listing 14.19: WEB-INF/web.xml

SchnarchServlet com.tutego.web.servlet.SchnarchServlet

SchnarchServlet /SchnarchServlet

1116

14.10

Servlets

index.html index.jsp

Damit ist alles komplett. Die Angabe von http://localhost:8080/web/SchnarchServlet präsentiert unser Servlet und seine Ausgabe.

Standard-Servlet-Mapping Im Fall des Standard-Servlet-Mappings wird ein Servlet unter der URI servlet/SERVLETNAME zugänglich. Um diese Möglichkeit einzuschalten, müssen wir die globale Konfigurationsdatei conf/web.xml modifizieren und an zwei Stellen das sogenannte Invoker-Servlet aktivieren.

14.10.3

Der Lebenszyklus eines Servlets

Der Container für Servlets registriert eine Anfrage durch den Client und lädt das Servlet in den Speicher. Da Servlets normale Klassen sind, übernimmt ein spezieller Klassenlader diese Aufgabe. Die Abarbeitung findet anschließend in einem Thread statt, der die Methoden des Servlet-Objekts aufruft. Über die Schnittstelle Servlet werden drei elementare Methoden für die Initialisierung, die Abarbeitung der Anfragen und die Beendigung vorgeschrieben. Der Ablauf dieser Methoden heißt Lebenszyklus eines Servlets. Die folgende Aufzählung zeigt alle Methoden, die die Schnittstelle Servlet für alle Java-Servlets vorschreibt. interface javax.servlet.Servlet Þ void init(ServletConfig config)

Wird zu Beginn eines Dienstes aufgerufen. Þ void service(ServletRequest req, ServletResponse res)

Der Container leitet die Anfrage an das Servlet an diese Stelle. Þ void destroy()

Wird am Ende eines Servlets vom Container genau einmal aufgerufen. Þ ServletConfig getServletConfig()

Liefert ein ServletConfig-Objekt, das Initialisierungs- und Startparameter kapselt. Þ String getServletInfo()

Liefert Informationen über das Servlet, wie Autor, Version und Copyright.

1117

14

14

JavaServer Pages und Servlets

Ein HttpServlet ist eine besondere Implementierung der Servlet-Schnittstelle. Die Klasse implementiert service(ServletRequest req, ServletResponse res), doch leitet es dann an service(HttpServletRequest req, HttpServletResponse resp) weiter, was wiederum zu den doXXX()Methoden geht.

14.10.4

Mehrere Anfragen beim Servlet und die Thread-Sicherheit

In der Regel nutzt der Container pro Anfrage einen Thread, der dann die service()-Methode des Servlet-Objekts betritt und die Anfrage bearbeitet. Es gibt demnach für mehrere Aufträge keine unterschiedlichen Exemplare des Servlets, sondern lediglich unterschiedliche Threads bei einem Servlet-Exemplar. Aus diesem Grund ist zu bedenken, dass die Dienste seiteneffektfrei sein müssen. Es ist unsere Aufgabe, die Methode so weit zu synchronisieren, dass es keine negativen Auswirkungen der Parallelität gibt. Die Synchronisation wirkt sich natürlich auf die Ausführungsgeschwindigkeit nachteilig aus, sodass auf die passende Granularität zu achten ist.

14.10.5

Servlets und Sessions

JSP gehören ganz automatisch zu einer Sitzung. Bei Servlets ist dies nicht der Fall. Wir benötigen also eine Möglichkeit, die uns Zugriff auf die Sitzung gibt. Das ist die Methode getSession() in dem aktuellen HttpServletRequest-Objekt. Sie liefert null, wenn noch keine Sitzung verwaltet wird, da der Client zum ersten Mal auf ein Servlet zugreift. Wenn der Client zum ersten Mal zugreift, müssen wir dies erkennen und automatisch ein Sitzungsobjekt initiieren. Für diese Aufgabe gibt es eine Abkürzung, die wie folgt aussieht: public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { HttpSession session = request.getSession( true ); ... out = response.getWriter(); ... }

Wir müssen also nicht request.getSession() != null überprüfen und dann manuell ein Sitzungsobjekt aufbauen, sondern können getSession(true) nutzen, das automatisch eine HttpSession anlegt.

1118

14.10

Servlets

interface javax.servlet.http.HttpServletRequest extends ServletRequest Þ HttpSession getSession()

Liefert die aktuelle Session, die mit der Anfrage assoziiert ist. Wenn es keine Session gab, wird automatisch eine angelegt. Þ HttpSession getSession(boolean create)

Wie getSession(), nur dass getSession(false) nicht automatisch eine neue Session anlegt, wenn es keine mit der Sitzung assoziierte gibt.

14.10.6

Weiterleiten und Einbinden von Servlet-Inhalten

Mit einem RequestDispatcher-Objekt kann sich ein Servlet zu einem anderen Servlet verbinden, oder es können Ausgaben von anderen Servlets in den aktuellen Datenstrom mit eingebunden werden. Die angebotenen Methoden vom Dispatcher sind include() und forward(). Um an den aktuellen RequestDispatcher zu gelangen, wird die Methode getServletDispatcher() aufgerufen, die eine Methode der Klasse ServletContext ist. Die Webseite, die eingebunden ist oder an die weitergeleitet wird, ist durch eine URL spezifiziert, die die Methode getServletDispatcher() als Argument bekommt. Den Methoden forward() und include() werden dann request und response übergeben. Ein Servlet soll eine einfache Fußzeile generieren, die ein anderes Servlet einbindet: public class FooterServlet extends HttpServlet { public void service( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { response.setContentType( "text/html" ); PrintWriter out = response.getWriter(); out.println( "Copyright © 2012" ); } }

Damit ein zweites Servlet die Ausgabe einbinden kann, inkludieren wir seine Ausgabe. Konzentrieren wir uns auf die service()-Methode: public void service( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException

1119

14

14

JavaServer Pages und Servlets

{ response.setContentType( "text/html" ); PrintWriter out = response.getWriter(); ServletContext con = getServletContext(); out.println( "Der Telefonmann meldet sich wieder" ); RequestDispatcher rq = con.getRequestDispatcher( "FooterServlet" ); rq.include( request, response ); } interface javax.servlet.RequestDispatcher Þ void forward(ServletRequest request,ServletResponse response)

throws ServletException, IOException

Die Anfrage wird an ein anderes Servlet, eine andere JSP oder eine andere HTML-Seite weitergeleitet. Eine ServletException kann auftreten, wenn das Ziel eine Ausnahme auslöst. Þ void include(ServletRequest request,ServletResponse response)

throws ServletException, IOException

Bindet den Inhalt eines Servlets, einer JSP oder einer Webseite in den aktuellen Datenstrom (»response«) ein. Das ServletResponse-Objekt kann keinen Header setzen (um zum Beispiel den Statuscode zu ändern). Änderungen werden ignoriert.

14.11

Zum Weiterlesen

Zum Thema Webtechnologien hat Oracle viel auf den Webseiten http://java.sun.com/ products/jsp/ und http://www.oracle.com/technetwork/java/index-jsp-135475.html veröffentlicht. Allerdings reicht das Wissen nicht aus, um wirklich moderne Webapplikationen zu entwickeln. Vielmehr sind JSP und Servlets die Basis jeder HTTP-getriebenen Anwendung. Tag-Libraries sind die Gewürze, die JSPs erst richtig schmackhaft machen. Servlets dienen (neben der seltenen Generierung von binären Dokumenten) lediglich dazu, als Front-Controller (http://java.sun.com/blueprints/corej2eepatterns/Patterns/FrontController.html) Anfragen vom Client entgegenzunehmen und an die Ansicht weiterzuleiten. Diese Weiterleitung selbst zu schreiben, erübrigt sich mit der Masse an MVC-Frameworks; die Notwendigkeit beschreibt das Dokument »Designing Enterprise Applications« (http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/web-tier/web-tier5.html). Als Standard-Framework hat sich JavaServer Faces (http://www.oracle.com/technetwork/java/javaee/javaserverfaces139869.html) herauskristallisiert. Webanwendungen nur mit einem einfachen Editor zu schreiben, ist aufwändig und fehlerträchtig. Entwickler-Teams sollten sich von Tools unter die Arme greifen lassen, um zum Bei-

1120

14.11

Zum Weiterlesen

spiel den Pageflow visuell zu modellieren und die Konfigurationsdateien schnell über Wizards zu generieren. Im Eclipse-Umfeld macht das JavaServer Faces Tooling Project (http:// www.eclipse.org/webtools/jsf/), das ein Teil vom WTP ist, eine gute Figur. Webanwendungen wirken auf den ersten Blick – mit einigen Klicks – so, als wären sie leicht zu entwickeln, sind aber bei genauerem Hinsehen in puncto Lastverteilung/Clustering (Tomcats Clustering wird immer besser) und Ausfallsicherheit bei großen Sites mit Sicherheit nicht trivial. Besonders die Sicherheit (Web Application Security) vernachlässigen Entwickler gern bis zum Schluss, um dann Teile, wie unzureichend geprüfte Eingaben oder Authentifizierung, anpassen zu müssen. Peinlich war zum Beispiel die Situation für die Firma Gateway, die jedem registrierten Benutzer eine sechsstellige ID zuwies und als Identifizierung an den Server schickte. Natürlich war es kein großes Problem, diese ID anders zu generieren und Informationen anderer Benutzer abzurufen. Der zeitlose Report »The Ten Most Critical Web Application Security Vulnerabilities« (http://prdownloads.sourceforge.net/owasp/OWASPTopTen2004.pdf) sensibilisiert für die Problematik. Injizierungsfehler (wie SQL-Injection, mit Beispielen etwa unter http:// www.unixwiz.net/techtips/sql-injection.html beschrieben) und Cross-Site-Scripting (http:// de.wikipedia.org/wiki/Cross-Site_Scripting) sind mittlerweile Fehler-Klassiker, die es zu vermeiden gilt.

14

1121

Kapitel 15 Applets »Mode ist, was man selbst trägt. Altmodisch ist, was die anderen tragen.« – Oscar Wilde (1854–1900)

Applets sind kleine Java-Programme, die in einem Webbrowser ablaufen. Sie gehören zu den Java-Programmen der ersten Stunde. Referenziert eine Web-Seite ein Applet, so startet der Browser eine virtuelle Maschine und führt das Applet aus.

15.1

Applets in der Wiege von Java 15

Haben sich in den Anfangsjahren die Browser-Hersteller selbst darum gekümmert, die Applets in ihren Browsern um Laufen zu bringen, hat sich das heute gewandelt. Oracle liefert die Java Plug-In Technology (kurz Java Plug-In) aus, was die Browser bei der Ausführung von Applets auf den gleichen Stand bringt. Das hat den Vorteil, dass die Java-Technologie auch zum Internet Explorer kommt, da ja Microsoft seine JVM nicht mehr aktualisiert. (Microsoft lieferte für Windows XP immerhin noch eine eigene JVM aus, obwohl sie auf dem Stand von Java 1.1 stehen blieb, aber XP ist ja auch nicht mehr Stand der Dinge.) Früher lief die virtuelle Maschine im Browser selbst, in neuen Java-Versionen läuft die Java-Anwendung in einem eigenen Prozess.

15.1.1

Applets heute

Obwohl Applets Java an die Spitze der Programmiersprachen brachten, sind sie heute nur noch selten zu sehen. Es gibt zwar Ausnahmen, wie den Routenplaner http://www. de.map24.com/ oder diverse Aktien-Chart-Analysen, doch im Allgemeinen sind Applets von öffentlichen Webseiten weitestgehend verschwunden. Der Grund, warum Java-Applets weniger attraktiv für den Konsumenten sind, liegt nicht darin, dass die clientseitige Darstellung und Logik unwichtig geworden ist, sondern vielmehr an anderen Gründen:

1123

15

Applets

Þ Mit HTML, CSS sowie JavaScript lassen sich heutzutage viele Aufgaben lösen, die 1995 un-

lösbar waren. Dagegen wirken compilierte Java-Programme nicht gerade wie Raketentechnik. Während bei Java-Applets erst eine JVM gestartet werden muss, was natürlich eine gewisse Zeit kostet, sind JavaScript und HTML sofort bereit. Starke JavaScript-Bibliotheken ermöglichen tolle Effekte und mit performanten JavaScript-Engines wie der quelloffenen V8 von Google oder TraceMonkey der Mozilla Foundation eine schnelle Verarbeitung. Þ Java ist als allgemeine Programmiersprache entworfen worden, aber nicht als einfache Pro-

grammiersprache für grafische Effekte. Hier liegt der Vorteil von Adobe Flash oder auch Microsoft Silverlight. Mit starken Tools können Designer großartige Oberflächen entwerfen, und die Verbreitung des Flash-Players ist phänomenal.1 Zudem erweitert Adobe die Multimedia-Technologie Flash, die Programmiersprache ActionScript sowie die Produktpalette zur Entwicklung kontinuierlich. Oracle versucht mit der Plattform JavaFX (http:// www.oracle.com/technetwork/java/javafx/overview/index.html) dagegenzusetzen, aber JavaFX kann wohl eher Swing für Rich-Clients ablösen und wird wohl im Internet ein Exot bleiben. Þ Ist Java installiert, steht auf den Rechnern eine moderne und schnelle Java-Laufzeitumge-

bung für Applets über ein Browser-Plugin zur Verfügung. Das Problem beginnt aber, wenn Anwender Applets nutzen möchten (oder müssen), aber kein JVM installiert ist. HTML, CSS und JavaScript sind immer Teil des Browsers, und in der Regel ist auch Flash installiert. Dagegen ist der Bezug einer JVM langwierig, und viele Megabyte Daten müssen vom OracleServer geladen werden. (Microsoft lieferte zwar früher für den IE eine JVM mit, doch wegen immer wieder auftretender Sicherheitsprobleme sollten Anwender Microsofts JVM deinstallieren. Microsoft liefert für Vista kein Java mit aus, und daher muss sowieso Oracles JVM installiert werden.2)

Hinweis Wir wollen im Folgenden davon ausgehen, dass nicht die Java-Laufzeitumgebung 1.1 von Microsoft installiert ist, sondern ein vollwertiges Java von Oracle. Ist Oracles JVM installiert, ersetzt sie – falls diese installiert war – die JVM von Microsoft, und aktuelle Java-Programme lassen sich ausführen.

15.1.2

(J)Applet und Applikationen

Verfügen normale Applikationen, die von der Kommandozeile gestartet werden, über eine statische main()-Methode, ist das bei Applets anders. Sie erweitern zwingend die Klasse 1

http://www.adobe.com/products/player_census/flashplayer/version_penetration.html

2

http://windowshelp.microsoft.com/Windows/en-US/Help/59c3a93d-1342-43a6-a01a-f720c7a17ffc1033.mspx

1124

15.1

Applets in der Wiege von Java

javax.swing.JApplet oder java.applet.Applet und implementieren Callback-Methoden statt

einer main()-Methode. (Hybride Programme, die sowohl Applikation als auch Applet sind, sind ebenfalls möglich. Diese Sorte Programm erweitert einfach die Klasse (J)Applet und implementiert eine statische main()-Methode.) Ein Applet unterliegt hohen Sicherheitsbestimmungen – eine auf dem Client-Rechner liegende Datei kann zum Beispiel nicht gelöscht werden, und schon der lesende Zugriff ist unzulässig. Vom Sicherheitsstandpunkt aus betrachtet, kontrolliert der SecurityManager die Handlungen der Software.

15.1.3

Das erste Hallo-Applet

Ein Programm wird leicht zu einem Applet, wenn es die Klasse Applet erweitert. Als Einstieg soll ein kleines Beispiel-Applet dienen. Listing 15.1: HelloWorldApplet.java import java.applet.Applet; import java.awt.Graphics;

15

public class HelloWorldApplet extends Applet { /* @Override */ public void paint( Graphics g ) { g.drawString( "Hallo Welt!", 50, 25 ); } }

Die beiden ersten import-Anweisungen binden die notwendigen Informationen über Applets und über Zeichenmethoden ein. Das HelloWorldApplet erweitert die Klasse Applet, denn so wird ein Applet erzeugt. Eine statische main()-Methode kommt nicht vor, und es muss eine Methode paint() überschrieben werden, die den Bildschirmaufbau übernimmt.3 Der Webbrowser oder Applet-Viewer ruft diese Methode per Callback auf.

3

Das gilt nicht für JApplet. Dort ist das Überschreiben von paint() oder paintComponent() unüblich, da im Allgemeinen eine eigene, sich zeichnende Komponente auf das JApplet gesetzt wird.

1125

15

Applets

Abbildung 15.1: Das erste Applet im Browser

Damit der Viewer weiß, was zu machen ist, gibt der HTML-Code einen Hinweis auf die Klasse, die in die Seite eingebettet wird. Dies wird über ein spezielles Tag erreicht. Listing 15.2: index.html



Neben dem Namen der Klasse übermittelt das Applet-Tag der virtuellen Maschine auch die Maße des Fensters, in dem das Applet zeichnen kann. Ab Java 1.1 lassen sich gepackte Dateien im Jar-Format übermitteln. Die Angabe der Klasse in der Code-Anweisung sollte keine Leerzeichen beinhalten. Die Endung .class ist nicht in jedem Browser erforderlich, wird aber empfohlen.

Abbildung 15.2: UML-Diagramm von Applet und AppletContext

1126

15.2

Die Applet-API

Fehler in Applets finden Werden Applets vom Browser nicht ausgeführt, weil es Fehler in unserem Programm gibt, so bietet die Java Console (http://www.java.com/en/download/help/javaconsole.xml) die Möglichkeit, Log-Informationen anzusehen und so die Fehler genauer zu studieren.

15.2

Die Applet-API

15.2.1

Die Zyklen eines Applets

Beim Start eines Applets werden unterschiedliche Methoden vom Browser automatisch aufgerufen. Es beginnt mit dem Aufruf der Methode init(). Dort sollten Initialisierungen erfolgen. init() wird nur einmal aufgerufen, wenn die Seite vom Browser geladen wird. Nach der Initialisierung folgt ein Wechsel der Methoden start() und stop() immer dann, wenn ein Applet im Browser sichtbar ist oder von der Seite verschwindet, etwa wenn der Anwender über die Schieberegler einen anderen Bereich auswählt, in dem das Applet nicht liegt. Beim Verlassen der Seite wird abschließend destroy() aufgerufen. Dort können Ressourcen freigegeben werden.

15 15.2.2

Parameter an das Applet übergeben

Dem Applet können Parameter im Applet-Tag übergeben werden. Dazu wird im -Element ein -Element eingebettet. Im Folgenden zeichnet ein Applet einen grünen oder roten Kasten, in Abhängigkeit davon, ob eine URL korrekt aufgebaut ist.

Abbildung 15.3: Unsere Beispielanwendung appletCheckUrl.html Listing 15.3: appletCheckUrl.html

Java-Seminare

1127

15

Applets

Java-Seminare Falsch



Das Applet nimmt den Parameter an und prüft den gültigen Aufbau der URL über eine MalformedURLException. Listing 15.4: CheckUrlApplet.java import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import java.net.MalformedURLException; import java.net.URL; public class CheckUrlApplet extends Applet { private boolean urlOk = false; @Override public void init() { try { urlOk = new URL( getParameter("url" ) ) != null; } catch ( MalformedURLException e ) { /* urlOk is false */ } } @Override public void paint( Graphics g ) { g.setColor( urlOk ? Color.GREEN : Color.RED ); g.fillRect( 0, 0, 10, 10 ); } }

Interessant wäre natürlich, wenn das Applet gleich die URL auf Erreichbarkeit prüfen würde. Relativ einfach ergeben sich dann folgende Zeilen:

1128

15.2

Die Applet-API

try { urlOk = ((HttpURLConnection)new URL( getParameter("url" ) ). openConnection()).getResponseCode() == HttpURLConnection.HTTP_OK; } catch ( IOException e ) { /* urlOk is false */ }

Doch bei der Prüfung von üblichen Links kommt es zu einem Fehler! Aus Sicherheitsgründen kann ein Applet nur auf den Rechner zugreifen, von dem es geladen wurde, nicht auf andere. Ohne explizite Sicherheitserweiterungen kann so ein allgemeines Applet zum Prüfen eines Links nicht geschrieben werden.

15.2.3

Wie das Applet den Browser-Inhalt ändern kann *

Das Applet kann mit showDocument() auf den Inhalt der Seite Einfluss nehmen. So lassen sich Applets bauen, die eine Baumstruktur der Seite anzeigen und dann zum Inhalt verweisen, falls eine Seite ausgewählt wird. Verwendet werden hier die Methoden von AppletContext. In Kurzform: getAppletContext().showDocument( new URL("http://tutego.com/") );

Oder, falls ein spezieller Frame mit Namen angesprochen ist:

15

getAppletContext().showDocument( new URL("http://tutego.org"), "Framename" ); class java.applet.Applet extends Panel Þ AppletContext getAppletContext()

Liefert den Kontext des Applets. Dieser Kontext erlaubt es dem Applet herauszufinden, in welcher Umgebung, also auf welcher Web-Seite, es sich bewegt. interface java.applet.AppletContext Þ void showDocument(URL url)

Ersetzt den Inhalt auf der aktuellen Seite durch eine neue Seite von der angegebenen URL. Þ void showDocument(URL url, String target)

Ersetzt den Inhalt auf der aktuellen Seite durch eine neue Seite von der angegebenen URL. Dabei wird das Dokument in einem Frame abgelegt, dessen Name zusätzlich festgelegt ist. Für target sind erlaubt: _self (Seite, die das Applet enthält), _parent (bettet die neue Seite in die Vaterseite des Applets ein; falls diese nicht existiert, verhält es sich wie _self), _top (im Top-Level-Frame anzeigen; falls dieser nicht existiert, wie _self), _blank (erzeugt ein neues Fenster), und wenn der Name nicht mit den Konstanten übereinstimmt, wird die Anzeige in einen Frame gelegt, der diesen Namen trägt.

1129

15

Applets

15.2.4

Den Ursprung des Applets erfragen

Greift ein Applet auf Daten des Servers zu und ist ihm die Adresse nicht bekannt, so kann es nachfragen. Die Applet-Klasse stellt die Methoden getCodeBase() und getDocumentBase() zur Verfügung. class java.applet.Applet extends Panel Þ URL getCodeBase()

Liefert die Basis-URL des Applets. Þ URL getDocumentBase()

Liefert die URL der Webseite, die das Applet enthält. Auf dem URL-Objekt liefert getHost() eine String-Repräsentation der URL. So kommen wir mit der Methode getCodeBase().getHost() an den Hostnamen und auch an die Daten des Servers.

Beispiel Applets können problemlos von Webseiten geklaut werden. Um dem einen Riegel vorzuschieben, können wir verlangen, dass die Zeichenkette von getDocumentBase().getHost() immer die Webseite unseres Servers repräsentiert. String web = getDocumentBase().getHost(); if ( ! "www.tutego.com".equals(web) ) { // hier meckern, dass was nicht stimmt. }

Wir könnten die Überprüfung auch über ein InetAddress-Objekt realisieren. class java.net.URL implements Serializable, Comparable Þ String getHost()

Liefert den Hostnamen des URL-Objekts. Handelt es sich um das »file«-Protokoll, so ist der Rückgabewert ein leerer String.

Beispiel Baue eine URL-Verbindung zu einer Grafikdatei auf. Wir benutzen hier zunächst die Methode getDocumentBase(), um an die URL des Servers zu gelangen, und anschließend den URL-Konstruktor, der uns relativ zur Basisadresse eine Pfadangabe erlaubt.

1130

15.2

Die Applet-API

Beispiel (Forts.) URL u1 = getDocumentBase(); try { URL u2 = new URL( u1, "image.gif" ); ... } catch ( MalformedURLException e ) { ... }

15.2.5

Datenaustausch zwischen Applets *

Sind mehrere Applets auf einer Webseite untergebracht, gibt es Fälle, in denen die Applets Daten austauschen wollen. Zwei Lösungen sind populär: Þ Da alle Applets in einer einzigen JVM laufen, lässt sich über statische Attribute auf die an-

deren Elemente zugreifen. Dies spricht jedoch gegen die Datenkapselung und ist sehr unfein. Diese Technik hat einen weiteren Schwachpunkt: Statische Variablen hängen eng mit dem Klassenlader zusammen, und hier traten in der Vergangenheit bei einigen Browsern Probleme auf. Þ Eleganter ist da schon die Möglichkeit über die Schnittstelle AppletContext, die es ermög-

licht, einen Verweis auf das Applet über den Namen zu bekommen. class java.applet.Applet extends Panel Þ AppletContext getAppletContext()

Bestimmt die Umgebung eines Applets.

Applets über den AppletContext erfragen Mit dem AppletContext gibt es zwei Möglichkeiten, an das Applet zu gelangen: Þ das Applet über einen Namen ansprechen Þ eine Aufzählung aller Applets erfragen

Um einen Namen zu vergeben, wird das name-Attribut im -Tag genutzt, etwa so:

1131

15

15

Applets

Eine Verbindung der Methoden getAppletContext() aus Applet und getApplet() aus AppletContext führt zu folgender Zeile: Applet anotherApplet = applet.getAppletContext().getApplet( "applet" );

Die zweite Variante war, sich mit getApplets() eine Enumeration aller Applets einer Seite zu besorgen: Applet otherApplet = null; Enumeration applets = getAppletContext.getApplets(); while ( applets.hasMoreElements() ) { otherApplet = (Applet) applets.nextElement(); if ( otherApplet != this ) break; // Jetzt können wir etwas mit dem anderen Applet machen // if ( otherApplet instanceof Applet2 ) //

...

} interface java.applet.AppletContext Þ Applet getApplet(String name)

Sucht das Applet namens name in dem Dokument, das durch den AppletContext gegeben ist. Der Name kann durch das HTML-Tag gesetzt sein. Falls kein Applet dieses Namens existiert, liefert die Methode null. Þ Enumeration getApplets()

Findet alle Applets, die durch AppletContext angegeben sind.

Praktische Kommunikation Das Applet können wir gegebenenfalls in eine Unterklasse casten. Dann lassen sich alle Methoden aufrufen und die Variablen auslesen. Leider funktionieren beide vorgestellten Methoden nur, wenn die Applets in dem gleichen Frame liegen. Liegen sie in verschiedenen Frames, findet zumindest die Netscape-Methode getApplet() das Applet leider nicht. Hier bleibt aber noch die Variante über statische Variablen übrig. Eine weitere Möglichkeit, Applets über verschiedene Frames kommunizieren zu lassen, führt über eine JavaScript-Funktion. Sie fungiert als Brücke, was etwa so aussieht: top.frames[1].document.applet["applet"].method().

1132

15.2

Die Applet-API

Das folgende Beispiel zeigt zwei Applets, Applet1 und Applet2, auf einer Webseite. Zunächst der HTML-Code: Listing 15.5: TwoAppletsCommunication.html





Es folgen die Implementierungen für die beiden Applets: Listing 15.6: Applet1.java import java.applet.Applet; import java.awt.*; public class Applet1 extends Applet

15

{ private TextField inputText = new TextField( "", 10 ); public void init() { add( inputText ); add( new Button( "Sende an Applet2" ) ); } public boolean action( Event ev, Object arg ) { if ( ev.target instanceof Button ) { Applet2 applet2 = (Applet2) getAppletContext().getApplet( "applet2" ); if ( applet2 != null ) { applet2.appendTheText( inputText.getText().trim() ); return true;

1133

15

Applets

} } return false; } } Listing 15.7: Applet2.java import java.applet.Applet; import java.awt.TextArea; public class Applet2 extends Applet { private TextArea textBox = new TextArea( 5, 40 ); public void init() { add( textBox ); } public void appendTheText( String s ) { textBox.append( s + "\n" ); } }

Da bei verschiedenen Frames getAppletContext() jedoch das andere Applet nicht zurückgeben muss, bleibt nur noch die Variante über die statische Variable. Glücklicherweise lassen sich mit Beobachtermustern aus auch elegante Benachrichtigungen realisieren.

15.2.6

Was ein Applet alles darf *

Ein Applet unterliegt bestimmten Sicherheitsbeschränkungen, die eine Java-Security-Einheit überprüft. In Kapitel 23, »Sicherheitskonzepte«, werden wir diese näher beleuchten. Viele der bekannten Fehler in Java, die potenzielle Sicherheitslücken darstellen, sind mittlerweile behoben. Schon das Auffinden setzt eine gründliche Kenntnis der Java-Quelltexte voraus, beispielsweise der Fehler mit der Host-Adresse: Wenn ein Benutzer ein Applet von tutego.com liest, darf dieses Applet nur mit diesem Host eine Verbindung aufbauen und mit

1134

15.2

Die Applet-API

keinem anderen. Doch leider gab es in den Quelltexten von Java einen Fehler, sodass das Applet nur den Rechnernamen des Hosts vergleicht, nicht aber die IP-Adresse. Ein bösartiges Applet kann nun dem DNS (Domain Name Server) eine falsche Zuordnung von Rechnername und IP-Adresse vorspielen, und nun verhält sich tutego.com wie www.ganz-boese.com.

15.2.7

Ist Java im Browser aktiviert? *

Wenn unser Browser Java-Applets ausführen soll, aber Java gar nicht aktiviert ist, dann lassen sich einige interaktive Benutzeraktionen nicht durchführen. Wir sollten daher zumindest eine Meldung anbieten, dass der Browser Java gerade nicht aktiviert hat. Dies kann beabsichtigt oder nicht beabsichtigt sein. Natürlich kommt Java dafür nicht infrage, aber eine SkriptSprache mit einem ähnlichen Namen: JavaScript. Ab JavaScript-Version 1.1 bietet uns der Interpreter die Funktion javaEnabled() an, sodass wir eine Weiterschaltung vornehmen können: if ( !navigator.javaEnabled() ) { self.location.href = "nix_mit_java.html"; }

Für diese Lösung muss natürlich JavaScript aktiviert sein. Für einige Surfer ist selbst dies schon eine Sicherheitslücke, und wenn JavaScript deaktiviert ist, lässt sich hier nichts mehr machen. Falls JavaScript aktiviert ist, kommen wir dem Benutzer einen Schritt entgegen, sodass er nicht mehr manuell angeben muss, ob Java aktiv ist oder nicht. Von dieser Technik sollten wir auch Gebrauch machen, denn nicht immer hat der Benutzer bewusst Java abgeschaltet. Im Beispiel oben haben wir eine Seite angesteuert, wobei natürlich andere Anweisungen denkbar sind. Doch diese Form ist sinnvoll, denn wir können Benutzern eine Kurzbeschreibung darüber liefern, wie Java im Browser aktiviert wird. Zusammen mit der Browservariante ist eine browsergenaue Beschreibung einsetzbar.

15.2.8

Applet unter Firefox (Netscape) oder Microsoft Internet Explorer? *

Kann der Browser ein Applet aus irgendwelchen Gründen nicht ausführen, so sind die Meldungen an den Benutzer meist mager. Oft beschränken sie sich auf eine Exception-Angabe in der Statuszeile. Dies mag keiner mehr sehen. Doch leider verschärfen inkompatible Browser die Situation. Was hier Abhilfe schafft, ist ein kleines Programm, das zunächst herausfindet, auf welchem Browser das Applet läuft. Dann können unter Umständen browser- und versionsabhängige Varianten ausgeführt werden. Wir verwenden einen Trick, der auch beim Erkennen von Prozessortypen angewendet wird: Wir versuchen, Klassen zu laden oder Methoden aufzurufen, die es für den jeweils anderen

1135

15

15

Applets

Browser nicht gibt. Der Internet Explorer hat zum Beispiel eine private Klasse com.ms. applet.GenericAppletContext, und Mozilla hat eine Klasse netscape.applet.MozillaAppletContext. Löst die JVM beim Laden der Klasse eine Exception aus, wissen wir Bescheid, um welchen Browser es sich handelt. Versuchen wir, über die selbst gebastelten Methoden isNetscape() und isMicrosoft() etwas über unsere Laufzeitumgebung herauszufinden. Listing 15.8: BrowserDetector.java import java.applet.Applet; public class BrowserDetector extends Applet { public void init() { if ( isNetscape() ) System.out.println( "Netscape, Firefox, ... Browser." ); if ( isMicrosoft() ) System.out.println( "Microsoft Browser." ); } public static boolean isNetscape() { try { Class.forName( "netscape.applet.MozillaAppletContext" ); } catch ( ClassNotFoundException e ) { return false; } return true; } public static boolean isMicrosoft() { try { Class.forName( "com.ms.applet.GenericAppletContext" ); } catch ( ClassNotFoundException e ) { return false; } return true; } }

1136

15.3

Webstart

Die Idee lässt sich natürlich auch anwenden, um Java-Versionen zu testen; es wird einfach eine Klasse erfragt, die bei einer neuen Java-Version hinzugekommen ist, bei Java 2 etwa Point2D.

Tipp Da nicht immer sichergestellt sein kann, dass Java in einer vernünftigen Version (>= 1.2) auf dem Client-Rechner der Benutzer installiert ist, lässt sich ein Test-Applet vorschalten, das zunächst die Java-Version prüft. Anschließend kann dieses Eingangs-Applet über getAppletContext().showDocument() auf eine andere Seite mit einem Applet verweisen. Für unterschiedliche Browser und Java-Installationen können somit unterschiedliche Applets auf die Situation eingehen.

15.3

Webstart

Bevor Software auf dem Rechner läuft, wird sie in der Regel installiert. Dazu legen die Hersteller der Software ein spezielles Installationsprogramm bei – unter Windows oft die InstallShields. Das Installationsprogramm legt passende Verzeichnisse an und initialisiert etwa die Registrierdatenbank unter Windows. Etwas anders sieht das bei Java-Programmen aus. Die Installation erfordert zuerst eine Java-Laufzeitumgebung. Anschließend kann das Programm entpackt und gestartet werden. Wünschenswert ist jedoch eine Art Umgebung, wie sie bei Java-Applets definiert ist. Ein Java-Applet läuft innerhalb eines Browsers in einem speziellen Sicherheitsmodus, und es wäre günstig, wenn dies auch für alle anderen Applikationen möglich wäre. Das bedeutet: Eine beliebige Applikation kann von einer Webseite geladen und auf dem lokalen Rechner ausgeführt werden. Die Applikation soll sich nicht von anderen Applikationen unterscheiden, die lokal installiert sind. Damit der Start möglich ist, ist die Oracle-Technologie Webstart nötig. Webstart deckt die Bereiche Installation, Start und Update durch ein eigenes Protokoll ab, das Java Network Launcher Protocol (JNLP). Die Technologie wurde auf der JavaOne 2000 erstmals vorgestellt. Neben der offiziellen Webseite widmet sich auch die (nicht mehr ganz so frische) Unofficial Java Web Start/JNLP FAQ unter http://lopica.sourceforge.net/faq.html dem Thema.

15.4

Zum Weiterlesen

Erstaunlicherweise gab es unter Java 6 Update 10 noch größere Änderungen in der AppletProgrammierung. So können Applets mittlerweile aus Web-Seiten herausgezogen werden,

1137

15

15

Applets

und Applets können problemlos den DOM-Baum der Seite modifizieren, in der sie laufen. Mehr Informationen zu diesen Themen gibt es in Oracles Java-Tutorial unter http://download.oracle.com/javase/tutorial/deployment/applet/index.html. Es gibt auch von Oracle eine JavaScript-API, die das korrekte HTML für ein Applet generiert, sodass Entwickler nicht das eigentlich veraltete nutzen müssen – Weiteres dazu unter http://download.oracle.com/ javase/6/docs/technotes/guides/jweb/deployment_advice.html#deplToolkit.

1138

Kapitel 16 Datenbankmanagement mit JDBC »Alle Entwicklung ist bis jetzt nichts weiter als ein Taumeln von einem Irrtum in den anderen.« – Henrik Ibsen (1828–1906)

Das Sammeln, Zugreifen auf und Verwalten von Informationen ist im »Informationszeitalter« für die Wirtschaft eine der zentralen Säulen. Während früher Informationen auf Papier gebracht wurden, bietet die EDV hierfür Datenbankverwaltungssysteme (DBMS, engl. database management systems) an. Diese arbeiten auf einer Datenbasis, also auf Informationseinheiten, die miteinander in Beziehung stehen. Die Programme, die die Datenbasis kontrollieren, bilden die zweite Hälfte der DBMS. Die Netzwerk- oder hierarchischen Datenmodelle sind mittlerweile den relationalen Modellen – kurz gesagt, Tabellen, die miteinander in Beziehung stehen – gewichen. Mittlerweile gibt es neben den relationalen Modellen auch andere Speicherformen für Datenbanken. Immer populärer werden objektorientierte Datenbanken und XML-Datenbanken. Auch mit ihnen werden wir uns kurz beschäftigen.

16.1

Relationale Datenbanken

16.1.1

Das relationale Modell

Die Grundlage für relationale Datenbanken sind Tabellen mit ihren Spalten und Zeilen. In der Vertikalen sind die Spalten und in der Horizontalen die Zeilen angegeben. Eine Zeile (auch Tupel genannt) entspricht einem Element einer Tabelle, eine Spalte (auch Attribut genannt) einem Eintrag einer Tabelle.

1139

16

16

Datenbankmanagement mit JDBC

Lfr_Code

Lfr_Name

Adresse

Wohnort

004

Hoven G. H.

Sandweg 50

Linz

009

Baumgarten R.

Tankstraße 23

Hannover

011

Strauch GmbH

Beerenweg 34a

Linz

013

Spitzmann

Hintergarten 9

Aalen

...

...

...

...

Tabelle 16.1: Eine Beispieltabelle

Jede Tabelle entspricht einer logischen Sicht des Benutzers. Die Zeilen einer Relation stellen die Datenbankausprägung dar, während das Datenbankschema die Struktur der Tabelle – also Anzahl, Name und Typ der Spalten – beschreibt. Wenn wir nun auf diese Tabellen Zugriff erhalten wollen, um damit die Datenbankausprägung zu erfahren, benötigen wir Abfragemöglichkeiten. Java erlaubt mit JDBC den Zugriff auf relationale Datenbanken.

16.2

Datenbanken und Tools

Vor dem Glück, eine Datenbank in Java ansprechen zu können, steht die Inbetriebnahme des Datenbanksystems (für dieses Kapitel ist das fast schon der schwierigste Teil). Nun gibt es eine große Anzahl von Datenbanken – manche sind frei und Open Source, manche sehr teuer –, sodass sich dieses Tutorial nur auf eine Datenbank beschränkt. Das Rennen macht in dieser Auflage die pure Java-Datenbank HSQLDB, die sehr leicht ohne Administratorrechte läuft und leistungsfähig genug ist. Da JDBC aber von Datenbanken abstrahiert, ist der Java-Programmcode natürlich auf jeder Datenbank lauffähig.

Hinweis Ab dem JDK 6 ist im Unterverzeichnis db, also etwa C:\Program Files\Java\jdk1.6.0\db, die Datenbank Java DB (http://developers.sun.com/javadb/) integriert. Sie basiert auf Apache Derby, dem früheren Cloudscape von IBM.

1140

16.2

16.2.1

Datenbanken und Tools

HSQLDB

HSQLDB (http://hsqldb.org/) ist ein pures Java-RDBMS unter der freien BSD-Lizenz. Die Datenbank lässt sich in zwei Modi fahren: als eingebettetes Datenbanksystem und als Netzwerkserver. Im Fall eines eingebauten Datenbanksystems ist lediglich die Treiberklasse zu laden und die Datenbank zu bestimmen, und schon geht’s los. Wir werden für die folgenden Beispiele diese Variante wählen. Auf der Download-Seite http://sourceforge.net/projects/hsqldb/files/ von SourceForge befindet sich ein Archiv wie hsqldb-2.2.5.zip (8,2 MiB), das wir auspacken, zum Beispiel nach c:\Programme\hsqldb. Unter C:\Programme\hsqldb\bin\ befindet sich ein Skript runManagerSwing.bat, das ein kleines Datenbank-Frontend öffnet. Im folgenden Dialog Connect setzen wir 1. den Typ auf HSQL Database Engine Standalone und 2. die JDBC-URL auf jdbc:hsqldb:file:c:\TutegoDB (statt c:\TutegoDB einen anderen Pfad eintragen, wohin die Datenbank erzeugt werden soll). Der Teil hinter file: gibt also den Pfad zu der Datenbank an, wobei der Pfad relativ oder absolut sein kann. Liegt die Datenbank im EclipseWorkspace, kann später die absolute Angabe entfallen. Existiert die Datenbank nicht, wird sie unter dem angegebenen Pfad angelegt – das machen wir im ersten Schritt –, existiert sie, wird sie zum Bearbeiten geöffnet.

16

Abbildung 16.1: Verbindung aufbauen zur Datenbank

Nach dem Beenden des Dialogs mit OK fügt im Menü Options die Operation Insert Test Data einige Tabellen mit Dummy-Daten ein und führt ein SQL-SELECT aus, das uns den Inhalt der Customer-Tabelle zeigt. Beenden wir anschließend das Swing-Programm mit File 폷 Exit. Im Dateisystem hat der Manager jetzt eine .log-Datei angelegt – zu ihr gesellt sich später noch eine .script-Datei –, eine .properties-Datei und eine .lck-Datei. Für den Datenbankzugriff aus Java ist nur das Archiv hsqldb.jar aus dem lib-Verzeichnis von HSQLDB in den Klassenpfad aufzunehmen.

1141

16

Datenbankmanagement mit JDBC

Abbildung 16.2: SQL-Kommandos absetzen und Resultate einsehen

16.2.2

Weitere Datenbanken *

Die Anzahl der Datenbanken ist zwar groß, aber es gibt immer wieder Standard-Datenbanken und freie Datenbank-Management-Systeme.

MySQL MySQL (http://www.mysql.de/) ist ein häufig eingesetzter freier und schneller Open-SourceDatenbank-Server. Er wird oft im Internet in Zusammenhang mit dynamischen Webseiten eingesetzt; das Zusammenspiel zwischen Linux, Apache, MySQL, PHP (LAMP-System) ist hoch gelobt. Herausragende Eigenschaften sind die Geschwindigkeit und die Bedienbarkeit. Die MySQL-Datenbank spricht der unter der LGPL stehende JDBC-Treiber MySQL Connector/J (http://dev.mysql.com/downloads/connector/j/) an. Nach dem Entpacken muss das Jar-Archiv des Treibers in den Klassenpfad aufgenommen werden. Er unterstützt die JDBC 4.0-API. Sun Microsystems hat im Februar 2008 MySQL übernommen, und heute gehört es zu Oracle.

PostgreSQL Die PostgreSQL-Datenbank (http://www.postgresql.org/) ist ebenfalls quelloffen, läuft auf vielen Architekturen und unterstützt weitgehend den SQL-Standard 92. Gespeicherte Prozeduren, Schnittstellen zu vielen Programmiersprachen, Views und die Unterstützung für Geoinformationssysteme (GIS) haben das unter der BSD-Lizenz stehende PostgreSQL sehr beliebt gemacht. Es gibt JDBC 4-Treiber unter http://jdbc.postgresql.org/.

1142

16.2

Datenbanken und Tools

H2 Die Beispiele im Buch basieren auf der HSQLDB-Datenbank, da sie so schön einfach ist. Eine andere leistungsfähige und einfach einzusetzende pure Java-Datenbank ist H2 (http:// www.h2database.com). Auch sie ist standalone oder eingebettet lauffähig und verfügt gegenüber HSQLDB noch über einige Extra-Features. H2 hat eine schöne Web-Oberfläche zur Konfiguration und für Abfragen und unterstützt alle wichtigen SQL-Eigenschaften wie Trigger, Joins, dazu abgesicherte Verbindungen und Volltextsuche. H2 hält außerdem den Speicherverbrauch klein. Weiterhin lässt sich ein ODBC-Treiber (von PostgreSQL) nutzen, um H2 auch unter Windows-Programmen (etwa Access) als Datenbank zu nutzen.

Oracle Database 10g Express Edition (Oracle Database XE) Um die Verbreitung ihrer Produkte weiter zu erhöhen, ist die Firma Oracle dazu übergegangen, eine vollwertige freie Version zum Download oder als CD anzubieten. Wer den Download nicht scheut, der kann unter http://www.oracle.com/technology/software/products/database/ index.html die Oracle Database XE für Windows, Mac OS X, Linux und weitere Unix-Systeme herunterladen. Die JDBC-Treiber sind auf dem neuen Stand.

DB2 Universal Database Express/DB2 Express-C Von IBM stammt die etwas eingeschränkte, aber freie Version von DB2 mit exzellenter JavaUnterstützung. Unter http://tutego.de/go/db2express lässt sich die Datenbank für Windows und Linux herunterladen.

Microsoft SQL Server und JDBC-Treiber Mit SQL Server 2008 Express (http://www.microsoft.com/express/Database/) bietet Microsoft eine freie Datenbank. Auch für die nicht-freie Version, den Microsoft SQL Server, bietet Microsoft unter http://msdn.microsoft.com/data/ref/jdbc/ einen aktuellen JDBC 3-Treiber. Er benötigt mindestens Java 5 und ist auf verschiedenen Betriebssystemen lauffähig.

Microsoft Access Microsoft Access ist keine freie Software, aber auf vielen Windows-Systemen installiert. Damit lässt sich mit wenigen Handgriffen eine Datenbank zusammenbauen, die dann anschließend über die JDBC-ODBC-Bridge aus Java zugänglich ist. Viele Anwender haben das Office-Paket von Microsoft zu Hause installiert und so schon eine Datenbank zur Verfügung. Eine einfache Datenbank ist schnell gebaut: Nach dem Start von Access erscheint automatisch ein Dialog mit dem Eintrag Leere Access-Datenbank (alternativ lässt sich eine neue Datenbank unter dem Menüpunkt Datei 폷 Neu ... einrichten). Wir wählen den ersten Eintrag

1143

16

16

Datenbankmanagement mit JDBC

Datenbank und speichern die Datenbank unter einem aussagekräftigen Namen. Access benötigen wir nicht mehr direkt, weil die Kommunikation mit der Datenbank anschließend über den ODBC-Manager läuft. Dieser setzt dann auf dem SQL-Kern von Access auf. Im ODBCManager muss dafür die Datenquelle angemeldet werden.

ODBC einrichten und Access damit verwenden Die meisten Datenbanken öffnen einen TCP/IP-Port, und die Programme kommunizieren über ein definiertes Netzwerkprotokoll. Access ist dazu nicht in der Lage, und so muss eine Access-Datenbank als ODBC-Datenquelle bei einer Zentrale angemeldet werden; im nächsten Schritt kommuniziert ein spezieller Java-Datenbanktreiber mit dieser ODBC-Zentrale. Zum Einrichten gehen wir zunächst in die Systemeinstellungen (Start 폷 Einstellungen 폷 Systemsteuerung) und suchen ab Windows 2000 im Verzeichnis Verwaltung nach dem Symbol Datenquellen (ODBC). Nach dem Start öffnet sich ein Dialog mit dem Titel ODBCDatenquellen-Administrator. Wir gehen auf Hinzufügen, um eine neue Benutzer-Datenquelle hinzuzufügen. Im Dialog mit dem Titel Neue Datenquelle erstellen wählen wir den Microsoft Access-Treiber aus und gehen auf Fertigstellen. Ein Dialog öffnet sich, und wir tragen unter Datenquellenname einen Namen für die Datenquelle ein. Unter diesem Namen können wir in Java später die Datenbank ansprechen. Der Name der Datei hat nichts mit dem Namen der Datenquelle gemeinsam. Optional können wir noch eine Beschreibung hinzufügen. Wichtig ist nun die Verbindung zur physikalischen Datenbank. Im umrandeten Bereich Datenbank aktivieren wir über die Schaltfläche Auswählen einen Datei-Selektor. Hier hangeln wir uns bis zur in Access erstellten Datei durch und tragen sie ein. Nun müssen wir nur noch einige Male OK anklicken, und wir sind fertig. Wenn der Administrator nicht meckert, können wir nun ein JDBC-Programm starten.

Abbildung 16.3: Auswählen einer Datenbank

1144

16.2

16.2.3

Datenbanken und Tools

Eclipse-Plugins zum Durchschauen von Datenbanken

Es gibt fast genauso viele Tools zum Administrieren von Datenbanken wie Datenbanken selbst. Zwar bringt NetBeans direkt ein Plugin zum Durchstöbern von Datenbanken mit, doch leider nicht die Eclipse IDE – auch nicht in der Ausgabe Eclipse IDE for Java EE Developers. So muss ein extra Plugin installiert werden. Von der Eclipse-Foundation gibt es Eclipse Data Tools Platform (DTP) und eine Webseite http://www.eclipse.org/datatools/. Frei und in Java (aber kein Eclipse-Plugin) ist SQuirreL (http://squirrel-sql.sourceforge.net/).

Eclipse Data Tools Platform (DTP) Die DTP wird über den Online-Update-Mechanismus installiert. Wir wählen Help 폷 Install New Software... und geben bei work with die URL http://download.eclipse.org/datatools/ updates/ ein. Wir aktivieren Add... und bestätigen den nächsten Dialog mit OK. Es folgt ein Dialog mit Versionen, aus denen wir die letzte DTP-Version auswählen können, etwa Eclipse Data Tools Platform SDK 1.9.0.

16

Abbildung 16.4: Auswählen des DTP-Plugings

1145

16

Datenbankmanagement mit JDBC

Nach ein paar Ja-Next-Dialogen wird Eclipse neu gestartet, und das Plugin ist installiert. Wir wechseln anschließend die Perspektive mit Window 폷 Open Perspective 폷 Other... und wählen dann Database Development. Es gibt in der Perspektive einige neue Ansichten. Eine ist Data Source Explorer, die sich auch durch Window 폷 Show View 폷 Data Source Explorer für andere Perspektiven aktivieren lässt. In der Ansicht wählen wir im Zweig Database Connections über das Kontextmenü den Eintrag New... So lässt sich eine neue Datenbankverbindung einrichten. Im folgenden Dialog wählen wir HSQLDB aus der Liste. Next bringt uns zu einem neuen Dialog. Rechts neben dem Auswahlfeld bei Drivers ist eine unscheinbare Schaltfläche mit einem Kreis und +-Symbol.

Abbildung 16.5: Treiberdetails bestimmen

Nach dem Aktivieren öffnet sich ein weiterer Dialog mit dem Titel New Driver Definition. Aus der Liste wählen wir unter Database den HSQLB JDBC Driver aus und gehen auf den zweiten Reiter, auf Jar List. Mit Add JAR/Zip... kommt ein Auswahldialog, und wir navigieren zu hsqldb.jar.

Abbildung 16.6: Jar-Datei auswählen

1146

16.2

Datenbanken und Tools

Nach diesem Eintrag aktivieren wir den dritten Reiter, Properties. Wir tragen Folgendes ein: Þ Connection URL: die JDBC-URL für die angelegte Datenbank, etwa jdbc:hsqldb:file:c:/Tu-

tegoDB Þ Database Name: ein beliebiger Name, der nur zur Anzeige dient, etwa tutegoDB Þ Driver Class: die Treiberklasse org.hsqldb.jdbcDriver Þ Unter User ID tragen wir »sa« ein.

16 Abbildung 16.7: Verbindungsdaten eintragen

Mit Ok bestätigen wir den Dialog, und anschließend sollte der Klick auf die Schaltfläche Test Connection bezeugen, dass alles gut geht und es keine Probleme mit den Parametern gab. Finish schließt den Dialog, und nach einer erfolgreichen Verbindung sind in der Ansicht die Datenbank sowie ihre Schemas zu sehen. Um eine SQL-Abfrage auszuführen, öffnen wir den Dialog unter File 폷 New 폷 Other... 폷 SQL Development 폷 SQL File, klicken auf Next und geben einen Dateinamen wie test für eine Skriptdatei an. Im unteren Bereich des Dialogs lässt sich direkt die Datenbank auswählen. Wählen wir für Database server type den Eintrag HSQLDB_1.8, für Connection profile name anschließend New HSQLDB und abschließend als Database name aus dem Auswahlmenü PUBLIC. Finish schließt den Dialog, legt eine Datei test.sqlpage an und öffnet diese in einem neuen Editor für SQL-Anweisungen. Tragen wir dort Folgendes ein: SELECT * FROM Customer

Das Kontextmenü im SQL-Editor bietet Execute All. In der Ansicht SQL Results sind die Ergebnisse dann abzulesen.

1147

16

Datenbankmanagement mit JDBC

Abbildung 16.8: Die drei Ansichten »Database Explorer«, »Data Output« und der Editor für das SQL Scrapbook

Tipp Mit der rechten Maustaste lassen sich im Kontextmenü Edit in SQL Query Builder... die Abfragen auch etwas mehr grafisch visualisieren. Wenn wir unsere Beispiele beendet haben, sollten wir im Data Source Explorer die Verbindung wieder schließen; dazu ist auf unserer Datenbank in der Ansicht Database Explorer im Kontextmenü Disconnect zu wählen.

16.3

JDBC und Datenbanktreiber

JDBC ist die inoffizielle Abkürzung für Java Database Connectivity und bezeichnet einen Satz von Schnittstellen, um relationale Datenbanksysteme von Java zu nutzen. Die erste JDBC-Spezifikation gab es im Juni 1996. Die Schnittstellen und wenigen Klassen sind ab dem JDK 1.1 im Core-Paket integriert. Die JDBC-API und ihre Treiber erreichen eine wirksame Abstraktion von relationalen Datenbanken, sodass durch die einheitliche Programmierschnittstelle die Funktionen differierender Datenbanken in gleicher Weise genutzt werden können. Das Lernen von

1148

16.3

JDBC und Datenbanktreiber

verschiedenen Zugriffsmethoden für unterschiedliche Datenbanken der Hersteller entfällt. Wie jedoch diese spezielle Datenbank nun wirklich aussieht, verheimlicht uns die Abstraktion. Jede Datenbank hat ihr eigenes Protokoll (und eventuell auch Netzwerkprotokoll), doch die Implementierung ist nur dem Datenbanktreiber bekannt. Das Modell von JDBC setzt auf dem X/OPEN-SQL-Call-Level-Interface (CLI) auf und bietet somit die gleiche Schnittstelle wie Microsofts ODBC (Open1 Database Connectivity). Dem Programmierer gibt JDBC Methoden, um Verbindungen zu Datenbanken aufzubauen, Datensätze zu lesen oder neue Datensätze zu verfassen. Zusätzlich können Tabellen aktualisiert und Prozeduren auf der Serverseite ausgeführt werden.

Hinweis Ein JDBC-Treiber muss nicht unbedingt relationale Datenbanken ansprechen, obwohl das der häufigste Fall ist. Mit dem freien xlSQL (https://xlsql.dev.java.net/) steht ein JDBC-Treiber bereit, der auf Excel-Tabellen beziehungsweise CSV-Dateien arbeitet, und Oracle bietet mit Synopsis (http://www.sunopsis.com/corporate/us/products/jdbcforxml/) ein Produkt, das statt relationaler Datenbanken XML-Dokumente verwendet.

Implementierung der JDBC-API Um eine Datenbank ansprechen zu können, müssen wir einen Treiber haben, der die JDBCAPI implementiert und zwischen dem Java-Programm und der Datenbank vermittelt. Jeder Treiber ist üblicherweise anders implementiert, denn er muss die datenbankunabhängige JDBC-API auf die konkrete Datenbank übertragen. Oracle veröffentlicht unter http://developers.sun.com/product/jdbc/drivers Treiber zu allen möglichen Datenbanken. Eine Suchmaske erlaubt die Eingabe einer Datenbank und die Auswahl eines gewünschten Typs.

16.3.1

Treibertypen *

Oracle definiert vier Treiber-Kategorien, die wir im Folgenden beschreiben. Sie unterscheiden sich im Wesentlichen darin, ob sie über einen nativen Anteil verfügen oder nicht.

Typ 1: JDBC-ODBC-Brücke ODBC (Open Database Connectivity Standard) ist ein Standard von Microsoft, der den Zugriff auf Datenbanken über eine genormte Schnittstelle möglich macht. ODBC ist insbesondere in

1

Microsoft und Open? Eine ungewohnte Kombination ...

1149

16

16

Datenbankmanagement mit JDBC

der Windows-Welt weit verbreitet, und für jede ernst zu nehmende Datenbank gibt es einen Treiber. Da es am Anfang der JDBC-Entwicklung keine Treiber gab, haben sich JavaSoft und Intersolv (seit 2000 Merant) etwas ausgedacht: eine JDBC-ODBC-Brücke, die die Aufrufe von JDBC in ODBC-Aufrufe der Clientseite umwandelt. Da die Performance oft nicht optimal und die Brücke nicht auf jeder Plattform verfügbar ist, stellt diese JDBC-Anbindung häufig eine Notlösung dar. Und weil ODBC eine systembezogene Lösung ist, hat der Typ-1-Treiber native Methoden, was die Portierung und seinen Einsatz – etwa über das Netz – erschwert. Die JDBCODBC-Brücke implementiert seit Version 1.4 den JDBC 2-Standard.

Hinweis Die Geschwindigkeit des Zugriffs über die JDBC-ODBC-Brücke hängt von vielen Faktoren ab, ist aber im Allgemeinen nicht so gut. Der Grund ist, dass die Abfrage unter JDBC bis zur Datenbank viele Schichten durchläuft. Jede der Schichten übersetzt die Abfragen für die nächste Schicht. Zusätzlich kommen zum Zeitaufwand noch Inkompatibilitäten und Fehler hinzu. Somit hängt das Gelingen der JDBC-ODBC-Brücke von vielen Schichten ab und ist oft nicht so performant wie eine native Implementierung.

Typ 2: native plattformeigene JDBC-Treiber Diese Treiber übersetzen die JDBC-Aufrufe direkt in Aufrufe der Datenbank-API. Dazu enthält der Treiber Programmcode, der native Methoden aufruft. Treiber vom Typ 1 oder 2 sind nicht portabel, da sie zum einen für die JDBC-ODBC-Brücke auf die Plattform-Bibliothek für ODBC zurückgreifen müssen und zum anderen auf plattformspezifische Zugriffsmöglichkeiten für die Datenbank angewiesen sind. Damit ist der Nachteil verbunden, dass Applets mit diesen Treibern nichts anfangen können. Ein Applet erlaubt es nicht, nativen Code von anderen Quellen zu laden und auszuführen. Das ist nicht leicht, wenn etwa ein Macintosh mit Power-PC-Prozessor einen binären Treiber für eine MS-SQL-Datenbank installieren möchte. Die Quintessenz daraus: Applets können damit keine Verbindung zu einer externen Datenquelle aufbauen.

Typ 3: universeller JDBC-Treiber Der universelle JDBC-Treiber ist ein in Java programmierter Treiber, der beim Datenbankzugriff auf den Client geladen wird. Der Treiber kommuniziert mit der Datenbank nicht direkt, sondern mit einer Softwareschicht, die zwischen der Anwendung und der Datenbank sitzt: der Middleware. Damit erfüllen Typ-3-Treiber eine Vermittlerrolle, denn erst die Middleware leitet die Anweisungen an die Datenbank weiter. Für Applets und Internet-Dienste hat ein

1150

16.3

JDBC und Datenbanktreiber

Typ-3-Treiber den großen Vorteil, dass seine Klassendateien oft kleiner als Typ-4-Treiber sind, da ein komprimiertes Protokoll eingesetzt werden kann. Über das spezielle Protokoll zur Middleware ist auch eine Verschlüsselung der Verbindung möglich. Kaum eine Datenbank unterstützt verschlüsselte Datenbankverbindungen. Da zudem das Middleware-Protokoll unabhängig von der Datenbank ist, müssen auf der Clientseite für einen Datenbankzugriff auf mehrere Datenbanken auch nicht mehr alle Treiber installiert werden, sondern im günstigsten Fall nur noch ein Typ-3-Treiber von einem Anbieter. Die Ladezeiten sind damit deutlich geringer.

Typ 4: direkte Netzwerktreiber Diese Treiber sind vollständig in Java programmiert und kommunizieren direkt mit dem Datenbank-Server. Sie sprechen mithilfe des datenbankspezifischen Protokolls direkt mit der Datenbank über einen offenen IP-Port. Dies ist in einer Direktverbindung die performanteste Lösung. Sie ist jedoch nicht immer realisierbar, etwa bei Datenbanken wie MS Access, dBase oder Paradox, die kein Netzwerkprotokoll definieren.

16.3.2

JDBC-Versionen *

Mit den Java-Versionen ist auch die Versionsnummer von JDBC gestiegen:

16 Þ Am Anfang stand JDBC 1.0, das Sun im Jahre 1997 in Java 1.1 integrierte. JDBC 1.0 basiert auf

SQL-92. Þ Die nächste Spezifikation ist die JDBC 2.0 API. Sie berücksichtigt SQL-99 (SQL-3). Die Spezi-

fikation der Version 2 setzt sich aus zwei Teilen zusammen: einer JDBC 2.0 core API und einer JDBC 2.0 Optional Package API. Die Core-API im Paket java.sql erweitert das Ur-JDBC um Batch-Updates und SQL-3-Datentypen. Das JDBC Optional Package liegt im Paket javax.sql und bietet unter anderem Data-Source, Connection-Pooling und verteilte Transaktionen. Während das Core-Paket fester Teil von Java 1.2 war, ist das optionale Paket in Java 1.2 noch echt optional und erst in Java 1.3 fest integriert. Für fast alle Datenbanken gibt es JDBC-2.0-Treiber. Þ JDBC 3.0 ist Teil von Java 1.4. Es integriert die JDBC 2.1 core API, das JDBC 2.0 Optional Pa-

ckage und nimmt neu unter anderem hinzu: Savepoints in Transaktionen, Wiederverwendung von Prepared-Statements, JDBC-Datentypen BOOLEAN und DATALINK, Abrufen automatisch generierter Schlüssel, Änderungen von LOBs und mehrere gleichzeitig geöffnete ResultSets. Þ In Java 5 hat sich nicht viel an JDBC geändert. Es ist immer noch JDBC 3.0, doch sind JDBC-

RowSet-Implementierungen hinzugekommen.

1151

16

Datenbankmanagement mit JDBC

Þ In JDBC 4.0, das in Java 6 Einzug gehalten hat, werden Treiber – wenn mit einer speziellen

Mata-Datei vorbereitet – automatisch angemeldet. Weiterhin gibt es XML-Datentypen aus SQL:2003 und Zugriff auf die SQL-ROWID. Þ Ein kleines Update auf JDBC 4.1 bringt Java 7 mit sich. Die API unterstützt etwa das neue

Sprachfeature try-mit-Ressourcen.

Hinweis Viele JDBC 4-Treiber gibt es im Moment noch nicht. Die aktuellen Treiber für die Datenbanken Oracle 11, Java DB (Apache Derby), DB 2 und MySQL implementieren einige JDBC 4-Eigenschaften, wenngleich nicht alle. http://developers.sun.com/product/jdbc/drivers zählt bislang keinen JDBC 4-Treiber auf. Für JDBC 3 gibt es immerhin mehrere.

Der Grad der SQL-Unterstützung Auch wenn uns der neueste Treiber einer Datenbank vorliegt, heißt das nicht, dass er auch alle JDBC-Möglichkeiten ausschöpft. Zum einen kann das daran liegen, dass die Datenbank diese Möglichkeiten gar nicht bietet – etwa Savepoints – oder dass der Treiber nicht hinreichend aktuell ist. Einige Möglichkeiten lassen sich über die Metadaten einer Datenbank erfragen. Dazu zählt zum Beispiel, ob ein Treiber beziehungsweise eine Datenbank den vollen ANSI-92-Standard unterstützt. Die Metadaten liefern über die Methoden supportsANSI92XXXSQL() den Hinweis, ob die Datenbank ANSI 92 Entry Level (gilt immer), Intermediate SQL oder Full SQL unterstützt. Auch für ODBC gibt es unterschiedliche Level: Minimum SQL Grammar, Core SQL Grammar und Extended SQL Grammar. Weitere Informationen bietet die Webseite http:// java.sun.com/products/jdbc/driverdevs.html.

16.4 Eine Beispielabfrage 16.4.1

Schritte zur Datenbankabfrage

Wir wollen kurz die Schritte skizzieren, die für einen Zugriff auf eine relationale Datenbank mit JDBC erforderlich sind: 1. Einbinden der JDBC-Datenbanktreiber in den Klassenpfad 2. unter Umständen Anmelden der Treiberklassen 3. Verbindung zur Datenbank aufbauen 4. eine SQL-Anweisung erzeugen

1152

16.4

Eine Beispielabfrage

5. SQL-Anweisung ausführen 6. das Ergebnis der Anweisung holen, bei Ergebnismengen über diese iterieren 7. die Datenbankverbindung schließen Wir beschränken uns im Folgenden auf die Verbindung zum freien Datenbanksystem HSQLDB.

16.4.2 Ein Client für die HSQLDB-Datenbank Ein Beispiel soll zu Beginn die Programmkonzepte für JDBC veranschaulichen, bevor wir im Folgenden das Java-Programm weiter sezieren. Das Programm in der Klasse FirstSqlAccess nutzt die Datenbank TutegoDB, die sich im Suchpfad befinden muss; wir können ebenso absolute Pfade bei HSQLDB angeben, etwa C:/TutegoDB. Bei der Parametrisierung »jdbc:hsqldb:file:...« von HSQLDB liest die Datenbank beim ersten Start die Daten aus der Datei ein, verwaltet sie im Speicher und schreibt sie am Ende des Programms wieder in eine Datei zurück. Da wir die Datenbank schon früher mit Demo-Daten gefüllt haben, lässt sich jetzt eine SQLSELECT-Abfrage absetzen: Listing 16.1: com/tutego/insel/jdbc/FirstSqlAccess.java

16

package com.tutego.insel.jdbc; import java.sql.*; public class FirstSqlAccess { public static void main( String[] args ) { try { Class.forName( "org.hsqldb.jdbcDriver" ); } catch ( ClassNotFoundException e ) { System.err.println( "Keine Treiber-Klasse!" ); return; }

1153

16

Datenbankmanagement mit JDBC

Connection con = null; try { con = DriverManager.getConnection( "jdbc:hsqldb:file:TutegoDB;shutdown=true", "sa", "" ); Statement stmt = con.createStatement(); //

stmt.executeUpdate( "INSERT INTO CUSTOMER " +

//

"VALUES(50,'Christian','Ullenboom','Immengarten 6','Hannover')" ); ResultSet rs = stmt.executeQuery( "SELECT * FROM Customer" ); while ( rs.next() ) System.out.printf( "%s, %s %s%n", rs.getString(1), rs.getString(2), rs.getString(3) ); rs.close(); stmt.close(); } catch ( SQLException e ) { e.printStackTrace(); } finally { if ( con != null ) try { con.close(); } catch ( SQLException e ) { e.printStackTrace(); } } }

}

Dem Beispiel ist in diesem Status schon die aufwändige Fehlerbehandlung anzusehen. Das Schließen vom ResultSet und Statement ist vereinfacht, aber okay, weil das finally auf jeden Fall die Connection schließt. Ab Java 7 kann auch try-mit-Ressourcen die Verbindung automatisch schließen.

1154

16.4

Eine Beispielabfrage

Hinweis Es ist möglich, auch ohne ODBC-Eintrag Zugriff auf eine Access-Datenbank aufzubauen – nützlich ist das zum Beispiel dann, wenn der Name der Datenbank erst später bekannt wird. con = DriverManager.getConnection( "jdbc:odbc:Driver="+ "{Microsoft Access Driver (*.mdb)};DBQ=c:/daten/test.mdb", "name", "pass" );

Ein ähnlicher String kann auch für den Zugriff auf eine dBase-Datenbank genutzt werden, für die ein ODBC-Treiber angemeldet ist: jdbc:odbc:Driver={Microsoft dBase Driver (*.dbf)};DBQ=c:\database.dbf

16.4.3

Datenbankbrowser und eine Beispielabfrage unter NetBeans

Wer mit NetBeans arbeitet, der kann einfach mit der ab Java 6 mitgelieferten Datenbank Java DB arbeiten, denn NetBeans bringt eine Beispieldatenbank für Java DB mit. Im Folgenden soll 1. die Beispieldatenbank gestartet, 2. die Datenbank mit der Browser untersucht und 3. ein Java-Programm geschrieben werden, das diese Datenbank anspricht.

16

Beispieldatenbank starten Ist NetBeans gestartet, wählen wir im Menü Window 폷 Services. Links kommt in der Darstellung ein Punkt Databases hinzu, wobei unser Interesse der Beispieldatenbank dient, zu der wir mit Connect über das Kontextmenü eine Verbindung aufbauen wollen.

Abbildung 16.9: Demo-Datenbank starten

1155

16

Datenbankmanagement mit JDBC

In der Ausgabe ist zu erkennen, dass die Datenbank gestartet und bereit für Verbindungen ist.

Abbildung 16.10: Ausgabe nach dem Start der Datenbank

SQL-Anweisungen absetzen Links ist anschließend der Baum mit vielen Informationen gefüllt, und alle Tabelleninformationen sind zugänglich. Mit der rechten Maustaste und dem Kontextmenü lassen sich anschließend SQL-Anweisungen über Execute Command... an die Datenbank absetzen.

Abbildung 16.11: SQL-Abfragen starten

1156

16.4

Eine Beispielabfrage

Es öffnet sich ein SQL-Editor, der Tastaturvervollständigung beherrscht und sogar in die Datenbank schaut, um Tabellen und Spaltennamen korrekt zu vervollständigen.

16 Abbildung 16.12: Ergebnis einer SQL-Abfrage

JDBC-Beispiel Das JDBC-Beispiel von eben können wir leicht auf die NetBeans-Datenbank übertragen. Drei Dinge müssen wir anpassen: Þ Der Treiber muss im Klassenpfad stehen. Þ Die Treiberklasse ist org.apache.derby.jdbc.ClientDriver. Das explizite Laden kann aber

entfallen, da Java einen JDBC 4-Treiber selbstständig findet, wenn er im Klassenpfad steht. Þ Die Datenbank-URL ist jdbc:derby://localhost:1527/sample.

Die letzten beiden Dinge sind schnell im Quellcode angepasst. Um den Treiber in den Klassenpfad zu setzen, wählen wir links im Projekt bei Libraries das Kontextmenü und dann Add Jar/Folder...

1157

16

Datenbankmanagement mit JDBC

Abbildung 16.13: Java-Archive hinzufügen

Aus dem JDK-Installationsverzeichnis unter db/lib wählen wir derbyclient.jar.

Abbildung 16.14: derbyclient.jar auswählen

Öffnen fügt das Jar-Archiv hinzu. Wir wollen das SELECT noch etwas anpassen, und dann folgt: Listing 16.2: com/tutego/insel/jdbc/FirstSqlAccess.java package com.tutego.insel.jdbc; import java.sql.*; public class SecondSqlAccess { public static void main( String[] args ) { Connection con = null;

1158

16.5

Mit Java an eine Datenbank andocken

try { con = DriverManager.getConnection( "jdbc:derby://localhost:1527/sample", "app", "app" ); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT NAME, ADDRESSLINE1, PHONE FROM Customer" ); while ( rs.next() ) System.out.printf( "%s, %s %s%n", rs.getString(1), rs.getString(2), rs.getString(3) ); rs.close(); stmt.close(); } catch ( SQLException e ) {

16

e.printStackTrace(); } finally { if ( con != null ) try { con.close(); } catch ( SQLException e ) { e.printStackTrace(); } } } }

16.5

Mit Java an eine Datenbank andocken

Zum Aufbau einer Datenbankverbindung und zur Herstellung einer Connection gibt es zwei Möglichkeiten: Þ Direkt über den DriverManager: Die Verbindungsdaten stehen im Quellcode (entweder di-

rekt, oder sie werden über Konfigurationsdateien bestimmt). Diesen Weg zeigte das Beispiel bisher.

1159

16

Datenbankmanagement mit JDBC

Þ Über einen zentralen Namensdienst: Im JNDI ist eine vorkonfigurierte Datenquelle

(DataSource) abgelegt, die wir entnehmen und über die wir eine Verbindung aufbauen. Im Java-Enterprise-Bereich ist das übliche Vorgehen der zweite Weg über eine DataSource. Wir wollen uns doch zunächst mit dem DriverManager beschäftigen, bevor wir zur DataSource und zum JNDI kommen. Alle verwendeten Klassen und Schnittstellen für den Datenbankteil liegen unter java.sql.*. Wenn wir mit einem Namensdienst arbeiten, sind Typen aus dem Paket javax.naming nötig.

16.5.1

Der Treiber-Manager *

Alle Datenbanktreiber werden an einer zentralen Stelle, dem Treiber-Manager, gesammelt. Die Zentrale ist in Java durch die Klasse DriverManager gegeben. Die Methoden der Klasse sind statisch, da sich ein Exemplar dieser Klasse nicht erzeugen lässt; der Konstruktor ist privat. Die wichtigste Methode des Treiber-Managers ist statisch und heißt getConnection(). Mit ihr können wir eine Verbindung zur Datenbank aufbauen. Es lassen sich aber auch alle angemeldeten Treiber erfragen.

16.5.2

Den Treiber laden

Vor der Ausführung der JDBC-Befehle muss ein passender Datenbanktreiber geladen werden. Der Datenbanktreiber ist eine Java-Klasse, die beim Treiber-Manager angemeldet sein muss.

Hinweis Seit Java 6 werden die Treiberklassen – soweit das vom Treiberproduzenten vorbereitet ist – automatisch geladen. Der Entwickler muss den Namen der Treiberklassen nicht mehr kennen. Intern funktioniert das über Service Provider. Für eigene automatisch zu ladende Klassen ist java.util.ServiceLoader einen Blick wert. Vor Java 6 und bei nicht vorbereiteten Datenbanken ist die Treiberklasse von Hand einzubinden. Zwei Möglichkeiten sind populär: Þ Die Property jdbc.drivers enthält den Namen des Datenbanktreibers. Auf der Kommando-

zeile lässt sich die Variable mit dem Schalter -D einfach setzen: $ java -Djdbc.drivers=org.hsqldb.jdbcDriver Þ Die zweite Möglichkeit bietet der Aufruf von Class.forName(driverclassname), die eine Trei-

berklasse lädt. Sie trägt sich automatisch beim Treiber-Manager ein.

1160

16.5

Mit Java an eine Datenbank andocken

final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ static Class forName(String className) throws ClassNotFoundException

Sucht, lädt und bindet die Klasse mit dem qualifizierten Namen className ins Laufzeitsystem ein. Die statische Methode liefert ein Class-Objekt zurück, falls sie die Klasse laden kann, andernfalls quittiert sie einen Fehler mit einer ClassNotFoundException. Die Programmzeilen für das manuelle Laden der Klasse org.hsqldb.jdbcDriver sind somit: Listing 16.3: com/tutego/insel/jdbc/DriverManagerDemo.java, Ausschnitt try { Class.forName( "org.hsqldb.jdbcDriver" ); } catch ( ClassNotFoundException e ) { // Blöd: Treiber konnte nicht geladen werden. e.printStackTrace(); }

Da wir die Klasse nur laden, aber die Referenz auf den Klassen-Deskriptor nicht benötigen, belassen wir es bei einem Aufruf und beachten den Rückgabewert nicht. Diese Class.forName() löst eine ClassNotFoundException aus, falls die Klasse nicht gefunden wurde, der Treiber also nicht geladen werden konnte.

Hinweis Ein Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); ist zum Laden des JDBC-ODBC-Treibers nicht nötig, da er schon initialisiert ist.

Datenbank

Klassenname für den JDBC-Treiber

Adabas D

de.sag.jdbc.adabasd.Adriver

Borland JDataStore

com.borland.datastore.jdbc.DataStoreDriver

Borland Interbase

interbase.interclient.Driver

DB2/Derby

com.ibm.db2.jcc.DB2Driver

Informix

com.informix.jdbc.IfxDriver

IDS Server

ids.sql.IDSDriver

Tabelle 16.2: Übersicht der jeweiligen voll qualifizierten Klassennamen für den JDBC-Treiber

1161

16

16

Datenbankmanagement mit JDBC

Datenbank

Klassenname für den JDBC-Treiber

Microsoft SQL Server

com.microsoft.jdbc.sqlserver.SQLServerDriver

mSQL

COM.imaginary.sql.msql.MsqlDriver

MySQL

com.mysql.jdbc.Driver

Oracle

oracle.jdbc.driver.OracleDriver

Pointbase

com.pointbase.jdbc.jdbcUniversalDriver

PostgreSQL

org.postgresql.Driver

Sybase

com.sybase.jdbc2.jdbc.SybDriver

Tabelle 16.2: Übersicht der jeweiligen voll qualifizierten Klassennamen für den JDBC-Treiber (Forts.)

16.5.3

Eine Aufzählung aller Treiber *

Die statische Methode DriverManager.getDrivers() liefert eine Aufzählung der angemeldeten Treiber. Die folgenden Zeilen geben einfach den Klassennamen aus – die Treiber implementieren nicht unbedingt eine sinnvolle toString()-Methode, sodass wir uns mit dem Klassennamen begnügen: Listing 16.4: com/tutego/insel/jdbc/DriverManagerDemo.java, Ausschnitt for ( Enumeration e = DriverManager.getDrivers(); e.hasMoreElements(); ) System.out.println( e.nextElement().getClass().getName() );

Die Elemente, die durch die Enumeration ausgelesen werden, sind Treiberobjekte vom Typ Driver. Jeder Datenbanktreiber implementiert diese Schnittstelle. Mit dem manuell geladenen Treiber org.hsqldb.jdbcDriver und dem Standard-JDBC-ODBC-Treiber verbunden ist die Ausgabe: sun.jdbc.odbc.JdbcOdbcDriver org.hsqldb.jdbcDriver

16.5.4

Log-Informationen *

Zu Testzwecken bietet es sich an, Informationen des Treibers und der Datenbank in einen speziellen Ausgabekanal zu schreiben. Wir können die Log-Informationen so umlenken, dass sie in den Standardausgabestrom geschrieben werden. Das macht die statische Methode setLogWriter(), die einen PrintWriter als Parameter erwartet:

1162

16.5

Mit Java an eine Datenbank andocken

Listing 16.5: com/tutego/insel/jdbc/DriverManagerDemo.java, Ausschnitt DriverManager.setLogWriter( new PrintWriter( System.out ) ); Class.forName( "org.hsqldb.jdbcDriver" );

Da damit die Log-Ausgaben in den Standard-Ausgabekanal kommen, ist die Ausgabe für das Laden des HSQLDB-Treibers: JdbcOdbcDriver class loaded registerDriver: driver[className=sun.jdbc.odbc.JdbcOdbcDriver,sun.jdbc.odbc.ð JdbcOdbcDriver@173a10f] DriverManager.initialize: jdbc.drivers = null JDBC DriverManager initialized registerDriver: driver[className=org.hsqldb.jdbcDriver,org.hsqldb.jdbcDriver@530daa]

Nicht nur Treiber und SQL-Klassen nutzen den Log-Stream, auch wir können Zeichenketten ausgeben. Dazu dient die statische Methode println(), die als Parameter nur einen String annimmt. println() ist so implementiert, dass bei einem nicht gesetzten Log-Stream die Ausgabe unterbleibt. class java.sql.DriverManager Þ static void setLogWriter(PrintWriter out)

Setzt den Log-Writer und startet damit das Logging. Mit dem Argument null wird das Logging wieder ausgeschaltet. Þ static PrintWriter getLogWriter()

Liefert den angemeldeten Log-Writer. Þ static void println(String message)

Schreibt eine Meldung in den Log-Stream.

16.5.5

Verbindung zur Datenbank auf- und abbauen

Nach dem Laden des Treibers können wir eine Verbindung zur Datenbank mithilfe des Connection-Objekts aufbauen, das DriverManager.getConnection() zurückgibt. Der Methode wird eine Datenbank-URL mitgegeben und optional Benutzername und Passwort.

Die Datenquelle angeben Alle Datenquellen sind durch eine besondere URL qualifiziert, die folgendes Format besitzt: jdbc:Subprotokoll:Datenquellenname

1163

16

16

Datenbankmanagement mit JDBC

Die Datenbank definieren jeweils unterschiedliche Subprotokolle, und die Angabe des Servernamens ist auch immer individuell: Datenbank

Subprotokoll

Beispiel

Derby

derby:net

jdbc:derby:net://host:1527/

IBM DB2

db2

jdbc:db2://database

HSQLDB

hsqldb

jdbc:hsqldb:file:database

Interbase

interbase

jdbc:interbase://host/dabase.gdb

MySQL

Mysql

jdbc:mysql://host/database

ODBC-Datenquellen

Odbc

jdbc:odbc:database

Oracle Thin

oracle:thin

jdbc:oracle:thin:@host:1243:database

Sybase

sybase:Tds

jdbc:sybase:Tds:host:1234/database

Tabelle 16.3: Protokoll-URLs einiger Datenbanken

Verbindung aufnehmen Der Aufruf von DriverManager.getConnection() liefert – wenn alles gut geht – ein ConnectionObjekt, das die Verbindung mit der Datenbank repräsentiert.

Beispiel Verbinde mit einer Datenbank, die den Namen »TutegoDB« trägt (im Fall von ODBC wurde der Name im Datenquellen-Administrator festgelegt und hat nichts mit dem Dateinamen zu tun): con = DriverManager.getConnection( "jdbc:hsqldb:file:TutegoDB;shutdown=true", "sa", "" );

Die statische Methode getConnection() erwartet bis zu drei Parameter: Die URL der Datenbank, zu der die Verbindung aufgenommen werden soll, ist der Pflichtparameter. Der Anmeldename und das Passwort sind optional und können auch leere Strings ("") sein, wenn eine Authentifizierung keine Rolle spielt. Meldet getConnection() keinen Fehler, so liefert sie uns eine geöffnete Datenbankverbindung. class java.sql.DriverManager Þ static Connection getConnection(String url) throws SQLException

Versucht, eine Verbindung zur Datenbank aufzubauen. Die Klasse DriverManager sucht dabei einen passenden Treiber aus der Liste der registrierten JDBC-Treiber für die Datenbank.

1164

16.5

Mit Java an eine Datenbank andocken

Þ static Connection getConnection(String url, String user, String password)

throws SQLException

Versucht, eine Verbindung zur Datenbank aufzubauen. user und password werden für die Verbindung zur Datenbank verwendet. Þ static Connection getConnection(String url, Properties info)

throws SQLException

Versucht, eine Verbindung zur Datenbank aufzubauen. Im Properties-Objekt können die Felder user und password sowie weitere Informationen vorhanden sein.

16

Abbildung 16.15: Klassendiagramm für DriverManager

Verbindung beenden Da eine Verbindung zu schließen ist (und nicht der DriverManager), finden wir eine Methode close() beim Connection-Objekt. Verbindungen zu schließen ist immens wichtig, sodass dieser Teil im Allgemeinen im finally-Block steht: Listing 16.6: com/tutego/insel/jdbc/FirstSqlAccess.java, Ausschnitt Connection con = null; try { con = DriverManager.getConnection( ... ); ... } catch ( SQLException e ) { e.printStackTrace();

1165

16

Datenbankmanagement mit JDBC

} finally { if ( con != null ) try { con.close(); } catch ( SQLException e ) { e.printStackTrace(); } } interface java.sql.Connection extends Wrapper, AutoCloseable Þ void close() throws SQLException

Schließt die Verbindung zur Datenbank. Auch hier kann eine SQLException auftauchen.

Wartezeit einstellen Wenn wir uns mit der Datenbank verbinden, lässt sich noch eine Wartezeit in Sekunden einstellen, die angibt, wie lange der Treiber für die Verbindung mit der Datenbank warten darf. Gesetzt wird dieser Wert mit setLoginTimeout(), und entsprechend wird er mit getLoginTimeout() ausgelesen. Standardmäßig ist dieser Wert 0. class java.sql.DriverManager Þ static void setLoginTimeout(int seconds)

Setzt die Zeit, die maximal gewartet wird, wenn der Treiber sich mit einer Datenbank verbindet. Þ static int getLoginTimeout()

Liefert die Wartezeit in Sekunden.

Wie der Treiber gefunden wird – hinter den Kulissen von getConnection() * Es lohnt sich, einmal hinter die Kulissen der Methode getConnection() zu blicken. Das DriverManager-Objekt wird veranlasst, die Verbindung zu öffnen. Dabei versucht es, einen passenden Treiber aus der Liste der JDBC-Treiber auszuwählen. Sein Treiber verwaltet die Klasse DriverManager in einem privaten Objekt DriverInfo. Dieses enthält ein Treiber-Objekt (Driver), ein Objekt (securityContext) und den Klassennamen (className). Bei getConnection() geht der DriverManager die Liste der DriverInfo-Objekte ab und versucht, sich über die connect()-Methode anzumelden. Bemerkt der Treiber, dass er mit der URL nicht viel anfangen kann, gibt er null zurück, und getConnection() versucht es mit dem nächsten Treiber. Ging alles daneben und konnte keiner der angemeldeten Treiber etwas mit dem Subprotokoll anfangen, so bekommen wir eine SQLException("No suitable driver", "08001").

1166

16.6

Datenbankabfragen

16.6 Datenbankabfragen Mit einer gelungenen Verbindung und dem Connection-Objekt in der Hand lassen sich SQLKommandos absetzen, und die Datenbank kann gesteuert werden.

16

Abbildung 16.16: Klassendiagramm für »Connection«

1167

16

Datenbankmanagement mit JDBC

16.6.1

Abfragen über das Statement-Objekt

Für alle SQL-Abfragen und Manipulationen der Datenbank sind Anweisungsobjekte von der Connection zu erfragen. JDBC bietet drei Typen von Anweisungsobjekten: Þ normale Anweisungen vom Typ Statement Þ vorbereitete Anweisungen (Prepared Statement) vom Typ PreparedStatement Þ gespeicherte Prozeduren (Stored Procedures) vom Typ CallableStatement

Für einfache Anweisungen liefert uns die Methode createStatement() ein Statement-Objekt, mit dem sich im nächsten Schritt Abfragen stellen lassen.

Beispiel Hole ein Statement-Objekt für einfache Abfragen: Statement stmt = con.createStatement();

Die Methode kann – wie fast alle Methoden aus dem SQL-Paket – eine SQLException auslösen.

interface java.sql.Connection extends Wrapper, AutoCloseable Þ Statement createStatement() throws SQLException

Liefert ein Statement-Objekt, um SQL-Anweisungen zur Datenbank zu schicken.

SQL-Anweisungen ausführen Das Statement-Objekt nimmt mit der Methode executeQuery() eine Zeichenfolge mit einer SQL-SELECT-Anweisung entgegen und mit executeUpdate() eine Anweisung für eine UPDATE-, INSERT- oder DELETE-Operation.

Beispiel Erfrage alle Spalten der Tabelle »Customer«: String query = "SELECT * FROM Customer"; ResultSet rs = stmt.executeQuery( query );

Der Aufruf liefert uns die Ergebnisse als Zeilen in Form eines ResultSet-Objekts.

1168

16.6

Datenbankabfragen

Hinweis Der JDBC-Treiber überprüft die SQL-Anweisungen nicht, sondern leitet sie fast ungesehen an die Datenbank weiter. Sind die SQL-Abfragen falsch, lassen sich Fehler schwer entdecken. Daher bietet es sich an, zum Testen erst die Kommandos auf der Konsole auszugeben. Insbesondere bei zusammengesetzten Ausdrücken finden sich so leichter Fehler.

interface java.sql.Statement extends Wrapper, AutoCloseable Þ ResultSet executeQuery(String sql) throws SQLException

Führt ein SQL-Statement aus, das für die Ergebnisliste ein einzelnes ResultSet-Objekt zurückgibt. Wird die gleiche SQL-Anweisung mehrmals ausgeführt, lohnt es sich, ein PreparedStatement zu konstruieren.

16.6.2 Ergebnisse einer Abfrage in ResultSet Um mit der Auswertung vom ResultSet beginnen zu können, muss der Treiber die Informationen von der Datenbank bezogen haben. Ein Aufruf der next()-Methode von ResultSet setzt den internen Cursor auf die erste Zeile der geladenen Ergebnisse. Mit diversen Methoden von ResultSet können wir die unterschiedlichen Spalten ansprechen und die Zeilen auswerten. Um weitere Zeilen zu erhalten, nutzen wir wieder next(). Die Methode gibt false zurück, falls es keine neue Zeile mehr gibt.

Beispiel Gehe mit einer while-Schleife durch die gesamte Ergebnisliste, und gib das Ergebnis der Spalten 1, 2 und 3 auf dem Bildschirm aus: ResultSet rs = stmt.executeQuery( "SELECT * FROM Customer" ); while ( rs.next() ) System.out.printf( "%s, %s, %s%n", rs.getString(1), rs.getString(2), rs.getString(3) );

Der numerische Parameter steht für den Spaltenindex, der bei 1 beginnt. Wird der Methode getString() ein String übergeben, so bestimmt er den Namen der Spalte. Die Methode executeQuery() liefert immer ein ResultSet-Objekt (bis auf den Fehlerfall, der zu einer SQLException führt), auch wenn das ResultSet keine Zeilen enthält. So lassen sich über das ResultSet immer noch Metadaten abfragen.

1169

16

16

Datenbankmanagement mit JDBC

interface java.sql.ResultSet extends Wrapper, AutoCloseable Þ boolean next() throws SQLException

Der erste Aufruf muss next() sein, damit der Cursor auf die erste Zeile gesetzt wird. Die folgenden Aufrufe setzen den Cursor immer eine Zeile tiefer. Falls es keine Zeilen mehr gibt, liefert die Methode false.

getXXX(int) und getXXX(String) Da die Spalten verschiedene Datentypen besitzen können, bietet die Schnittstelle ResultSet für jeden Datentyp eine entsprechende Methode getXXX() an – XXX ist ein Datentyp wie int. Zwei Ausführungen der Methode getXXX() sind verfügbar: Bei der ersten Variante ist eine Ganzzahl als Parameter aufgeführt. Dieser Parameter gibt die Spalte der Operation an. Die zweite Variante erlaubt es, den Namen der Spalte anzugeben. Da alle Spalten immer als String ausgelesen werden können, ist es möglich, einfach getString() zu verwenden. Im Folgenden soll der Typ String stellvertretend für andere Typen wie int, double usw. stehen. interface java.sql.ResultSet extends Wrapper, AutoCloseable Þ String getString(int column) throws SQLException

Liefert aus der aktuellen Zeile den Inhalt der Spalte column als String. Die erste Spalte ist mit 1 adressiert. Þ String getString(String columnName) throws SQLException

Liefert in der aktuellen Zeile den Inhalt der Spalte mit dem Namen columnName als String.

Hinweis Üblicherweise ist es performanter, ein Spaltenelement über den Index anstatt über den Spaltennamen zu erfragen. Gibt es zwei Spalten mit dem gleichen Namen, liefert die mit dem Namen aufgerufene Methode immer die erste Spalte.

16.6.3 Java und SQL-Datentypen Jeder Datentyp in SQL hat einen mehr oder weniger passenden Datentyp in Java. Die Klasse java.sql.Types identifiziert alle SQL-Typen. So konvertiert der JDBC-Treiber bei jeder getXXX()Methode diese zu einem Datentyp, doch nur dann, wenn diese Konvertierung möglich ist. So lässt er es nicht zu, bei einer String-Spalte die getInteger()-Methode auszuführen. Umgekehrt

1170

16.6

Datenbankabfragen

lassen sich alle Datentypen als String auslesen. Die folgende Tabelle zeigt die Übereinstimmungen. Einige SQL-Datentypen können durch mehrere Zugriffsmethoden geholt werden: Ein INTEGER lässt sich mit getInt() oder getBigDecimal() holen und TIMESTAMP mit getDate(), getTime() oder getTimestamp(). Java-Methode

SQL-Typ

getInt()

INTEGER

getLong()

BIG INT

getFloat()

REAL

getDouble()

FLOAT

getBignum()

DECIMAL

getBigDecimal()

NUMBER

getBoolean()

BIT

getString()

VARCHAR

getString()

CHAR

getAsciiStream()

LONGVARCHAR

getDate()

DATE

getTime()

TIME

getTimestamp()

TIME STAMP

getObject()

jeder Typ

16

Tabelle 16.4: Datentypen in SQL und ihre Entsprechung in Java

In der Regel passen die Typen recht gut in das Java-System. So liefert getInt() ein int und getString() ein String-Objekt. Für einige Daten wurden jedoch spezielle Klassen entworfen; am

auffälligsten ist die Klasse java.sql.Date, auf die wir gleich noch zu sprechen kommen. Ist ein Eintrag in der Datenbank mit NULL belegt, so liefert die Methode eine null-Referenz. interface java.sql.ResultSet extends Wrapper, AutoCloseable Þ String getString(int columnIndex) Þ String getString(String columnLabel)

Liefert den Wert in der Spalte als Java-String. Þ boolean getBoolean(int columnIndex) Þ boolean getBoolean(String columnLabel)

Liefert den Wert in der Spalte als Java-boolean.

1171

16

Datenbankmanagement mit JDBC

Þ byte getByte(int columnIndex) Þ byte getByte(String columnLabel)

Liefert den Wert in der Spalte als Java-byte. Þ short getShort(int columnIndex) Þ short getShort(String columnLabel)

Liefert den Wert in der Spalte als Java-short. Þ int getInt(int columnIndex) Þ int getInt(String columnLabel)

Liefert den Wert in der Spalte als Java-int. Þ long getLong(int columnIndex) Þ long getLong(String columnLabel)

Liefert den Wert in der Spalte als Java-long. Þ float getFloat(int columnIndex) Þ float getFloat(String columnLabel)

Liefert den Wert in der Spalte als Java-float. Þ double getDouble(int columnIndex) Þ double getDouble(String columnLabel)

Liefert den Wert in der Spalte als Java-double. Þ BigDecimal getBigDecimal(int columnIndex, int scale) Þ BigDecimal getBigDecimal(String columnLabel, int scale)

Liefert den Wert in der Spalte als java.lang.BigDecimal-Objekt. Þ byte[] getBytes(int columnIndex) Þ byte[] getBytes(String columnLabel)

Liefert den Wert in der Spalte als Byte-Feld. Es besteht aus uninterpretierten Rohdaten. Þ Date getDate(int columnIndex) Þ Date getDate(String columnLabel)

Liefert den Wert in der Spalte als java.sql.Date-Objekt. Þ Time getTime(int columnIndex) Þ Time getTime(String columnLabel)

Liefert den Wert in der Spalte als java.sql.Time-Objekt. Þ Timestamp getTimestamp(int columnIndex) Þ Timestamp getTimestamp(String columnLabel)

Liefert den Wert in der Spalte als java.sql.Timestamp-Objekt. Þ InputStream getAsciiStream(int columnIndex)

1172

16.6

Datenbankabfragen

Þ InputStream getAsciiStream(String columnLabel)

Die Methode ermöglicht über einen InputStream Zugriff auf den Inhalt der Spalte. Nützlich ist dies für den Datentyp LONGVARCHAR. Der JDBC-Treiber konvertiert die Daten mitunter in das ASCII-Format. Þ InputStream getBinaryStream(int columnIndex) Þ InputStream getBinaryStream(String columnLabel)

Die Methode erlaubt es, auf den Inhalt der Spalte als InputStream zuzugreifen. Nützlich ist dies für den Datentyp LONGVARBINARY. Der JDBC-Treiber konvertiert die Daten mitunter in das ASCII-Format. Bevor aus einer anderen Spalte Daten ausgelesen werden, müssen die Daten vom Stream gelesen werden. Ein weiterer Aufruf schließt selbstständig den Datenstrom. Die Methode available() liefert die Rückgabe null, sofern keine Daten anliegen. Alle getXXX()-Methoden können eine SQLException in dem Fall auslösen, dass etwas mit der Datenbank nicht stimmt. Der throws-Ausdruck ist also in der Aufzählung nicht explizit angegeben.

16.6.4 Date, Time und Timestamp Datenbankseitig können Datumswerte im SQL-Typ DATE, TIME und TIMESTAMP abgelegt sein: Þ DATE speichert ein Datum, also Tag, Monat und Jahr. Die Textrepräsentation hat die Form

»YYYY-MM-DD«. Þ TIME speichert eine Zeit im 24-Stundenformat, also Stunden, Minuten und Sekunden. Se-

kunden können einen Sekundenbruchteil haben, sodass die Genauigkeit in die Nanosekunden geht. Die Darstellung ist »hh:mm:ss« bzw. »hh:mm:ss.nnnnnnn«. Þ TIMESTAMP verbindet DATE und TIME.

Unterschiedliche Datenbanken erlauben weitere Spezialitäten wie TIMESTAMP WITH TIME ZONE, was hier aber keine Rolle spielen soll. Zur Abbildung der SQL-Typen auf Java sieht JDBC drei entsprechende Klassen vor: SQL-Typ

Java-Klasse

DATE

java.sql.Date

TIME

java.sql.Time

TIMESTAMP

java.sql.Timestamp

Tabelle 16.5: Datum-/Zeittypen in SQL und ihre Entsprechung in Java

Es fällt auf, dass alle drei JDBC-Klassen von der Basisklasse java.util.Date erben. Das hat unterschiedliche Konsequenzen. Im Einzelnen:

1173

16

16

Datenbankmanagement mit JDBC

Þ Die Klasse java.sql.Date repräsentiert das SQL-DATE. Die Basisklasse java.util.Date ist na-

türlich etwas merkwürdig und kollidiert mit dem liskovschen Substitutionsprinzip, da die Unterklasse Eigenschaften wegdefiniert, die die Oberklasse bietet. Denn ein java.util.Date ist ja für Datum und Zeit verantwortlich, wobei java.sql.Date nur Tag, Monat, Jahr speichert. Wenn also ein java.sql.Date mit Zeitinformationen gefüllt wird, so wird beim Abspeichern in die Datenbank diese Zeit auf null gesetzt. Das nennt die API-Dokumentation »Normalisierung«. Þ Die Klasse Time repräsentiert ein SQL-TIME. Die Basisklasse java.util.Date ist genauso wi-

dersprüchlich, denn hier wird der Datumsteil ausgeblendet, und nur der Zeitanteil ist erlaubt. Þ Für den SQL-Typ TIMESTAMP fasst die Java-Klasse Timestamp die Datums- und Zeitangaben mit

einer Genauigkeit von Nanosekunden zusammen. Das ist wichtig zu beachten, denn bei einer Umwandlung eines Timestamp in ein java.util.Date gehen die Nanosekunden verloren, da java.util.Date diese Genauigkeit nicht bietet. Die Klasse Timestamp erbt von Date und fügt intern ein int nano-Attribut hinzu.

Die Verwandtschaft von java.sql.Date und java.util.Date Ein Datenbankprogramm, das die Klasse java.sql.Date nutzt und ebenfalls java.util eingebunden hat, wird bei der Compilierung zu einem Fehler führen, da der Compiler den Bezug auf die Klasse Date nicht zuordnen kann. Denkbar sind zwei Lösungen: Wird util nur deswegen eingebunden, weil Datenstrukturen, aber nicht die Date-Klasse genutzt werden, dann ließe sich die import-Deklaration umbauen, sodass die von util genutzten Klassen direkt in import genannt werden, etwa import java.util.ArrayList. Bei vielen benutzten Klassen aus dem util-Paket ist aber eine andere Lösung einfacher. Wir setzen vor die Klasse, die uns Ärger bereitet, einfach die volle Qualifizierung, schreiben also zum Beispiel: java.sql.Date date = rs.getDate( "birthday" );

Konvertierung von java.sql.Date in java.util.Date und umgekehrt Ein weiteres Problem betrifft die Konvertierung der beiden Klassen. Wollen wir beispielsweise eine Zeichenkette aus der Eingabe in eine Datenbank schreiben, dann haben wir das Problem, dass das Parsen mittels DateFormat nur ein java.util.Date liefert. Wir müssen also erst mit getTime() die Zeit erfragen und auf das SQL-DATE übertragen: java.sql.Date sqlDate = new java.sql.Date( java_util_Date.getTime() );

Der Konstruktor von java.sql.Date() mit den Millisekunden ist auch der einzige Konstruktor, der nicht veraltet ist. Daneben hat die Klasse java.sql.Date aber noch drei andere Methoden:

1174

16.6

Datenbankabfragen

class java.sql.Date extends java.util.Date Þ static Date valueOf(String s)

Wandelt einen String im JDBC-Stil (also »yyyy-mm-dd«) in ein Date-Objekt um. Þ String toString()

Liefert das Datum im JDBC-Datenformat. Þ void setTime(long date)

Setzt das Datum mit den Millisekunden.

16.6.5 Unicode in der Spalte korrekt auslesen Der Aufruf von getString() führt bei Unicode-kodierten Zeichenfolgen in der Datenbank unter Umständen zu Problemen. Bemerkbar macht sich dies durch seltsame Zeichen wie »?« oder Hexadezimal »0x3f«, die im String anstelle der Sonderzeichen auftauchen. Das liegt oft daran, dass der JDBC-Treiber die Kodierung nicht kennt und einfach jedes ASCII-Byte in ein Char umwandelt, obwohl in der Datenbank Umlaute als 2-Byte-Unicode oder Latin-1 kodiert werden. Bei eigenen Datenbanken funktioniert es, die Kodierung beim Verbindungsaufbau ausdrücklich zu setzen, um damit eine Konvertierung vorzuschreiben. getString() sollte dann die richtige Zeichenkette liefern. Bei anderen Datenbanken funktioniert es wiederum, den Text als Byte-Feld zu holen und dann ausdrücklich umzukodieren. Das Folgende ist etwa eine Lösung für PostgreSQL: new String( rs.getBytes(1), "ISO-8859–1" )

16.6.6 Eine SQL-NULL und wasNull() bei ResultSet Ist der Wert einer Spalte eine SQL-NULL, so ist bei einer Abfrage mit der getXXX()-Methode Vorsicht geboten. Eine Methode wie getString() liefert standardmäßig null, und getInt(), getLong(), getFloat(), getDouble() und weitere Methoden liefern 0; getBoolean() liefert ein false, und bei anderen Methoden sieht es ähnlich aus – keine Methode löst eine Ausnahme aus. Die Behandlung von Nullwerten ist in JDBC recht ungewöhnlich gelöst. Wir würden erwarten, dass es eine Methode isNull(column) auf einem ResultSet-Objekt gibt, die uns ja oder nein liefert hinsichtlich der Frage, ob ein Spalteninhalt unbelegt ist. Dass die Methode wasNull() heißt, ist vielleicht noch zu verkraften, aber dass sie parameterlos ist, erstaunt.

1175

16

16

Datenbankmanagement mit JDBC

Beispiel Die allgemeine Vorgehensweise für einen SQL-NULL-Test am Beispiel einer String-Abfrage ist: String s = rs.getString( column ); if ( rs.wasNull() ) System.out.println( "SQL-NULL" );

interface java.sql.ResultSet extends Wrapper, AutoCloseable Þ boolean wasNull()

Ermittelt, ob die Spalte ein SQL-NULL enthält. Vorher muss eine getXXX()-Methode für die Spalte aufgerufen werden!

16.6.7 Wie viele Zeilen hat ein ResultSet? * Um herauszufinden, wie viele Zeilen ein ResultSet liefern kann, lassen sich trickreiche JDBC 2Eigenschaften nutzen. Soll in der Variablen row die Anzahl der Zeilen stehen, schreiben wir: rs.last(); int rows = rs.getRow(); rs.beforeFirst();

Bei dieser Programmierung muss natürlich ein Treiber JDBC 2-fähig sein und scrollbare Cursor unterstützen, das heißt Cursor, die auch rückwärts laufen können. Gleichzeitig muss dann aber auch beim Aufbau eines Statement-Objekts ein scrollbarer Cursor angemeldet werden: stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE );

Unterstützt ein Treiber kein JDBC 2, kann immer noch über eine Zeile wie SELECT COUNT(*) erfragt werden, wie viele Ergebnisse die Datenbank produziert.

16.7

Elemente einer Datenbank hinzufügen und aktualisieren

Bisher haben wir executeQuery() benutzt, um Abfragen zu verfassen. Es lassen sich jedoch auch Einfüge-Operationen vornehmen, denn Tabelleninhalte bleiben nicht unveränderlich.

1176

16.7

Elemente einer Datenbank hinzufügen und aktualisieren

Das SQL-Kommando INSERT fügt Daten ein, und UPDATE aktualisiert sie. Damit Spalten verändert werden können, müssen wir in zwei Schritten vorgehen: 1. eine SQL-Anweisung mit einem UPDATE aufbauen und 2. anschließend executeUpdate() aufrufen. Damit wird die Änderung wirksam. Dies ist eine andere Statement-Methode, bisher kannten wir nur executeQuery(). Neben den Methodennamen gibt es aber noch einen anderen Unterschied: executeUpdate() liefert als Rückgabewert ein int, das angibt, wie viele Zeilen von der Änderung betroffen sind.

Beispiel Folgende SQL-Anweisung ändert die Adresse eines Lieferanten einer fiktiven Relation: String updateString = "UPDATE Lieferanten SET Adresse = " + "'Uferstraße 80' WHERE Adresse LIKE 'Uferstrasse 78'"; stmt.executeUpdate( updateString );

Die Methode gibt uns immer zurück, wie viele Zeilen von den Änderungen betroffen sind. Sie ist 0, falls das SQL-Statement nichts bewirkt. interface java.sql.Statement

16

extends Wrapper, AutoCloseable Þ int executeUpdate(String sql) throws SQLException

Führt eine SQL-Anweisung aus, die Manipulationen an der Datenbank vornimmt. Die SQLAnweisungen sind in der Regel INSERT-, UPDATE- oder DELETE-Anweisungen. Zurückgegeben wird die Anzahl der veränderten Zeilen, oder null, falls eine SQL-Anweisung nichts verändert hat.

16.7.1

Batch-Updates

Das Einfügen und Ändern großer Mengen von Daten kostet viel Zeit, da für jede Modifikation ein INSERT oder UPDATE über ein Statement-Objekt abgewickelt werden muss. Eine Verbesserung stellen Batch-Updates dar, die in einem Rutsch gleich eine ganze Reihe von Daten zur Datenbank transferieren. Statt mit execute() und deren Varianten zu arbeiten, nutzen wir die Methode executeBatch(). Damit zuvor die einzelnen Aktionen dem Statement-Objekt mitgeteilt werden können, bietet die Klasse die Methoden addBatch() und clearBatch() an. Die Datenbank führt die Anweisungen in der Reihenfolge aus, wie sie im Batch-Prozess eingefügt wurden. Ein Fehler wird über eine BatchUpdateException angezeigt.

1177

16

Datenbankmanagement mit JDBC

Beispiel Wir fügen einige Einträge der Datenbank als Batch hinzu. con sei unser Connection-Objekt: int[] updateCounts = null; try { Statement s = con.createStatement(); s.addBatch( "INSERT INTO Lieferanten VALUES (x,y,z)" ); s.addBatch( "INSERT INTO Lieferanten VALUES (a,b,c)" ); s.addBatch( "INSERT INTO Lieferanten VALUES (d,e,f)" ); updateCounts = s.executeBatch(); } catch ( BatchUpdateException e ) { /* Behandeln! */ } catch ( SQLException e ) { /* Behandeln! */ }

Nach dem Abarbeiten von executeBatch() erhalten wir als Rückgabewert ein int-Feld mit den Ergebnissen der Ausführung. Dies liegt daran, dass in der Batch-Verarbeitung ganz unterschiedliche Anweisungen vorgenommen werden können und jede davon einen unterschiedlichen Rückgabewert verwendet. Soll der gesamte Ablauf als Transaktion gewürdigt werden, so setzen wir im try-Block den AutoCommit-Modus auf false, damit nicht jede SQL-Anweisung als einzelne Transaktion gewertet wird. Im Fall eines Fehlers müssen wir im catch-Block ein Rollback ausführen. Übertragen wir dies auf das obere Beispiel, dann müssen nur die beiden Anweisungen für die Transaktion eingesetzt werden: try { con.setAutoCommit( false ); Statement s ..... ... } catch ( BatchUpdateException e ) { con.rollback(); }

1178

16.7

16.7.2

Elemente einer Datenbank hinzufügen und aktualisieren

Die Ausnahmen bei JDBC, SQLException und Unterklassen

Normale Ausnahmen in Java tragen lediglich eine Nachricht, die sich mit getMessage() erfragen lässt. Da bei Datenbanken aber viele Dinge schiefgehen können, hätten die Architekten der JDBC-API viel zu tun, wenn sie für jede mögliche Ausnahme eine Exception-Klasse bereitstellen wollten. Doch wegen der schier unüberschaubaren Anzahl an Fehlern haben sie sich für ein anderes Modell entschieden.

JDBC-Fehlerbasisklasse SQLException Zunächst einmal gibt es für JDBC-Ausnahmen den Basistyp SQLException. Zusätzlich speichert jedes SQLException-Objekt Fehlercodes, die der JDBC-Treiber der Datenbank setzen und so über den konkreten Fehler informieren kann. Die genauen Informationen einer SQL-Ausnahme sind über drei Methoden zugänglich: Þ String getMessage(): eine textuelle Beschreibung des Fehlers Þ String getSQLState(): Einen String mit dem SQL-Status. Hier gibt es zwei Konventionen.

Einmal kann es ein SQL-Status nach der SQL-CLI-Spezifikation der Open Group (vor über zehn Jahren hieß sie X/Open) sein – oder ein SQL:2003-Code. Beide sind datenbankunabhängig. Nach welcher Spezifikation der Code formuliert ist, sagt die Methode getSQLStateType() vom DatabaseMetaData-Objekt. Der Open-Group-Standard ist üblich. Þ int getErrorCode(): Ein Fehler-Code vom JDBC-Treiber. Er kommt vom Hersteller der Daten-

bank bzw. vom Datenbanktreiber. Er ist datenbankabhängig. Der Open-Group-SQL-Status ist eigentlich eine Zahl, aber als String verpackt. Im Optimalfall ist der Code »00000«, was »Alles paletti« heißt. Die ersten beiden Ziffern stehen für die Fehlerklasse. 01 ist eine Warnung, 02 sagt, dass Daten fehlen, usw.2

Eine Verkettung unglücklicher Tatsachen Eine SQLException hat eine Besonderheit, die sonst keine Ausnahme in der Java-Bibliothek aufweist. Sie implementiert die Schnittstelle Iterable: class java.sql.SQLException extends Exception implements Iterable

2

Unter ftp://ftp.software.ibm.com/ps/products/db2/info/vr6/htm/db2m0/db2state.htm bekommen Leser einen Überblick.

1179

16

16

Datenbankmanagement mit JDBC

Das heißt, dass eine SQLException ein Bündel von Ausnahmen repräsentieren kann und nicht nur genau eine. Welche JDBC-Ausnahmen noch an der SQLException hängen, liefert getNextException() bzw. steckt im Iterator der SQLException.

Beispiel Laufe alle Fehler ab: try { ... } catch ( SQLException e ) { for ( ; e != null; e = e.getNextException() ) { System.err.println( "Message:

" + e.getMessage() );

System.err.println( "SQL State: " + e.getSQLState() ); System.err.println( "Error Code: " + e.getErrorCode() ); } }

SQLWarning Nicht jeder Fehler bzw. jede Meldung der Datenbank ist gleich ein kritischer Fehler, der zum Abbruch der Datenbankoperationen führt. Die JDBC-API bietet mit der Klasse SQLWarning eine besondere Unterklasse von SQLException, doch wird sie nicht als Exception ausgelöst, sondern muss im Programm explizit über getWarnings() geholt werden. Die Typen Connection, ResultSet und Statement deklarieren diese Operation. Im besten Fall holen sich Entwickler alle Warnungen und loggen sie. Da die SQLWarning eine SQLException ist, ist auch die Verarbeitung von SQL-Code und Fehlercode gleich. Anstatt jedoch mit getNextException() zu arbeiten, bietet SQLWarning die Methode getNextWarning() um zur nächsten Warnung vorzustoßen. Werden die Meldungen nicht geholt, dann werden sie bei der Ausführung der nächsten SQL-Anweisung gelöscht.

Daten fehlen Für den SQL-Status »01004« und »22001« gibt es eine eigene Fehlerklasse, die DataTruncation. Sie ist ein spezieller Typ einer SQL-Warnung und wird immer dann erzeugt, wenn Daten während der Schreib- oder Leseoperationen verloren gingen. Die Meldung wird genauso geholt wie SQLWarning, nur wird mittels instanceof DataTruncation überprüft, ob es sich um DataTruncation handelt. Dies erfordert eine Typumwandlung von SQLWarning auf DataTruncation. Dann stehen Methoden wie getIndex() oder getTransferedSize() bereit, die aussagen, für welche

1180

16.7

Elemente einer Datenbank hinzufügen und aktualisieren

Spalte wie viel Bytes korrekt übertragen wurden. DataTruncation ist die einzige Unterklasse von SQLWarning.

16

Abbildung 16.17: Vererbungshierarchie der JDBC-Fehlerklassen

1181

16

Datenbankmanagement mit JDBC

16.8 ResultSet und RowSet * Ein ResultSet haben wir als Lieferanten von Zeileninformationen kennengelernt. Mit einem Cursor konnten wir über Zeilen laufen und auch scrollen, wenn positionierbare Cursor angemeldet waren. Updates waren ebenfalls möglich. Ein ResultSet ist allerdings kein Datencontainer, vergleichbar mit einer Liste, die alle Daten bei sich hat. Das ResultSet muss immer eine Verbindung mit der Datenbank haben, und ein Aufruf von next() könnte sich die Zeileninformationen immer von der Datenbank besorgen. Ein ResultSet ist also kein Container, der Daten speichert. Aus diesem Grund implementiert ResultSet auch nicht die Schnittstelle Serializable.

16.8.1

Die Schnittstelle RowSet

Mittlerweile gibt es die Schnittstelle javax.sql.RowSet, die von interessanten Klassen implementiert wird. Ein RowSet ist ein ResultSet – das heißt, die Schnittstelle RowSet erbt von ResultSet – und schreibt weiteres Verhalten vor. In erster Linie ist sie als Container für Daten gedacht. Da sie ein ResultSet ist, kann sie natürlich alles, was ein normales ResultSet auch kann: mittels getXXX() Daten besorgen und mit updateXXX() Daten aktualisieren. Aber als Container verhält sie sich wie eine JavaBean, und an sie lassen sich Listener hängen. Sie informieren, wann etwa Elemente eingeführt oder gelöscht werden. Falls es beispielsweise eine Visualisierung gibt, kann sie sich abhängig von den Veränderungen immer informieren lassen und die neuen Werte anzeigen. Zwar kann ein scrollendes ResultSet auch den Cursor nach oben bewegen und ihn an eine beliebige Position setzen, das RowSet kann das aber immer, auch wenn das ResultSet diese Eigenschaft nicht hat. Zusätzlich lässt sich ein RowSet auch serialisieren und ist prinzipiell nicht an Datenbanken gebunden, und die Daten können auch von einer Excel-Tabelle kommen. Jedes RowSet hat seine eigenen Metadaten, die durch ein RowSetMetaDataImpl-Objekt implementiert werden.

16.8.2 Implementierungen von RowSet Um ein RowSet aufzubauen, gibt es unterschiedliche Implementierungen. Da es ein Aufsatz auf die JDBC-API ist, ist es auch nicht mit einem Treiber verbunden, sondern kann unabhängig vom Treiber implementiert werden – die Umsetzung ist generisch. Auch gibt es nicht nur ein RowSet, sondern wir unterscheiden zwischen verschiedenen Typen, die als Unterschnittstellen von javax.sql.RowSet im Paket javax.sql.rowset deklariert sind: Þ JDBCRowSet ist ein kleiner Wrapper um das ResultSet, um es als JavaBean zugänglich zu ma-

chen. Eine Verbindung zur Datenbank muss bestehen.

1182

16.8

ResultSet und RowSet *

Þ Ein CachedRowSet benötigt initial eine Verbindung zur Datenbank, um mit einer SQL-Anwei-

sung automatisch alle Daten zu lesen. Anschließend ist keine Verbindung zur Datenbank nötig, wobei geänderte Daten später zurückgespielt werden können. Das ist perfekt in solchen Fällen, in denen Daten auf ein mobiles Endgerät wandern, dort Änderungen erfahren und diese später wieder eingespielt werden sollen. Þ Ein WebRowSet erweitert CachedRowSet und bildet Daten und Operationen in XML ab, um sie

zum Beispiel für Web-Services übertragen zu können. Þ Das FilteredRowSet ist ein WebRowSet und ermöglicht eine zusätzliche Selektion mit Prädika-

ten. Þ Ein JoinRowSet ist ebenfalls ein spezielles WebRowSet, das Daten unterschiedlicher RowSet-

Schnittstellen wie über ein SQL-JOIN verbinden kann. Seit Java 7 liefert der RowSetProvider.createFactory() eine Implementierung von RowSetFactory, das mit unterschiedlichen createXXXRowSet()-Methoden die Row-Sets liefert. Vor Version 7 liefert Java zwar Implementierungen der RowSet-Schnittstellen mit, doch das waren com.sun.rowset.XXXSetImpl-Klassen. Einige Datenbankhersteller liefern eigene Implementierungen mit aus, die vor Java 7 dann explizit angesprochen werden mussten.

16.8.3 Der Typ CachedRowSet Um ein CachedRowSet aufzubauen, das keine dauerhafte Verbindung zur Datenbank benötigt, ist zunächst mit RowSetProvider.newFactory().createCachedRowSet() ein Implementierung von CachedRowSet aufzubauen. Anschließend ist dem Objekt zu sagen, welcher Datenbank welche Daten zu entnehmen sind. Wie üblich muss vorher der Treiber geladen sein: Listing 16.7: com/tutego/insel/jdbc/CachedRowSetDemo.java, main() CachedRowSet crset = RowSetProvider.newFactory().createCachedRowSet(); crset.setDataSourceName( "TutegoDS" ); crset.setCommand( "SELECT * FROM Customer" ); crset.execute();

Kommen die Daten nicht aus einer DataSource, bestimmt setUrl() die JDC-URL. In beiden Fällen können setUsername() und setPassword() den Benutzernamen und das Passwort angeben. Damit die Bean weiß, welche Daten sie aufnehmen soll, ist eine SQL-Anweisung zu formulieren. Der Aufruf execute() füllt das CachedRowSet (mit einem Logger lässt sich gut beobachten, dass die Datenbankverbindung auf- und dann wieder abgebaut wird). Falls eine Verbindung zur Datenbank schon besteht, kann bei execute() auch ein Connection-Objekt übergeben werden. Das CachedRowSet führt dann die gesetzte Anweisung über die bestehende Connection aus und überträgt die Daten aus der Datenbank in die Bean.

1183

16

16

Datenbankmanagement mit JDBC

Wie aus ResultSet bekannt ist, lässt sich mit next() durch die Ergebnismenge eines RowSet iterieren: while ( crset.next() ) System.out.println( crset.getString(1) ); crset.close();

Eine Position zurück geht previous(). Absolute Positionierung ist erlaubt und mit der Methode absolute(int position) möglich. Die aktuelle Zeile liefert getRow(), und die Anzahl geladener Zeilen liefert size(). Mit den updateXXX()-Methoden lassen sich Zeilen ändern. Vor einer Bewegung des Cursors muss updateRow() die Änderung bestätigen. Grundsätzlich müssen Änderungen mit setConcurrency() angekündigt sein, denn der Standardmodus ist ResultSet.CONCUR_READ_ONLY: crset.setConcurrency( ResultSet.CONCUR_UPDATABLE );

Änderungen werden an die Datenbank nur mit einer speziellen Methode zurückgeschrieben, die in der Regel am Ende aller Operationen aufgerufen wird: acceptChanges(). Spätestens dann muss es wieder eine Verbindung zur Datenbank geben. crset.acceptChanges();

Veränderte Werte werden dann in der Datenbank aktualisiert und überschrieben. Wiederum ist auch eine Variante mit einem Connection-Parameter implementiert, die die Daten zu einer existierenden Datenbankverbindung schreibt.

16.8.4 Der Typ WebRowSet Das WebRowSet schreibt zusätzlich zur Oberschnittstelle CachedRowSet nur zwei Arten von Operationen vor: readXml() und writeXml(). Die Lese-Methoden übertragen aus unterschiedlichen Datenquellen – etwa InputStream oder Reader – die Daten auf das WebRowSet. Die Schreibmethoden schreiben das RowSet in einen OutputStream oder Writer: Listing 16.8: com/tutego/insel/jdbc/WebRowSetDemo.java, main() WebRowSet data = RowSetProvider.newFactory().createWebRowSet(); data.setDataSourceName( "TutegoDS" ); data.setCommand( "SELECT * FROM Customer" ); data.setMaxRows( 2 ); data.execute(); data.writeXml( System.out ); data.close();

1184

16.8

ResultSet und RowSet *

Die (gekürzte) Ausgabe sieht so aus:

SELECT * FROM Customer 1008 TutegoDS true 1000 0 2



0

16

2 0 true ResultSet.TYPE_SCROLL_INSENSITIVE false Customer

com.sun.rowset.providers. RIOptimisticProvider Sun Microsystems Inc. 1.0 2 1

5

1185

16

Datenbankmanagement mit JDBC

1 false false false 0 true true 11 ID ID PUBLIC 10 0 CUSTOMER

4 INTEGER

... ... ... ...

0 Laura Steel 429 Seventh Av. Dallas

1 Susanne King 366 – 20th Ave.

1186

16.9

Vorbereitete Anweisungen (Prepared Statements)

Olten



16.9 Vorbereitete Anweisungen (Prepared Statements) Die SQL-Anweisungen, die mittels execute(), executeQuery() oder executeUpdate() an die Datenbank gesendet werden, haben bis zur Ausführung im Datenbanksystem einige Umwandlungen vor sich. Zuerst müssen sie auf syntaktische Korrektheit getestet werden. Dann werden sie in einen internen Ausführungsplan der Datenbank übersetzt und mit anderen Transaktionen optimal verzahnt. Der Aufwand für jede Anweisung ist messbar. Deutlich besser wäre es jedoch, eine Art Vorübersetzung für SQL-Anweisungen zu nutzen. Diese Vorübersetzung ist eine Eigenschaft, die JDBC unterstützt und die sich Prepared Statements nennt. »Vorbereitet« (engl. prepared) deshalb, weil die Anweisungen in einem ersten Schritt zur Datenbank geschickt und dort in ein internes Format umgesetzt werden. Später verweist ein Programm auf diese vorübersetzten Anweisungen, und die Datenbank kann sie schnell ausführen, da sie in einem optimalen Format vorliegen. Ein Geschwindigkeitsvorteil macht sich immer dann besonders bemerkbar, wenn Schleifen Änderungen an Tabellenspalten vornehmen. Dies kann durch die vorbereiteten Anweisungen schneller geschehen.

Hinweis Nicht jedes Datenbanksystem unterstützt Prepared Statements.

16.9.1

PreparedStatement-Objekte vorbereiten

Wie createStatement() auf einem Connection-Objekt ein Statement-Objekt aufbaut, so legt prepareStatement() ein PreparedStatement-Objekt an. Als Argument wird eine SQL-Zeichenkette

übergeben, die den gleichen Aufbau wie etwa ein executeUpdate() hat. Einen Unterschied werden wir jedoch feststellen: Bei den normalen Statement-Objekten können wir dynamische Einträge einfach in den String mit einbauen. Dies geht bei vorbereiteten Anweisungen nicht mehr. Woher sollte auch die Anweisung wissen, was der Benutzer in seine Eingabemaske tippt? Damit jetzt auch eine vorbereitete Anweisung Parameter enthalten kann, werden in die Zeichenkette Platzhalter mit einem Fragezeichen eingefügt.

1187

16

16

Datenbankmanagement mit JDBC

Beispiel Aufbau eines PreparedStatement-Objekts mit einem parametrisierten String: PreparedStatement updateLieferant = con.prepareStatement( "UPDATE Lieferanten SET Adresse = ? WHERE Adresse LIKE ?" );

Abbildung 16.18: PreparedStatement ist ein besonderes Statement.

1188

16.9

Vorbereitete Anweisungen (Prepared Statements)

Die Zeile instruiert die Datenbank, die Zeile zu interpretieren, in das interne Format umzusetzen und vorbereitet zu halten. Im nächsten Schritt muss die Anweisung für die Platzhalter Werte einsetzen.

16.9.2 Werte für die Platzhalter eines PreparedStatement Bevor die executeUpdate()-Methode die vorbereitete Anweisung abarbeitet, müssen die Platzhalter gefüllt werden. Dazu bietet das PreparedStatement-Objekt für die Datentypen jeweils eine setXXX()-Methode an, die den Wert für einen angegebenen Platzhalter setzt. So wird setInt(1,100) die Zahl 100 für das erste Fragezeichen einsetzen. Nach der Zuweisung ist das Objekt für die Ausführung bereit. executeUpdate() kann aufgerufen werden: PreparedStatement updateLieferant = con.prepareStatement( "UPDATE Lieferanten SET Adresse = ? WHERE Adresse LIKE ?" ); updateLieferant.setString( 1, "Uferstraße 80" ); updateLieferant.setString( 2, "Uferstrasse 78" ); updateLieferant.executeUpdate();

Vergleichen wir diese Zeilen mit der Lösung ohne PreparedStatement: String updateString = "UPDATE Lieferanten SET Adresse = 'Uferstraße 80' " +

16

"WHERE Adresse LIKE 'Uferstrasse 78'"; stmt.executeUpdate( updateString );

Die Anweisung ist zwar etwas kürzer, aber dadurch mit der notwendigen Übersetzungszeit verbunden, insbesondere dann, wenn sich die Werte ändern. In einer Schleife lässt sich nun immer wieder executeUpdate() aufrufen, und die schon gesetzten Parameter werden übernommen. Ein Aufruf von clearParameters() löscht alle Parameter. PreparedStatement updateLieferant = con.prepareStatement( "UPDATE Lieferanten SET Adresse = ? WHERE Adresse LIKE ?" ); updateLieferant.setString( 1, "Uferstraße 80" ); updateLieferant.setString( 2, "Uferstrasse 78" ); updateLieferant.executeUpdate(); updateLieferant.setString( 1, "Sommerstraße 23" ); updateLieferant.setString( 2, "Sommerstrasse 23" ); updateLieferant.executeUpdate();

1189

16

Datenbankmanagement mit JDBC

16.10 Transaktionen Transaktionen sind für Datenbanken ein sehr wichtiges Konzept, denn nur so bleibt die Integrität der Daten erhalten. Transaktionen sind mit einer atomaren Ausführung bei Threads vergleichbar, mit dem Unterschied, dass die Datenbank inmitten einer gescheiterten Transaktion die bisher veränderten Werte rückgängig macht. In der Standardverarbeitung in JDBC wird jede SQL-Anweisung für sich als Transaktion abgearbeitet. Dies nennt sich Auto-Commit. Um jedoch eine Folge von Anweisungen in einer Transaktion auszuführen, muss zunächst das Auto-Commit zurückgesetzt werden. Dann werden die Datenbankmanipulationen ausgeführt, und die Transaktion kann anschließend abgeschlossen (commit) oder zurückgesetzt (rollback) werden.

Beispiel Datenbankoperationen sollen in einer Transaktion ausgeführt werden: con.setAutoCommit( false ); // Datenbankmanipulationen vornehmen con.commit(); con.setAutoCommit( true );

// Standard wiederherstellen

Tritt ein Fehler auf, können wir mit con.rollback() die gestartete Transaktion zurücksetzen. Dann ist es lohnenswert, eine Ausnahmebehandlung zu schreiben und im catch das rollback() einzusetzen.

16.11

Metadaten *

Von einer Datenbank können verschiedene Informationen ausgelesen werden. Zum einen sind dies Informationen zu einer bestimmten Tabelle, zum anderen Informationen über die Datenbank selbst.

16.11.1

Metadaten über die Tabelle

Bei der Abfrage über alle Spalten müssen wir die Struktur der Datenbank kennen, insbesondere dann, wenn wir allgemeine Abfragen vornehmen und die passenden Daten herauslesen wollen. So liefert SELECT * FROM Item ein ResultSet mit der Anzahl der Spalten, wie sie die Tabelle Item hat. Doch bevor wir nicht die Anzahl und die Art der Spalten kennen, können wir nicht auf die Daten zugreifen.

1190

16.11

Metadaten *

Um diese Art von Informationen, sogenannte Metadaten, in Erfahrung zu bringen, befindet sich die Klasse ResultSetMetaData, mit der wir diese Informationen erhalten, unter den SQLKlassen. Metadaten können für jede Abfrage angefordert werden. So lässt sich unter anderem leicht herausfinden: Þ wie viele Spalten wir in einer Zeile abfragen können Þ wie der Name der Spalte lautet Þ welchen SQL-Typ die Spalte hat Þ ob NULL für eine Spalte in Ordnung ist Þ wie viele Dezimalzeichen eine Spalte hat

Einige Informationen über die Bestellelemente Um Anzahl und Art der Spalten einer Bestelltabelle herauszufinden, werden wir zunächst ein ResultSet mit stmt.executeQuery("SELECT * FROM Item") erzeugen und dann via getMetaData() ein ResultSetMetaData-Objekt erfragen. Das ResultSetMetaData-Objekt besitzt viele Methoden, um Aussagen über die Tabelle und Spalten zu treffen. So fragen wir mit getColumnCount() nach, wie viele Spalten die Tabelle hat. Anschließend lässt sich für jede Spalte der Name und Typ erfragen: Listing 16.9: com/tutego/insel/jdbc/TableMetaData.java, main()

16

Connection con = ((DataSource) new InitialContext().lookup( "TutegoDS" )).getConnection(); try { ResultSet rs = con.createStatement().executeQuery( "SELECT * FROM ITEM" ); ResultSetMetaData meta = rs.getMetaData(); int numerics = 0; for ( int i = 1; i >, um für alle Tabellen Entity-Beans zu generieren (siehe Abbildung 16.20).

1199

16

16

Datenbankmanagement mit JDBC

Abbildung 16.19: NetBeans-Dialog zum Anlegen neuer Entity-Klassen

Abbildung 16.20: Tabellen für den Quellcode-Generator hinzufügen

Nach Next erscheint ein neuer Dialog. Einstellungen sind nur beim Paket nötig. Ich wähle com.tutego.insel.jpa:

1200

16.13

JPA-Beispiel mit der NetBeans-IDE

Abbildung 16.21: Wählen des Paketnamens und Abschluss des Dialogs

Danach ist ein Finish fällig.

16 16.13.2

Die Quellen im Überblick

Der Generator hat eine ganze Reihe von Dateien generiert und Eintragungen gemacht: Þ Neue Java-Archive stehen im Klassenpfad. Sie enthalten die JPA-Annotationen und den

JPA-Provider EclipseLink (die Referenzimplementierung für JPA). Þ Die Entity-Bean-Klassen wurden generiert – ihr Name stammt vom Tabellennamen. Þ Ein META-INF-Verzeichnis führt die Datei persistence.xml auf.

Abbildung 16.22: Generierte Dateien

1201

16

Datenbankmanagement mit JDBC

Die Customer-Klasse repräsentiert die CUSTOMER-Tabelle. Ein Ausschnitt aus der Datei sieht so aus: @Entity @Table(name = "CUSTOMER") @NamedQueries({ @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c"), @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c

ð

WHERE c.customerId = :customerId"), ... @NamedQuery(name = "Customer.findByCreditLimit", query = "SELECT c FROM Customer c WHERE c.creditLimit = :creditLimit")}) public class Customer implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "CUSTOMER_ID") private Integer customerId; @Basic(optional = false) @Column(name = "ZIP") private String zip; ... public Customer() { } ... public Integer getCustomerId() { return customerId; } public void setCustomerId(Integer customerId) { this.customerId = customerId; }

1202

ð

16.13

JPA-Beispiel mit der NetBeans-IDE

public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; } ... }

Es lässt sich festhalten, dass es für jede Spalte in der Tabelle ein Attribut und Setter/Getter gibt. Die anderen Beans sehen im Prinzip genauso aus.

16.13.3

Persistence Unit

Die Datei im META-INF-Vezeichnis ist eine sogenannte PU-Datei (für Persistence Unit). Sie zählt alle Klassen auf und enthält die Zugangsdaten zur Datenbank.

16

Abbildung 16.23: Editor mit persistence.xml

Der Persistence-Unit-Name ist InselPU, und diesen Namen brauchen wir später noch.

1203

16

Datenbankmanagement mit JDBC

16.13.4

Ein JPA-Beispielprogramm

Der NetBeans-Editor hat extra einen eigenen Menüpunkt für JPA, sodass es einfach ist, ein kleines Beispiel aufzubauen. Beginnen wir mit einer Standard-Klasse. (Das geht in NetBeans über File 폷 New File... 폷 Java 폷 Java Class.) Nennen wir die Klasse JpaDemo. Daraufhin öffnet sich der Editor mit der neuen Klasse. import com.tutego.insel.jpa.Customer; import com.tutego.insel.jpa.DiscountCode; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class JpaDemo { public static void main( String[] args ) { EntityManagerFactory emf = Persistence.createEntityManagerFactory( "InselPU" ); EntityManager em = emf.createEntityManager(); try { DiscountCode discountCode = em.find( DiscountCode.class, 'H' ); Customer customer1 = new Customer( 888 ); customer1.setName( "Chris" ); customer1.setZip( "999" ); customer1.setDiscountCode( discountCode ); em.getTransaction().begin(); em.persist( customer1 ); em.getTransaction().commit(); String query = "SELECT c FROM Customer c"; for ( Customer c : em.createQuery( query, Customer.class ).getResultList() ) System.out.println( c.getName() ); } catch ( Exception e ) { e.printStackTrace(); em.getTransaction().rollback();

1204

16.14

Zum Weiterlesen

} finally { em.close(); } } }

Das Programm besorgt sich Zugang zum OR-Mapper – vereinfacht gesagt repräsentiert als EntityManager-Objekt. Der EntityManager bietet Methoden wie persist() zum Speichern oder find() zum Suchen. Der erste JPA-Code erfragt ein DiscountCode-Objekt, was mit der ID 'H' in der Datenbank steckt. Mit diesem Discount-Code legen wir einen neuen Kunden an. Anschließend geben wir alle Kunden aus, und der neue Kunde ist natürlich dabei: JumboCom Livermore Enterprises Oak Computers Nano Apple HostProCom CentralComp Golden Valley Computers

16

Top Network Systems West Valley Inc. Ford Motor Co Big Car Parts New Media Productions Yankee Computer Repair Chris

Wenn es einen Fehler der Art »Exception in thread "main" javax.persistence.PersistenceException: Exception [EclipseLink-4003] (Eclipse Persistence Services – 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.DatabaseException« gibt, dann fehlt der JDBC-Treiber für die Derby-Datenbank; Abschnitt 16.4.3 erklärt, wie er einzubinden ist.

16.14

Zum Weiterlesen

Direkter Datenbankzugriff über JDBC ist heutzutage selten. Gut entworfene Programme nutzen im Allgemeinen OR-Mapper, also Bibliotheken, die Java-Objekte auf die Datenbank-

1205

16

Datenbankmanagement mit JDBC

relationen abbilden. Eine Standard-API für objekt-relationale Abbildungen ist die Java Persistence API (JPA) – eine kurze Charakterisierung gibt es unter http://www.oracle.com/technetwork/java/javaee/documentation/index.html. Eine JPA-Implementierung heißt Provider, übernimmt die Abbildung auf die Datenbank und bietet Methoden zum Erzeugen, Lesen, Aktualisieren und Löschen von Datensätzen an (sogenannte CRUD-Operationen). JPA ist Teil von Java EE 5, aber auch ohne Enterprise Container verwendbar. Die beliebte Open-Source-Software Hibernate (http://www.hibernate.org/), die von JBoss (also Red Hat) finanziell unterstützt wird, implementiert die JPA und bietet darüber hinausgehende Funktionalität. Dass jedoch auch hier Ungemach von der Patentseite droht, diskutiert http://www.patentlyo.com/ patent/2006/06/red_hat_faces_p.html.

1206

Kapitel 17 Technologien für die Infrastruktur »Wenn einer keine Angst hat, hat er keine Phantasie.« – Erich Kästner (1899–1974)

17.1

Property-Validierung durch Bean Validation

In den Settern einer JavaBean konnten wir mithilfe der PropertyChangeEvents Änderungen melden. Gleichsam kann ein Listener gegen eine ungewünschte Belegung sein Veto einlegen. Wenn eine Property selbst einen bestimmten Wert nicht annehmen kann, kann sie gut eine IllegalArgumentException melden. Die Validierung innerhalb von Settern ist aber nur lokal und nicht besonders flexibel, wenn es zum Beispiel Abhängigkeiten zwischen den Properties gibt oder temporäre Falschbelegungen erlaubt sein sollten. So lässt sich ein anderer Weg einschlagen, nämlich dass zunächst eine Property mit allen möglichen Werten initialisiert werden darf (also auch das Alter einer Person mit 1000) und erst später das Objekt zu einem Validator gegeben wird, der schaut, ob Property-Belegungen erlaubt sind. Seit Java EE 6 gibt es hier einen Standard, der im JSR-303, »Bean Validation«, beschrieben ist. An die Properties werden Annotationen gesetzt, die zum Beispiel erlaubte Minimal-/Maximalwerte für Zahlen bestimmen oder reguläre Ausdrücke für gültige Stringbelegungen. Dieses Vorgehen ist deklarativ und kommt ohne Programmierung aus. Im zweiten Schritt wird ein Validierer auf die Bean angesetzt, der die Zustände selbstständig ausliest und auf die Einschränkungen testet.

Bezug der JSR-303-Referenz-Implementierung Jeder Java EE 6-Container bringt bereits eine Implementierung mit. Da Java 7 nicht mit einer Implementierung der JSR-303 daherkommt, muss sie extra installiert werden. Die Referenzimplementierung hat ihr Zuhause bei http://www.hibernate.org/subprojects/validator.html, und der Download befindet sich unter http://sourceforge.net/projects/hibernate/files/hibernate-validator. Im Zip-Archiv hibernate-validator-4.2.0.Final-dist.zip (etwa 16 MiB groß) stehen mehrere Jar-Dateien, die wir in den Klassenpfad aufnehmen müssen:

1207

17

17

Technologien für die Infrastruktur

Þ Zunächst ist es validation-api-1.0.0.GA.jar im Verzeichnis lib\required, das die neuen

Annotationen wie @NotNull, @Size, @Pattern, ... deklariert. Þ Dann folgt mit hibernate-validator-4.0.2.GA.jar die eigentliche Implementierung des

Frameworks.

Eine Person, die nie null heißen durfte Hat ein Spieler einen Namen und ein Alter, so lassen sich leicht erste Gültigkeitsregeln aufstellen. Der Name soll gesetzt sein, also nicht null sein, und das Alter soll zwischen 10 und 110 liegen. Genau diese sogenannten Constraints werden über Annotationen an die Objektvariablen oder Getter gesetzt. Listing 17.1: com/tutego/insel/bean/validation/Player.java, Player import javax.validation.constraints.*; public class Player { private String name; @Min(10) @Max(110) private int

age;

public void setName( String name ) { this.name = name; } @NotNull public String getName() { return name; } public void setAge( int age ) { this.age = age; }

1208

17.1

Property-Validierung durch Bean Validation

public int getAge() { return age; } }

Die Annotation @NotNull sagt aus, dass getName() nie null liefern darf, und @Min(10)/@Max(110) besagt, dass sich age zwischen 10 und 110 (jeweils inklusiv) bewegen muss. Dass @Min(10) vor @Max(110) geschrieben wird, ist unwichtig, denn die Reihenfolge beim Prüfen ist unbestimmt. Ob die Annotation an den Attributen oder Gettern der Property sitzt, ist egal; im ersten Fall greift das Validierungsframework direkt auf das Attribut zu, und sind die Getter annotiert, wird die getXXX()-Methode aufgerufen. Die Sichtbarkeit der Objektvariablen spielt keine Rolle. Statische Variablen können nicht annotiert und geprüft werden.

Durchführen der Validierung Das Beispiel zeigt die erste Hälfte der JavaBean Validation: die deklarative Angabe von gültigen Belegungen. Die zweite Hälfte von Validiation besteht aus einer API, damit die tatsächliche Überprüfung auch stattfinden kann und Fehler der einzelnen Properties erkannt werden. Listing 17.2: com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 1

17

Player p = new Player(); Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set constraintViolations = validator.validate( p ); for ( ConstraintViolation violation : constraintViolations ) System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Der Player wird nicht korrekt mit einem Namen und einem Alter initialisiert, sodass zwei Fehler zu erwarten sind. Die tatsächliche Überprüfung übernimmt ein Validator-Objekt, das über eine Fabrik erfragt wird. Der Validator besitzt eine validate()-Methode, die als Argument das zu validierende Objekt bekommt. Die Rückgabe ist eine Menge von Fehler-Objekten; gibt es keinen Fehler, ist die Menge leer. Da wir in unserem Beispiel zwei Fehler haben, iteriert die Schleife durch die Menge mit den ConstraintViolation-Objekten und gibt den Namen der Property und eine vordefinierte Fehlermeldung aus. Nach dem Start ergibt sich folgende Ausgabe (wobei die Ausgaben vom Logger ignoriert werden):

1209

17

Technologien für die Infrastruktur

age muss grössergleich 10 sein name kann nicht null sein

Dass die Ausgaben auf Deutsch sind, sollte uns nicht verwundern, denn die Implementierung ist internationalisiert. Der Validator hat also die beiden Fehler korrekt erkannt. In der Ausgabe sehen wir vom ConstraintViolation-Objekt die Rückgaben von getPropertyPath() und getMessage(). Die erste Methode liefert den Namen der Property, die zweite eine Standard-Meldung, die wir aber auch überschreiben können, indem wir in der Annotation die Eigenschaft message setzen, etwa so: @NotNull(message="muss ungleich null sein"). Zusätzliche Methoden von ConstraintViolation sind unter anderem getRootBean(), getRootBeanClass(), getLeafBean() und getInvalidValue(). Mit validate(p) setzen wir den Validator auf das ganze Player-Objekt an. Es lassen sich aber auch einzelne Properties validieren. Dazu wird validateProperty() eingesetzt, eine Methode, die erst das zu validierende Objekt erwartet und anschließend im String den Namen der Property. Listing 17.3: com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 2 Set ageViolation = validator.validateProperty( p, "age" ); if ( ! ageViolation.isEmpty() ) System.out.println( new ArrayList(ageViolation) .get( 0 ).getMessage() );

Die von validateProperty() zurückgegebene Menge ist entweder leer oder enthält Fehler. In der letzten Zeile kopieren wir zum Test die Fehlermenge in eine Liste und greifen auf den ersten Fehler zu. class javax.validation.Validation Þ static ValidatorFactory buildDefaultValidatorFactory()

Gibt die Standard-ValidatorFactory zurück. interface javax.validation.ValidatorFactory Þ Validator getValidator()

Liefert den Validator der Fabrik.

1210

17.1

Property-Validierung durch Bean Validation

interface javax.validation.Validator Þ Set validateProperty(T object, String propertyName,

Class... groups)

Validiert vom Objekt object die Eigenschaft propertyName. Optionale Gruppen (sie werden später vorgestellt) sind möglich. Þ Set validate(T object, Class... groups)

Validiert das gesamte Objekt. interface javax.validation.ConstraintViolation Þ String getMessage()

Liefert die Fehlernachricht. Þ T getRootBean()

Liefert die Hauptbean, die validiert wird. Þ T getLeafBean()

Liefert die tatsächliche Bean, bei der die Eigenschaft validiert wird. Þ Object getInvalidValue()

Liefert den fehlerhaften Wert.

Hinweis Das Validation-Framework kann ganze Objektgraphen beachten und so eine tiefe Validierung durchführen. Wenn etwa ein Game-Objekt einen Player referenziert und die Validierung auf dem Game ausgeführt wird, kann der Player mit überprüft werden. Automatisch wird diese tiefe Prüfung aber nicht durchgeführt. Die Game-Klasse muss an der Player-Referenz die Annotation @Valid tragen. Selbst wenn das Game in einer Datenstruktur (Feld, Map oder alles Iterable wie Collection) viele Player referenziert, werden alle Spieler in der Sammlung überprüft, wenn die Sammlung die @Valid-Annotation trägt.

Die Constraints im Überblick Unser Player nutzt drei Constraints, aber standardmäßig gibt es noch einige mehr. Zudem lassen sich eigene Constraints leicht programmieren. Die folgende Tabelle gibt einen Überblick über alle vordefinierten Contraints: Constraint-Annotation

Aufgabe

Gültig an den Typen

@Null

Die Referenz muss null bezie-

Referenzvariablen

@NotNull

hungsweise nicht null sein.

Tabelle 17.1: Die wichtigsten Annotationen von Bean-Validation

1211

17

17

Technologien für die Infrastruktur

Constraint-Annotation

Aufgabe

Gültig an den Typen

@AssertTrue

Das Element muss true bezie-

boolean/Boolean

@AssertFalse

hungsweise false sein.

@Min(value=)

Muss eine Zahl und größer/klei-

byte/Byte, short/Short,

@Max(value=)

ner oder gleich dem Wert sein.

int/Integer, long/Long, BigInteger, BigDecimal

@DecimalMin(value=)

Muss eine Zahl und größer/klei-

@DecimalMax(value=)

ner oder gleich dem Wert sein.

Double/Double und float/ Float sowie String, byte/ Byte, short/Short, int/ Integer, long/Long, BigInteger, BigDecimal

@Size([min=],[max=])

Die Größe muss sich in einem

String, Collection, Map, Feld

Intervall bewegen. Das Element muss eine gege-

String, byte/Byte, short/

bene Anzahl an Stellen besit-

Short, int/Integer, long/

zen.

Long, BigInteger, BigDecimal

@Past

Das Element ist ein Datum in

Date, Calendar

@Future

der Vergangenheit/Zukunft

@Digits(integer=,fraction=)

bezogen auf jetzt. @Pattern(regex=[,flags=])

Der String muss einem Pattern

String

gehorchen. Tabelle 17.1: Die wichtigsten Annotationen von Bean-Validation (Forts.)

Der Unterschied zwischen @Min/@Max und @DecimalMin/@DecimalMax ist der, dass im zweiten Fall ein String angegeben wird. So sind @Min(10) und @DecimalMax("10") gleichwertig. Bei @Digits ist die Angabe der Nachkommastellen nicht optional. Bei @Size können min/max alleine oder zusammen angegeben werden. Beim @Pattern ist flags optional. Ohne Flag wird nur der reguläre Ausdruck als String angegeben, etwa so: @Pattern(regexp="\\d*"). Falls Flags angegeben werden, entsprechen sie dem Flag der Pattern-Klasse, etwa @Pattern(regexp="\\d*", flags=Pattern.Flag.CASE_INSENSITIVE).

Hinweis Der Standard sieht double und float aufgrund von Rundungsproblemen nicht bei @Min/@Max/ @DecimalMin/@DecimalMax vor, wobei die tatsächliche Implementierung das durchaus berücksichtigen kann. Wer Einschränkungen auf Fließkommazahlen benötigt, muss also im Moment den Variablentyp auf java.math.BigDecimal setzen.

1212

17.1

Property-Validierung durch Bean Validation

Alle Annotationstypen deklarieren einen inneren Annotationstyp List, der eine Auflistung vom gleichen Contraint-Typ erlaubt. Die Verkettung erfolgt nach dem Oder-Prinzip. List ist sinnvoll, denn mehrfach kann die gleiche Annotation nicht an einem Element stehen.

Beispiel Die Zeichenkette ist entweder eine E-Mail oder eine Webadresse: @Pattern.List({ @Pattern(regexp="[\\w|-]+@\\w[\\w|-]*\\.[a-z]{2,3}"), @Pattern(regexp="https?://[-\\w]+(\\.\\w[-\\w]*)") }) String emailOrWeb;

Es ist naheliegend, diese beiden komplexen Ausdrücke nicht zu einem großen unleserlichen regulären Ausdruck zu vermengen. Die Liste ist auch sinnvoll, wenn jeder Teilausdruck zu unterschiedlichen Gruppen gehört – zu den Gruppen folgt jetzt mehr.

Validierung von Gruppen Ist ein Objekt korrekt, erfüllt es alle Validierungs-Constraints. Dieses Alles-oder-nichts-Prinzip ist jedoch etwas hart. Betrachten wir zwei Szenarien: Þ In der grafischen Oberfläche wird ein Spieler erfasst und später in der Datenbank gespei-

chert. Sein Name kann aber in der Oberfläche leer bleiben, da der Platz, falls nichts angegeben ist, mit einem Zufallsnamen gefüllt wird. Geht der Spieler jedoch in die Datenbank, so muss er auf jeden Fall einen Namen tragen. Je nach Lebenszkylus des Spielers sind also unterschiedliche Belegungen erlaubt. Þ Eine grafische Oberfläche zeigt einen Wizard über mehrere Seiten an. Auf der ersten Seite

muss zum Beispiel der Spieler seinen Namen angeben, auf der zweiten Seite sein Alter. Würde auf der ersten Seite der Spieler komplett validiert werden, wäre das Alter ja noch gar nicht gesetzt, und die Validierung würde einen Fehler melden. Da ein Objekt in den unterschiedlichsten Phasen korrekt sein kann, lassen sich Gruppen bilden. Um eine eigene Validierungsgruppe zu nutzen, wird 1. eine leere Java-Schnittstelle aufgebaut, 2. einer Annotation wie @NotNull ein Class-Objekt für die Validierungsschnittstelle mitgegeben und 3. der validate()-Methode die Validierungsschnittstelle als letzter Parameter bekannt gegeben. Führen wir das exemplarisch mit einem Spieler durch, der im ersten Schritt einen gültigen Namen haben muss und im zweiten Schritt ebenfalls ein gültiges Alter. Deklarieren wir zwei

1213

17

17

Technologien für die Infrastruktur

Schnittstellen in einer Klasse DialogPlayer. Um es kurz zu halten, verzichtet der DialogPlayer auf Setter/Getter. Listing 17.4: com/tutego/insel/bean/validation/DialogPlayer.java, main() public class DialogPlayer { public interface NameValidation { } public interface AgeValidation extends NameValidation { } @NotNull( groups = NameValidation.class ) public String name; @Min( value = 10, groups = AgeValidation.class ) @Max( value = 110, groups = AgeValidation.class ) public int age; }

Die ersten beiden Schritte sind hier in dem Spieler schon sichtbar. Die zwei Schnittstellen sind deklariert und als groups-Element bei den Validierungsannotationen angegeben. Das Validierungs-Framework erlaubt auch Vererbung, was das Beispiel bei interface AgeValidation extends NameValidation zeigt; AgeValidation basiert auf NameValidation, sodass der Name auf jeden Fall auch korrekt ist – also nicht null sein darf – und nicht nur allein das Alter sich zwischen 10 und 110 bewegt. Jeder Constraint kann zu mehreren Gruppen gehörten, aber das brauchen wir hier nicht. Standardmäßig gibt es auch eine Default-Gruppe, wenn keine eigene Gruppe definiert wird. Versteckt steht dann standardmäßig groups = Default.class. Im dritten Schritt gilt es, der validate()-Methode das Class-Objekt für die Gruppe mitzugeben. Listing 17.5: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 1 Validator v = Validation.buildDefaultValidatorFactory().getValidator(); Set constraintViolations; DialogPlayer p = new DialogPlayer(); constraintViolations = v.validate( p, DialogPlayer.NameValidation.class ); for ( ConstraintViolation violation : constraintViolations ) System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

1214

17.1

Property-Validierung durch Bean Validation

Der DialogPlayer hat keinen Namen, daher ist die Ausgabe »name kann nicht null sein«. Weitere Fehler kommen nicht vor, denn DialogPlayer.NameValidation.class validiert nur alle Eigenschaften, die ein groups = NameValidation.class tragen. Und das trägt nur name. Setzen wir den Namen, und validieren wir neu: Listing 17.6: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 2 p.name = "chris"; System.out.println( v.validate( p, DialogPlayer.NameValidation.class ).size() ); // 0

Es gibt keine Fehlermeldungen mehr, auch wenn das Alter nicht gesetzt ist. Validieren wir mit dem DialogPlayer.AgeValidation, gibt es wieder einen Fehler, da age noch auf 0 war: Listing 17.7: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 3 constraintViolations = v.validate( p, DialogPlayer.AgeValidation.class ); for ( ConstraintViolation violation : constraintViolations ) System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Die Ausgabe ist »age muss grössergleich 10 sein«. Dass AgeValidation einen Namen ungleich null und das Alter prüft, zeigt sich, wenn der name auf null gesetzt wird. Initialisieren wir das Alter, damit es nicht zwei Fehler, sondern nur einen Fehler wegen dem Namen gibt:

17

Listing 17.8: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 4 p.name = null; p.age = 60; constraintViolations = v.validate( p, DialogPlayer.AgeValidation.class ); for ( ConstraintViolation violation : constraintViolations ) System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Jetzt ist die Ausgabe nur »name kann nicht null sein«, da das Alter sich im korrekten Bereich befindet. Mit einem gesetzten Namen verschwinden alle Fehler: Listing 17.9: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 5 p.name = "chris"; System.out.println( v.validate( p, DialogPlayer.NameValidation.class ).size() ); // 0

1215

17

Technologien für die Infrastruktur

Hinweis Den validateXXX()-Methoden kann nicht nur eine Validierungsgruppe mitgegeben werden, sondern über ein Varargs auch mehrere. Doch wenn es etwa validate(p, V1.class, V2.class) heißt, ist die Reihenfolge unbestimmt, auch wenn der Aufruf suggeriert, es würden erst die Eigenschaften aus V1 validiert und dann die aus V2. Um eine feste Reihenfolge vorzugeben, muss eine neue Schnittstelle deklariert werden, die eine Annotation GroupSequence trägt, in der die Reihenfolge bestimmt wird. @GroupSequence( {V1.class, V2.class} ) public interface OrderedValidation {}

Reihenfolgen sind auch nützlich, um schnelle Validierungen zuerst durchzuführen und anschließend Validierungen anzuwenden, die zeitaufwändiger sind.

Eigene Validatoren Die Anzahl der Standard-Validatoren ist beschränkt, doch Anforderungen nach etwa einem E-Mail-Validator ergeben sich schnell. Wir wollen – ohne auf die Details von eigenen Annotationstypen genau einzugehen – einen neuen Validierungsannotationstyp für E-Mail-Adressen schreiben. Beginnen wir mit einer Person, die ein Attribut email besitzt und deren E-Mail-Adresse geprüft werden soll. Die Variable email bekommt unsere eigene Annotation EMail. Listing 17.10: com/tutego/insel/bean/validation/PersonValidator.java, PersonValidator public class PersonValidator { public static class Person { @NotNull @EMail public String email; // = "[email protected]"; } public static void main( String[] args ) { Validator v = Validation.buildDefaultValidatorFactory().getValidator(); Person p = new Person();

1216

17.1

Property-Validierung durch Bean Validation

Set constraintViolations = v.validate( p ); for ( ConstraintViolation violation : constraintViolations ) System.out.println( violation.getPropertyPath() + " " + violation.getMessage() ); } }

Läuft das Programm, gibt es zwei Fehlermeldungen aus, da email gleich null ist und nicht dem entsprechenden Pattern gehorcht: email ist keine gültige E-Mail-Adresse email kann nicht null sein

Die Meldung »ist keine gültige E-Mail-Adresse« kommt von unserem eigenen Annotationstyp. Der ist wie folgt deklariert: Listing 17.11: com/tutego/insel/bean/validation/Email.java, EMail @Constraint( validatedBy = EMailValidator.class ) @Target({ METHOD, FIELD, ANNOTATION_TYPE }) @Retention( RUNTIME ) @Documented public @interface EMail

17

{ String message() default "ist keine gültige E-Mail-Adresse"; Class[] groups() default {}; Class c2 = new java.util.Date().getClass(); // oder Class c3 = Class.forName( "java.util.Date" ); System.out.println( c3 );

// class java.util.Date

} catch ( ClassNotFoundException e ) { e.printStackTrace(); }

Die Variante mit forName() ist sinnvoll, wenn der Klassenname bei der Übersetzung des Programms noch nicht feststand. Sonst ist die vorhergehende Technik eingängiger, und der Compiler kann prüfen, ob es den Typ gibt.

Beispiel Klassenobjekte für primitive Elemente liefert forName() nicht! Die beiden Anweisungen Class.forName("boolean") und Class.forName(boolean.class.getName()) führen zu einer ClassNotFoundException.

1229

18

Reflection und Annotationen

class java.lang.Object Þ final Class forName(String className) throws ClassNotFoundException

Liefert das Class-Exemplar für die Klasse oder Schnittstelle mit dem angegebenen voll qualifizierten Namen. Falls sie bisher noch nicht vom Programm benötigt wurde, sucht und lädt der Klassenlader die Klasse. Die Methode liefert niemals null zurück. Falls die Klasse nicht geladen und eingebunden werden konnte, gibt es eine ClassNotFoundException. Eine alternative Methode forName() ermöglicht auch das Laden mit einem gewünschten Klassenlader. Der Klassenname muss immer voll qualifiziert sein.

ClassNotFoundException und NoClassDefFoundError * Eine ClassNotFoundException lösen die Methoden forName() aus Class und loadClass() bzw. findSystemClass() aus ClassLoader immer dann aus, wenn der Klassenlader die Klasse nach ihrem Klassennamen nicht finden kann. Neben der Exception-Klasse gibt es ein NoClassDefFoundError – ein harter LinkageError, den das System immer dann auslöst, wenn die JVM eine im Bytecode referenzierte Klasse nicht laden kann. Nehmen wir zum Beispiel eine Anweisung wie new MeineKlasse(). Führt die JVM diese Anweisung aus, versucht sie den Bytecode von MeineKlasse zu laden. Ist der Bytecode für MeineKlasse nach dem Compilieren entfernt worden, löst die JVM durch den nicht geglückten Ladeversuch den NoClassDefFoundError aus. Auch tritt der Fehler auf, wenn beim Laden des Bytecodes die Klasse MeineKlasse zwar gefunden wurde, aber MeineKlasse einen statischen Initialisierungsblock besitzt, der wiederum eine Klasse referenziert, für die keine Klassendatei vorhanden ist. Während ClassNotFoundException häufiger vorkommt als NoClassDefFoundError, ist es im Allgemeinen ein Indiz dafür, dass ein Java-Archiv im Klassenpfad fehlt.

Umbenennungen der Klassennamen durch den Obfuscator Dass der Compiler automatisch Bytecode gemäß dieses veränderten Quellcodes erzeugt, führt nur dann zu unerwarteten Problemen, wenn wir einen Obfuscator über den Programmtext laufen lassen, der nachträglich den Bytecode modifiziert und damit die Bedeutung des Programms beziehungsweise des Bytecodes verschleiert und dabei Klassen umbenennt. Offensichtlich darf ein Obfuscator Klassen, deren Class-Exemplare abgefragt werden, nicht umbe-

1230

18.2

Metadaten der Klassen mit dem Class-Objekt

nennen; oder der Obfuscator müsste die entsprechenden Zeichenketten ebenfalls korrekt ersetzen (aber natürlich nicht alle Zeichenketten, die zufällig mit Namen von Klassen übereinstimmen).

18.2.2

Was das Class-Objekt beschreibt *

Ein Class-Exemplar kann eine Schnittstelle, eine Klasse, einen primitiven Datentyp oder auch einen Array-Typ beschreiben. Dies lässt sich durch die drei Methoden isInterface(), isPrimitive() und isArray() herausfinden. Wenn keine der drei Methoden für ein Class-Exemplar true liefert, repräsentiert das Objekt eine gewöhnliche Klasse. Dass es auch Class-Exemplare gibt, die die primitiven Datentypen von Java beschreiben, erstaunt zunächst. Damit ist es jedoch möglich, die Parameter- und Ergebnistypen beliebiger Java-Methoden einheitlich durch Class-Exemplare zu beschreiben. Dazu kodieren jede der acht Wrapper-Klassen, die zu den Datentypen boolean, byte, char, short, int, long, float und double gehören, und die spezielle Klasse für den Typ void eine Konstante TYPE. Benötigen wir ein Class-Objekt für den primitiven Typ int, so greifen wir mit Integer.TYPE (oder alternativ mit int.class) darauf zu. Alle Class-Exemplare für primitive Datentypen werden automatisch von der JVM erzeugt. Die Methode isPrimitive() gibt genau für diese neun besonderen ClassExemplare true zurück, sodass sie von Repräsentanten für echte Klassen unterschieden werden können.

Hinweis

18

Obwohl void kein Typ ist, meldet isPrimitive() dies: System.out.println( void.class.isPrimitive() ); // true

Das folgende Programmstück testet die Attribute von Class-Objekten systematisch durch. Wir benutzen die Methode getName(), um den Namen des Class-Objekts auszugeben. Im nächsten Unterkapitel mehr dazu. Das Class-Objekt für Felder setzt sich aus dem Basistyp und Paaren von eckigen Klammern zusammen, etwa double[][].class. Listing 18.2: com/tutego/insel/meta/CheckClassType.java, CheckClassType class CheckClassType { public static void main( String[] args ) { checkClassType( Observer.class ); checkClassType( Observable.class ); checkClassType( (new int[2][3][4]).getClass() );

1231

18

Reflection und Annotationen

checkClassType( Integer.TYPE ); } static void checkClassType( Class c ) { if ( c.isArray() ) System.out.println( c.getName() + " ist ein Feld." ); else if ( c.isPrimitive() ) System.out.println( c + " ist ein primitiver Typ."); else if ( c.isInterface() ) System.out.println( c.getName() + " ist ein Interface." ); else System.out.println( c.getName() + " ist eine Klasse." ); } }

Die Ausgabe des Programms ist nun: java.util.Observer ist ein Interface. java.util.Observable ist eine Klasse. [[[I ist ein Feld. int ist ein primitiver Typ. final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ boolean isInterface()

Liefert true, wenn das Class-Objekt eine Schnittstelle beschreibt. Þ boolean isArray()

Liefert true, wenn das Class-Objekt einen Array-Typ beschreibt. Þ boolean isPrimitive()

Testet, ob das Class-Objekt einen primitiven Datentyp beschreibt.

Komponententyp bei Feldern Die Methode getComponentType() liefert bei Feldern den Typ der Elemente als Class-Objekt. Steht das Class-Objekt für kein Feld, ist die Methodenrückgabe null.

1232

18.2

System.out.println( double[].class.getComponentType() );

Metadaten der Klassen mit dem Class-Objekt

// double

System.out.println( double[][].class.getComponentType() ); // class [D System.out.println( double.class.getComponentType() );

18.2.3

// null

Der Name der Klasse

Liegt zu einer Klasse das Class-Objekt vor, so können wir zur Laufzeit ihren voll qualifizierten Namen über die Methode getName() ausgeben. Da jeder Typ über einen Namen verfügt, führt diese Methode also jedes Mal zum Ziel: Listing 18.3: SampleName.java String n1 = new java.util.Date().getClass().getName(); System.out.println( n1 );

// java.util.Date

String n2 = java.util.RandomAccess.class.getName(); System.out.println( n2 );

// java.util.RandomAccess

String n3 = Deprecated.class.getName(); System.out.println( n3 );

// java.lang.Deprecated

String n4 = Thread.State.class.getName(); System.out.println( n4 );

// java.lang.Thread$State

Kodierung von Feldern * Schwieriger ist die Kodierung bei Array-Typen, die ja eine besondere Form von Klassen sind. getName() kodiert sie mit einer führenden »[«. Jede Klammer steht dabei für eine Dimension des Array-Typs. Nach den Klammern folgt in einer kodierten Form der Typ der Array-Elemente. So liefert System.out.println( int[][][].class.getName() );

// [[[I

System.out.println( (new int[2][3][4]).getClass().getName() ); // [[[I

den String »[[[I«, also einen dreidimensionalen Array-Typ mit Array-Elementen vom primitiven Typ int. Der Elementtyp ist wie folgt kodiert: Kürzel

Datentyp

B

Byte

C

Char

D

Double

Tabelle 18.1: Kodierung der Elementtypen

1233

18

18

Reflection und Annotationen

Kürzel

Datentyp

F

Float

I

Int

J

Long

LElementtyp;

Klasse oder Schnittstelle

S

Short

Z

Boolean

Tabelle 18.1: Kodierung der Elementtypen (Forts.)

Nimmt das Array Objektreferenzen auf, wird deren Typ in der Form »LKlassenname;« kodiert. So liefert (new Object[3]).getClass().getName() den String [Ljava.lang.Object;. Der Klassenbeziehungsweise Schnittstellenname ist wie üblich voll qualifiziert. Der String ist auch für Class.forName() von Bedeutung. Im Fall von Arrays liefert die Methode ein Class-Objekt für den Elementtyp. Die ersten Versuche, ein Class-Objekt für Felder zu beziehen, scheitern an einer ClassNotFoundException: Class.forName( "String[]" ); Class.forName( "java.lang.String[]" );

In der ersten Anweisung ist der Klassenname nicht voll qualifiziert, und auch in der zweiten Anweisung ist der String falsch aufgebaut. out.println( Class.forName("[Ljava.lang.String;") ); // class [Ljava.lang.String;

Steht die Frage an, ob ein Class-Objekt für ein Feld von Objekten steht oder für ein primitives Feld, lässt sich das Ergebnis von getName() auswerten: public static boolean isObjectArray( Class clazz ) { if ( clazz != null && clazz.isArray() ) return clazz.getName().startsWith( "[L" ); return false; }

So liefert: System.out.println( isObjectArray( Object[].class ) );

// true

System.out.println( isObjectArray( int[].class ) );

// false

System.out.println( isObjectArray( Object.class ) );

// false

1234

18.2

Metadaten der Klassen mit dem Class-Objekt

toString() Auch eine zweite Methode ist uns bekannt, um Class-Exemplare für Menschen lesbar auszugeben: die Methode toString(). Sie basiert im Kern auf getName(), fügt aber zusätzlich die Art der repräsentierten Klasse (normale Klasse, Schnittstelle oder primitiver Datentyp) ein: public String toString() { return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + getName(); } final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ String getName()

Liefert für ein Class-Exemplar als String den voll qualifizierten Namen der repräsentierten Klasse oder Schnittstelle beziehungsweise des repräsentierten Array-Typs oder des primitiven Datentyps. Þ String toString()

Liefert eine für Menschen lesbare String-Repräsentation des Class-Objekts.

18.2.4 instanceof mit Class-Objekten * Der binäre Operator instanceof testet, ob ein Objekt Exemplar einer Klasse oder der Oberklasse ist. Wenn das Ergebnis wahr ist, lässt sich das Objekt unter dem gegebenen Typ ansprechen, ist also zuweisungskompatibel. Der rechte Operator bei instanceof, der Typname, muss jedoch immer zur Übersetzungszeit bekannt sein und kann nicht dynamisch, etwa durch einen String, festgelegt werden. Ist der Typname zur Compilierzeit vielleicht unbekannt, kann das Class-Objekt helfen. Die Methode isInstance(Object) ist sozusagen ein dynamisches instanceof. Gilt mit dem Operator object instanceof ReferenceType

so heißt das mit der Methode: ReferenceType-Class-Objekt.isInstance( object )

Gewöhnungsbedürftig ist sicherlich die Tatsache, dass bei der Methode isInstance() die beiden Operanden umgedreht sind. Dazu ein paar Beispiele:

1235

18

18

Reflection und Annotationen

Listing 18.4: IsAssignableFrom.java, main() Component b = new JLabel(); out.println( b instanceof JLabel );

// true

out.println( JLabel.class.isInstance( b ) );

// true

out.println( Object.class.isInstance( b ) );

// true

out.println( Class.forName("java.awt.Component").isInstance( b ) );

// true

out.println( String.class.isInstance( b ) );

// false

Die Methode isInstance(object) ist natürlich ein wenig dadurch eingeschränkt, dass es immer ein Test-Objekt geben muss. Die Frage etwa, ob das Class-Objekt der Schnittstelle PublicKey eine »Ist-eine-Art-von-Serializable« ist, kann isInstance(object) nicht beantworten, denn dann müsste es vorher ein Objekt geben. Für diesen Fall bietet das Class-Objekt noch eine zweite Methode, isAssignableFrom(Class): Class clazz = Serializable.class; out.println( clazz.isAssignableFrom( String.class ) );

// true

out.println( clazz.isAssignableFrom( Thread.class ) );

// false

out.println( clazz.isAssignableFrom( PublicKey.class ) );

// true

Solange der Typname zur Übersetzungszeit bekannt ist, ist instanceof immer noch die beste Lösung. Doch wenn die Klasse nur durch ein Class-Objekt gegeben ist, bleibt immer noch isAssignableFrom(). Die Methode clazz.isInstance(obj) ist sozusagen eine Kurzform von clazz.isAssignableFrom(obj.getClass()).

18.2.5

Oberklassen finden *

Das Class-Exemplar für eine Klasse gibt Zugriff auf die Oberklasse, die Sichtbarkeitsstufe und weitere Informationen. Die Oberklasse ermittelt getSuperclass(). Die Methode gibt null zurück, falls das Class-Objekt eine Schnittstelle repräsentiert oder wir schon am oberen Ende der Hierarchie sind, also bei dem Class-Objekt für die Wurzelklasse Object. Das folgende Programm findet alle Oberklassen einer Klasse durch den wiederholten Aufruf der Methode getSuperclass(): Listing 18.5: com/tutego/insel/meta/ShowSuperclasses.java Class subclass

= javax.swing.JButton.class;

Class superclass = subclass.getSuperclass(); while ( superclass != null ) { String className = superclass.getName();

1236

18.2

Metadaten der Klassen mit dem Class-Objekt

System.out.println( className ); subclass

= superclass;

superclass = subclass.getSuperclass(); }

Wahrscheinlich wäre eine rekursive Variante noch eleganter, aber darauf kommt es jetzt nicht an. javax.swing.AbstractButton javax.swing.JComponent java.awt.Container java.awt.Component java.lang.Object final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ Class c = java.text.SimpleDateFormat.class; System.out.println( "class " + c.getName() + " {" ); for ( Field publicField : c.getFields() ) { String fieldName = publicField.getName();

1243

18

18

Reflection und Annotationen

String fieldType = publicField.getType().getName(); System.out.printf( "

%s %s;%n", fieldType, fieldName );

} System.out.println( "}" );

Dies ergibt die (gekürzte) Ausgabe: class java.text.SimpleDateFormat { int ERA_FIELD; int YEAR_FIELD; ... int SHORT; int DEFAULT; } final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ Field[] getFields()

Liefert ein Array mit Field-Objekten. Die Einträge sind unsortiert. Das Array hat die Länge 0, wenn die Klasse beziehungsweise Schnittstelle keine öffentlichen Variablen deklariert oder erbt. getFields() liefert automatisch auch Einträge für die aus Oberklassen beziehungsweise Schnittstellen geerbten öffentlichen Variablen. Þ Field getField(String name) throws NoSuchFieldException

Erfragt ein bestimmtes Feld. Die Klasse Field implementiert im Übrigen das Interface Member und ist eine Erweiterung von AccessibleObject. AccessibleObject ist die Basisklasse für Field-, Method- und Constructor-Objekte. Auch Method und Constructor implementieren das Interface Member, das zur Identifikation über getName() oder getModifiers() dient. Zusätzlich liefert getDeclaringClass() das ClassObjekt, das tatsächlich eine Variable oder Methode deklariert. Da geerbte Elemente in der Aufzählung mit auftauchen, ist dies der einzige Weg, um die Position der Deklaration in der Vererbungshierarchie exakt zu bestimmen. Mit dem Field-Objekt können wir vieles erfragen: den Namen des Attributs, den Datentyp und auch wieder die deklarierten Modifizierer. Werfen wir einen Blick auf die toString()Methode der Klasse Field: public String toString() { int mod = getModifiers(); return (((mod == 0) ? "" : (Modifier.toString(mod) + " ")) + getTypeName(getType()) + " "

1244

18.3

Attribute, Methoden und Konstruktoren

+ getTypeName(getDeclaringClass()) + "." + getName()); }

Beispiel Für die Schleife über die Field-Objekte von SimpleDateFormat und einen Aufruf von toString() liefern die Zeilen for ( Field publicField : c.getFields() ) System.out.println( " " + publicFields );

dann: class java.text.SimpleDateFormat { public static final int java.text.DateFormat.ERA_FIELD public static final int java.text.DateFormat.YEAR_FIELD ... public static final int java.text.DateFormat.SHORT public static final int java.text.DateFormat.DEFAULT }

final class java.lang.reflect.Field extends AccessibleObject implements Member

18

Þ Class getDeclaringClass()

Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Variable deklariert wurde. Diese Methode ist Teil der Schnittstelle Member. Þ int getModifiers()

Liefert die deklarierten Modifizierer für die Variable. Þ String getName()

Liefert den Namen der Variable. Diese Methode ist Teil der Schnittstelle Member. Þ Class getType()

Liefert ein Class-Objekt, das dem Datentyp der Variable entspricht. Þ String toString()

Liefert eine String-Repräsentation. Am Anfang stehen die Sichtbarkeitsmodifizierer (public, protected oder private), und es folgen die weiteren Modifizierer (static, final, transient, volatile). Dann kommt der Datentyp, gefolgt vom voll qualifizierten Namen der deklarierenden Klasse, und schließlich der Name der Variable.

1245

18

Reflection und Annotationen

Abbildung 18.2: UML-Diagramm mit den Unterklassen von Member

18.3.2

Methoden einer Klasse erfragen

Um herauszufinden, über welche Methoden eine Klasse verfügt, wenden wir eine ähnliche Vorgehensweise an wie bei den Variablen: getMethods(). Diese Methode liefert ein Array mit Method-Objekten. Über ein Method-Objekt lassen sich Methodenname, Ergebnistyp, Parametertypen, Modifizierer und eventuell resultierende Exceptions erfragen. Wir werden später sehen, dass sich die durch ein Method-Exemplar repräsentierte Methode über invoke() aufrufen lässt.

1246

18.3

Attribute, Methoden und Konstruktoren

Hinweis Auch wenn zwei Klassen die gleiche Methode besitzen, muss doch ein Method-Objekt immer für jede Klasse erfragt werden. Method-Objekte sind immer mit dem Class-Objekt verbunden.

final class java.lang.Class implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ Method[] getMethods()

Gibt ein Array von Method-Objekten zurück, die alle öffentlichen Methoden der Klasse/ Schnittstelle beschreiben. Geerbte Methoden werden mit in die Liste übernommen. Die Elemente sind nicht sortiert, noch gibt es keine Reihenfolge. Die Länge des Arrays ist null, wenn es keine öffentlichen Methoden gibt. Þ Method getMethod(String name, Class... parameterTypes)

throws NoSuchMethodException

Liefert zu einem Methodennamen und einer Parameterliste das passende Method-Objekt oder löst eine NoSuchMethodException aus. Besitzt die Methode keine Parameter – wie eine übliche getXXX()-Methode –, ist das Argument null und wird wegen der Varargs auf Class[] angepasst. Nachdem wir nun mittels getMethods() ein Array von Method-Objekten erhalten haben, lassen die Method-Objekte verschiedene Abfragen zu. So liefert getName() den Namen der Methode, getReturnType() den Ergebnistyp, und getParameterTypes() erzeugt ein Array von Class-Objekten, das die Typen der Methodenparameter widerspiegelt. Wir kennen dies schon von den Attributen. Wir wollen nun ein Programm betrachten, das alle Methoden und ihre Parametertypen sowie Ausnahmen ausgibt: Listing 18.8: com/tutego/insel/meta/ShowMethods.java package com.tutego.insel.meta; import java.lang.reflect.*; class ShowMethods { public static void main( String[] args ) { showMethods( java.awt.Color.BLACK ); }

1247

18

18

Reflection und Annotationen

static void showMethods( Object o ) { for ( Method method : o.getClass().getMethods() ) { String returnString = method.getReturnType().getName(); System.out.print( returnString + " " + method.getName() + "(" ); Class[] parameterTypes = method.getParameterTypes(); for ( int k = 0; k < parameterTypes.length; k++ ) { String parameterString = parameterTypes[k].getName(); System.out.print( " " + parameterString ); if ( k < parameterTypes.length – 1 ) System.out.print( ", " ); } System.out.print( " )" ); Class[] exceptions = method.getExceptionTypes(); if ( exceptions.length > 0 ) { System.out.print( " throws " ); for ( int k = 0; k < exceptions.length; k++ ) { System.out.print( exceptions[k].getName() ); if ( k < exceptions.length – 1 ) System.out.print( ", " ); } } System.out.println(); } } }

Die Ausgabe sieht gekürzt so aus: int hashCode( ) boolean equals( java.lang.Object ) java.lang.String toString( )

1248

18.3

Attribute, Methoden und Konstruktoren

... [F getRGBColorComponents( [F ) ... void wait( long ) throws java.lang.InterruptedException void notify( ) void notifyAll( )

Wir bemerken an einigen Stellen eine kryptische Notation, wie etwa »[F«. Dies ist aber lediglich wieder die schon erwähnte Kodierung für Array-Typen. So gibt getRGB-Components() ein float-Array zurück und erwartet ein float-Array als Argument. final class java.lang.reflect.Method extends AccessibleObject implements GenericDeclaration, Member Þ Class getDeclaringClass()

Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Methode deklariert wurde. Diese Methode ist Teil der Schnittstelle Member. Þ String getName()

Liefert den Namen der Methode. Diese Methode ist Teil der Schnittstelle Member. Þ int getModifiers()

Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member. Þ Class getReturnType()

18

Gibt ein Class-Objekt zurück, das den Ergebnistyp beschreibt. Þ Class[] getParameterTypes()

Liefert ein Array von Class-Objekten, die die Typen der Parameter beschreiben. Die Reihenfolge entspricht der deklarierten Parameterliste. Das Array hat die Länge null, wenn die Methode keine Parameter erwartet. Þ Class[] getExceptionTypes()

Liefert ein Array von Class-Objekten, die mögliche Exceptions beschreiben. Das Array hat die Länge null, wenn die Methode keine solchen Exceptions mittels throws deklariert. Das Feld spiegelt nur die throws-Klausel wider. Sie kann prinzipiell auch zu viele Exceptions enthalten, bei einer Methode foo() throws RuntimeException, NullPointerException etwa genau die beiden Ausnahmen. Þ String toString()

Liefert eine String-Repräsentation der Methode, ähnlich dem Methodenkopf in einer Deklaration.

1249

18

Reflection und Annotationen

18.3.3

Properties einer Bean erfragen

Eine Bean besitzt Properties (Eigenschaften), die in Java (bisher) durch Setter und Getter ausgedrückt werden, also Methoden, die einer festen Namenskonvention folgen. Gibt es Interesse an den Properties, lässt sich natürlich getMethods() auf dem Class-Objekt aufrufen und nach den Methoden filtern, die der Namenskonvention entsprechen. Die Java-Bibliothek bietet aber im Paket java.beans eine einfachere Lösung für Beans: einen PropertyDescriptor.

Beispiel Gib alle Properties von Color aus (es gibt nur lesbare): Listing 18.9: com/tutego/insel/meta/PropertyDescriptors.java, main() BeanInfo beanInfo = Introspector.getBeanInfo( Color.class ); for ( PropertyDescriptor pd : beanInfo.getPropertyDescriptors() ) System.out.println( pd.getDisplayName() + " : " + pd.getPropertyType().getName() );

Die Ausgabe: RGB : int alpha : int blue : int class : java.lang.Class colorSpace : java.awt.color.ColorSpace green : int red : int transparency : int

Interessanter sind vom PropertyDescriptor die Methoden getReadMethod() und getWriteMethod(), die beide ein Method-Objekt liefern – sofern es verfügbar ist –, um so die Methode gleich aufrufen zu können. BeanInfo liefert mit getPropertyDescriptors() zwar die Properties, kann jedoch über getMethodDescriptors() auch alle anderen Methoden liefern.

18.3.4

Konstruktoren einer Klasse

Konstruktoren und Methoden haben einige Gemeinsamkeiten, unterscheiden sich aber insofern, als Konstruktoren keinen Rückgabewert haben. Die Ähnlichkeit zeigt sich auch in der Methode getConstructors(), die ein Array von Constructor-Objekten zurückgibt. Über dieses Array lassen sich dann wieder Name, Modifizierer, Parameter und Exceptions der Konstrukto-

1250

18.3

Attribute, Methoden und Konstruktoren

ren einer Klasse erfragen. Wie wir in Abschnitt 18.4.1, »Objekte erzeugen«, sehen werden, lassen sich auch über die Methode newInstance() neue Objekte erzeugen. Wegen der weitgehenden Ähnlichkeit der Klassen Constructor und Method sind die folgenden Methoden hier nicht näher beschrieben.

Beispiel Zeige alle Konstruktoren der Color-Klasse: Listing 18.10: com/tutego/insel/meta/ShowConstructors.java, main() for ( Constructor c : java.awt.Color.class.getConstructors() ) System.out.println( c );

Die Klasse Constructor implementiert eine auskunftsfreudige toString()-Methode. Die String-Repräsentation zeigt die Signatur mit Sichtbarkeit. Nach dem Aufruf erhalten wir: public java.awt.Color(float,float,float,float) public java.awt.Color(int) public java.awt.Color(int,int,int) public java.awt.Color(int,int,int,int) public java.awt.Color(java.awt.color.ColorSpace,float[],float) public java.awt.Color(int,boolean) public java.awt.Color(float,float,float)

final class java.lang.Class

18

implements Serializable, GenericDeclaration, Type, AnnotatedElement Þ Constructor[] getConstructors()

Liefert ein Feld mit Constructor-Objekten. Þ Constructor getConstructor(Class... parameterTypes)

throws NoSuchMethodException

Liefert ein ausgewähltes Constructor-Objekt. final class java.lang.reflect.Constructor extends AccessibleObject implements GenericDeclaration, Member Þ Class getDeclaringClass()

Eine ziemlich langweilige Methode, da Konstruktoren nicht vererbt werden. Sie gibt immer nur jene Klasse aus, von der das Class-Objekt kommt. Das ist ein wichtiger Unterschied zwischen Methoden und Konstruktoren, der bei dieser Methode deutlich auffällt.

1251

18

Reflection und Annotationen

Þ Class[] getExceptionTypes() Þ int getModifiers() Þ String getName() Þ Class[] getParameterTypes()

Abbildung 18.3: UML-Diagramm mit den Unterklassen von Member

1252

18.4

18.3.5

Objekte erzeugen und manipulieren

Annotationen

Annotationen erfragen Methoden der Schnittstelle AnnotatedElement, die unter anderem Class, Constructor, Field, Method und Package implementieren. Ein Blick in AnnotatedElement verrät, wie an die Annotationen heranzukommen ist: interface java.lang.reflect.AnnotatedElement Þ Annotation[] getAnnotations()

Liefert alle mit diesem Element assoziierten Annotationen. Þ Annotation[] getDeclaredAnnotations()

Liefert alle an diesem Element deklarierten Annotationen. Vererbte Annotationen werden ignoriert. Þ boolean isAnnotationPresent(Class clazz, Collection list ) { Field[] fields = clazz.getDeclaredFields(); AccessibleObject.setAccessible( fields, true ); for ( Field f : fields ) { try { list.add( f.getName() + "=" + f.get( o ) ); } catch ( IllegalAccessException e ) { e.printStackTrace(); } }

1258

18.4

Objekte erzeugen und manipulieren

if ( clazz.getSuperclass().getSuperclass() != null ) toString( o, clazz.getSuperclass(), list ); } }

Die private Methode toString(Object, Class, ArrayList) dient eigentlich nur dem rekursiven Aufruf durch die Oberklassen. Falls es eine Oberklasse gibt, also clazz.getSuperclass().getSuperclass() ein Objekt liefert, müssen wir für die Oberklasse ebenfalls die Attribute ablaufen. Das machen wir rekursiv. Testen wir anschließend ToStringHelper in einer Klasse ToStringHelperDemo, die von Ober abgeleitet ist. Damit bekommen wir zwei Attribute in der Oberklasse. Eines davon ist interessant (die Variable i), denn die Unterklasse überdeckt sie. Dennoch findet toString() beide Belegungen. Wäre das nicht erwünscht, müssten wir einfach die Liste durchschauen und suchen, ob schon ein Attribut mit dem gleichen Namen vorhanden ist. Da der Algorithmus rekursiv erst die Unterklasse und dann die Oberklasse(n) durchsucht, bekommen wir auch die Attribute in dem sichtbaren Bereich, wie sie auch der Benutzer sieht: Listing 18.14: com/tutego/insel/meta/ToStringHelperDemo.java package com.tutego.insel.meta; class Ober { int i = 123;

18

/* private */double d = 3.1415; } public class ToStringHelperDemo extends Ober { String hello = "world"; int

i

= 42;

public static void main( String[] args ) { ToStringHelperDemo t = new ToStringHelperDemo(); System.out.println( ToStringHelper.toString( t ) ); // ToStringHelperTest[hello=world, i=42, i=123, d=3.1415] } }

1259

18

Reflection und Annotationen

18.4.4 Variablen setzen Bei Debuggern oder grafischen Editoren ist es nur eine Seite der Medaille, die Werte von Variablen anzuzeigen. Hinzu kommt noch das Setzen der Werte von Variablen. Dies ist aber genauso einfach wie das Abfragen. Anstelle der getXXX()-Methoden kommen nun verschiedene setXXX()-Methoden zum Einsatz. So trägt setBoolean() einen Wahrheitswert oder setDouble() eine Fließkommazahl in eine Variable ein. Eine allgemeine set()-Methode dient Objektreferenzen wie im Fall von get(). Die Methode set() nimmt ebenso Wrapper-Objekte für Variablen von primitiven Datentypen. Die folgenden set()-Methoden setzen daher alle »ihren« Datentyp. Wir müssen aber dafür sorgen, dass die Variable existiert und wir Zugriff darauf haben. In allen Fällen muss auf IllegalArgumentException und IllegalAccessException geachtet werden. Das folgende Programm erzeugt klassisch ein Point-Objekt mit dem Konstruktor, der x und y setzt. Anschließend verändert die eigene Methode modify() ein gewünschtes Attribut: Listing 18.15: com/tutego/insel/meta/SetFieldElements.java package com.tutego.insel.meta; import java.lang.reflect.*; import java.awt.*; class SetFieldElements { public static void main( String[] args ) { Point p = new Point( 11, 22 ); System.out.println( p ); modify( p, "x", 1111 );

// java.awt.Point[x=11,y=22]

modify( p, "y", 2222 );

// java.awt.Point[x=1111,y=2222]

System.out.println( p ); modify( p, "z", 0 );

// java.lang.NoSuchFieldException: z

} static void modify( Object o, String name, Integer param ) {

1260

18.4

Objekte erzeugen und manipulieren

try { Field field = o.getClass().getField( name ); field.set( o, param ); } catch ( NoSuchFieldException e ) { e.printStackTrace(); } catch ( IllegalAccessException e ) { e.printStackTrace(); } } }

Die Veränderung der Variablen erfolgt mit der set()-Methode. Da wir primitive Datentypen übergeben, wickeln wir sie für die modify()-Methode in ein Integer-Objekt ein. Das IntegerObjekt haben wir nicht explizit programmiert, denn es wird über Boxing vom Compiler selbst zur Verfügung gestellt. Für bekannte Typen könnten wir neben der allgemeinen Methode set() auch etwa setInt() verwenden. final class java.lang.reflect.Field extends AccessibleObject implements Member

18

Þ void set(Object obj, Object value)

Setzt das Attribut des Objekts obj, das dieses Field-Objekt repräsentiert, auf den neuen Wert value. Þ void setBoolean(Object obj, boolean z) Þ void setByte(Object obj, byte b) Þ void setChar(Object obj, char c) Þ void setDouble(Object obj, double d) Þ void setFloat(Object obj, float f) Þ void setInt(Object obj, int i) Þ void setLong(Object obj, long l) Þ void setShort(Object obj, short s)

Belegt das Feld eines Objekts obj mit einem primitiven Element.

1261

18

Reflection und Annotationen

18.4.5

Bean-Zustände kopieren *

In mehrschichtigen Anwendungen gibt es oft das Muster, dass eine JavaBean etwa über eine objekt-relationale Mapping-Technologie automatisch aus einer Datenbankzeile aufgebaut wird und dann intern in der Geschäftsschicht verwendet wird. Soll nun diese Information über das Netzwerk an einen anderen Rechner verteilt werden, ist es nicht immer angebracht, diese JavaBean etwa direkt über Serialisierung zu versenden. Stattdessen kann ein TransferObjekt aufgebaut werden, eine spezielle JavaBean zum Beispiel, sodass der Empfänger keine Abhängigkeit zu der Bean in der internen Geschäftsschicht hat. Nun werden sich aber diese Geschäftsschicht-Bean und die Transfer-Bean sehr ähnlich sein, und viele Entwickler scheuen die Mühe, lästigen Kopiercode zu erstellen. Doch manuelle Arbeit ist nicht nötig, und eine Lösung für das Kopierproblem ist über Refection schnell geschrieben. Über die BeanInfo kommen wir an den PropertyDescriptor (siehe dazu Abschnitt 18.3.3, »Properties einer Bean erfragen«), und dann liefern getReadMethod() und getWriteMethod() die Setter/Getter. Bei einer eigenen Kopiermethode wie copyProperties(Object source, Object target) müssen wir bei der Quell-Bean jede Property auslesen und entsprechend bei der Ziel-Bean nach der Property suchen und den Setter aufrufen. Wenn das Ganze ohne Typkonvertierungen programmiert werden soll, sind es nur wenige Zeilen Programmcode. Kommen einfache Konvertierungen dazu, etwa wenn einmal ein Wrapper als Property-Typ genutzt wird und einmal der primitive Datentyp, ist es etwas mehr. Der Aufwand mit einer eigenen Implementierung ist allerdings nicht nötig, denn zwei populäre Implementierungen können helfen: Þ Apache Commons BeanUtils (http://commons.apache.org/beanutils/):

Die Klasse org.apache.commons.beanutils.BeanUtils bietet praktische statische Methoden wie copyProperty(Object bean, String name, Object value), copyProperties(Object dest, Object orig), Object cloneBean(Object bean) oder populate(Object bean, Map properties). Þ Dozer (http://dozer.sourceforge.net/): Dozer bringt ausgefeilte Mapping-Möglichkeiten

mit, die weit über BeansUtils hinausgehen. Das geht so weit, dass es ein Eclipse-Plugin zur Konfiguration der Abbildungen gibt.

18.4.6 Private Attribute ändern Wenn es der Sicherheitsmanager zulässt, kann ein Programm auch private- oder protectedAttribute ändern und Methoden/Konstruktoren eingeschränkter Sichtbarkeit aufrufen. Die Schlüsselfigur in diesem Spiel ist die Oberklasse java.lang.reflect.AccessibleObject, die den Klassen Constructor, Field und Method die Methode setAccessible(boolean) vererbt. Ist das Argument true und lässt der Sicherheitsmanager die Operation zu, lässt sich auf jedes Element (also Konstruktor, Attribut oder Methode) zugreifen:

1262

18.4

Objekte erzeugen und manipulieren

Listing 18.16: com/tutego/insel/meta/ReadPrivate.java package com.tutego.insel.meta; public class ReadPrivate { @SuppressWarnings( "all" ) private String privateKey = "Schnuppelhase"; public static void main( String[] args ) throws Exception { ReadPrivate key = new ReadPrivate(); Class c = key.getClass(); java.lang.reflect.Field field = c.getDeclaredField( "privateKey" ); field.setAccessible( true ); System.out.println( field.get(key) ); // Schnuppelhase field.set( key, "Schnuckibutzihasidrachelchen"); System.out.println( field.get(key) ); // Schnuckibutzihasidrachelchen } }

Warnung Mit dieser Technik lässt sich viel Unsinn anrichten. Es gibt Dinge, die in der Laufzeitumgebung einfach fest sein müssen. Dazu zählen einmal angelegte Strings oder Wrapper-Objekte. Strings sind immutable, weil sie intern in einem privaten char-Feld gehalten werden und es keine Modifikationsmöglichkeiten gibt. Auch Wrapper-Objekte sind, wenn sie einmal mit einem Konstruktor angelegt wurden, nicht über öffentliche Methoden veränderbar. Sie anschließend per Reflection zu modifzieren bringt große Unordnung, insbesondere bei den gecachten Integer/Long-Wrapper-Objekten, die die statischen valueOf()-Methoden liefern.

18.4.7

Methoden aufrufen

Nach dem Abfragen und Setzen von Variablenwerten und Konstruktor-Aufrufen zum Erzeugen eines Objekts ist das Aufrufen von Methoden per Reflection der letzte Schritt. Wenn zur Compilezeit der Name der Methode nicht feststeht, lässt sich zur Laufzeit dennoch eine im Programm deklarierte Methode aufrufen, wenn ihr Name als Zeichenkette vorliegt.

1263

18

18

Reflection und Annotationen

Zunächst gehen wir wieder von einem Class-Objekt aus, das die Klasse des Objekts beschreibt, für das eine Objektmethode aufgerufen werden soll. Anschließend wird ein Method-Objekt als Beschreibung der gewünschten Methode benötigt; wir bekommen dies mit der Methode getMethod() aus dem Class-Exemplar. getMethod() verlangt zwei Argumente: einen String mit dem Namen der Methode und ein Array von Class-Objekten. Jedes Element dieses Arrays entspricht einem Parametertyp aus der Signatur der Methode. Damit werden überladene Methoden unterschieden. Nachdem wir das beschreibende Method-Exemplar und die Parameterwerte für den Aufruf vorbereitet haben, ruft invoke() die Zielmethode auf – im Englischen heißt dies dynamic invocation. invoke() erwartet zwei Argumente: ein Array mit Argumenten, die der aufgerufenen Methode übergeben werden, und eine Referenz auf das Objekt, auf dem die Methode aufgerufen werden soll und zur Auflösung der dynamischen Bindung dient. final class java.lang.reflect.Method extends AccessibleObject implements GenericDeclaration, Member Þ Object invoke(Object obj, Object... args)

throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

Ruft eine Methode des Objekts obj mit den gegebenen Argumenten auf. Wie schon beim Konstruktor löst die Methode eine InvocationTargetException aus, wenn die aufzurufende Methode eine Exception auslöst.

Beispiel Wir erzeugen ein Point-Objekt und setzen im Konstruktor den x-Wert auf 10. Anschließend fragen wir mit der Methode getX(), die wir dynamisch aufrufen, den x-Wert wieder ab: Listing 18.17: com/tutego/insel/meta/InvokeMethod.java, main() Point p = new Point( 10, 0 ); Method method = p.getClass().getMethod( "getX" ); String returnType = method.getReturnType().getName(); Object returnValue = method.invoke( p ); System.out.printf( "(%s) %s", returnType, returnValue ); // (double) 10.0

Beispiele der Varargs sind bei getMethod() die Parametertypen und bei invoke() die Argumente für setLocation(). Da getMethod() eine beliebige Anzahl von Argumenten annehmen kann und kein Argument dazuzählt, muss die Methode nicht so parametrisiert werden: Method method = p.getClass().getMethod( "getX", (Class[]) null );

1264

18.4

Objekte erzeugen und manipulieren

Auffälliger ist die Möglichkeit der variablen Argumentanzahl bei invoke(). Da ein Getter keine Parameter besitzt, heißt es kurz method.invoke(p); statt wie vor Java 5: method.invoke( p, (Object[]) null );

Interessant sind Methoden mit Parameterliste, wie setLocation(): Point p = new Point(); Method method = p.getClass().getMethod( "setLocation", int.class, int.class ); method.invoke( p, 1, 2 ); System.out.println( p );

18.4.8 Statische Methoden aufrufen Wir wollen ein Beispiel programmieren, in dem die Klasse InvokeMain die statische main()-Methode einer anderen Klasse, HasMain, mit einem Parameter aufruft: Listing 18.18: com/tutego/insel/meta/InvokeMain.java package com.tutego.insel.meta; import java.lang.reflect.*; import java.util.Arrays; public class InvokeMain

18

{ public static void main( String[] args ) throws Exception { String[] argv = { "-option", "Parameter" }; Method method = Class.forName( "com.tutego.insel.meta.HasMain" ). getMethod( "main", argv.getClass() ); method.invoke( null, new Object[]{ argv } ); } } class HasMain { public static void main( String[] args )

1265

18

Reflection und Annotationen

{ System.out.println( "Got: " + Arrays.toString( args ) ); } }

18.4.9 Dynamische Methodenaufrufe bei festen Methoden beschleunigen * Werden über Reflection Methoden aufgerufen, deren Methodennamen erst zur Laufzeit bestimmt werden, so verlieren wir die Typsicherheit vom Compiler, und die Geschwindigkeit ist nicht optimal – auch wenn es seit Java 5 nicht mehr so schlimm ist (doch immer noch zeigt eine einfache Zeitmessung einen deutlichen Unterschied). Diese Aufrufe lassen sich prinzipbedingt auch durch einen JIT-Compiler nicht weiter beschleunigen. Wir müssen also nach einer Lösung suchen, mit der wir diese Art von Aufruf beschleunigen können. Ein möglicher Weg hierbei ist, in Kenntnis des Namens der Methode – nennen wir die Methode meth() – den Namen in einer (abstrakten) Oberklasse dem Compiler bereits bekannt zu machen. Reflection ist nur zum Laden einer Unterklasse mit gegebenem Namen nötig; die normale, dynamische Methodenbindung erledigt den Rest – ganz ohne versteckte Schnüre, doppelte Böden oder Spiegel (Reflection). Versuchen wir, den folgenden Code nach diesem Schema zu ändern: Listing 18.19: com/tutego/insel/meta/DynamReflection.java, main() Class clazz = Class.forName("com.tutego.insel.meta.DynamReflectionMethod" ); Object o = clazz.newInstance(); clazz.getMethod( "meth" ).invoke( o ); DynamReflection ist die Hauptklasse, die die Klasse DynamReflectionMethod über Class.forName()

anfordert: Listing 18.20: com/tutego/insel/meta/DynamReflectionMethod.java package com.tutego.insel.meta; public class DynamReflectionMethod { @Override public void meth() { System.out.println( "Bewusste Raucher trinken Filterkaffee" ); } }

1266

18.4

Objekte erzeugen und manipulieren

Über das Class-Objekt erzeugt newInstance() ein neues Exemplar. getMethod() sucht die Beschreibung der Methode meth() heraus, und invoke() ruft die Methode meth() auf. Hier genau entsteht ein kleiner Geschwindigkeitsverlust. Wenn es uns gelänge, um das invoke() herumzukommen, wäre das schon ein großer Fortschritt. Dies schaffen wir, indem wir eine Schnittstelle (oder Oberklasse) für DynamReflectionMethod konstruieren, die genau diese Methode vorschreibt. Die implementierende Klasse (beziehungsweise Unterklasse) wird dann eine Implementierung angeben: Listing 18.21: com/tutego/insel/meta/DynamAbstract.java package com.tutego.insel.meta; interface DynamBase { void meth(); } class DynamBaseMethod implements DynamBase { public void meth() { System.out.println( "Bewusste Raucher trinken Filterkaffee" ); } }

18

public class DynamAbstract { public static void main( String[] args ) throws Exception { Class clazz = Class.forName( "com.tutego.insel.meta.DynamBaseMethod" ); DynamBase o = (DynamBase) clazz.newInstance(); o.meth(); } } DynamBase ist eine Schnittstelle, die zur Übersetzungszeit bekannt ist. Die virtuelle Maschine

löst den Aufruf nach den üblichen Regeln der dynamischen Bindung selbst auf. Die Klasse DynamBaseMethod wird ebenfalls erst zur Laufzeit geladen. Wir verstecken hier sehr elegant den Aufwand. Wir haben die gleiche Funktionalität und Flexibilität wie im vorangegangenen Reflection-Beispiel – wenn wir die Möglichkeit, den Klassennamen durch einen String anzu-

1267

18

Reflection und Annotationen

geben, außer Acht lassen –, aber mit der höheren Geschwindigkeit eines konventionellen Methodenaufrufs ohne Reflection.

Hinweis Der Aufruf von Class.forName() liefert ein Class-Objekt, bei dem Typ nicht bekannt ist. Daher ist unsere Variable clazz mit Class deklariert. Bei newInstance() ist dann auch eine explizite Typanpassung auf den Ergebnistyp nötig, was nicht nötig wäre, wenn das Class-Objekt korrekter wäre. Um das Class-Objekt von Class.forName() zu spezialisieren, gibt es eine Methode asSubclass(). Dann lässt sich statt dem bekannten Class clazz = Class.forName( "DynamBaseMethod" ); DynamBase o = (DynamBase) clazz.newInstance();

Folgendes schreiben: Class findClass( String name ) throws ClassNotFoundException { MemJavaFileObject fileObject = classFiles.get( name ); if ( fileObject != null ) { byte[] bytes = fileObject.getClassBytes();

1305

19

Dynamische Übersetzung und Skriptsprachen

return defineClass( name, bytes, 0, bytes.length ); } return super.findClass( name ); } }

19.3

Ausführen von Skripten

Seit Java 6 bietet die Standardbibliothek über das Paket javax.script eine API, um Skriptsprachen wie JavaScript, Groovy oder Jython anzusprechen. Diese API wurde im JSR-223, »Scripting for the Java Platform«, definiert, und das Standard-JDK und JRE bringt mit Rhino (http://www.mozilla.org/rhino/ScriptingJava.html) einen Interpreter für JavaScript mit. (Bei anderen Java SE-Implementierungen muss das nicht zwingend gegeben sein.) Das in Java 6 neu ins bin-Verzeichnis aufgenommene Kommandozeilenprogramm jrunscript ermöglicht das Setzen von Variablen, das Ausführen von Skript-Code und das Auswerten von Ergebnissen. Eine Skript-Sprache kann auf den vollen Umfang der Java-API zurückgreifen, definiert aber üblicherweise auch eigene Bibliotheken.

Beispiel Führe ein simples Skript über das Kommandozeilenprogramm aus: $ jrunscript -e "print('Hallo JavaScript')"

Eine Skript-Engine führt die Skripte aus, indem sie das Parsen, Interpretieren und Verwalten der Objekte übernimmt. Rhino übernimmt JavaScript, doch die Webseite https://scripting.dev.java.net/ führt mehr als 10 weitere Skriptsprachen auf, die sich über die neue API integrieren lassen.

19.3.1

JavaScript-Programme ausführen

Bevor wir uns mit der Java-Seite beschäftigen, wollen wir ein kleines JavaScript-Programm schreiben. Es liest aus einer Variablen im Kontext, gibt etwas auf der Konsole aus und deklariert und initialisiert eine neue Variable.

1306

19.3

Ausführen von Skripten

Listing 19.12: com/tutego/insel/script/tutego.js f = new javax.swing.JFrame() f.setSize( 500, 100 ) f.defaultCloseOperation = javax.swing.JFrame.EXIT_ON_CLOSE f.title = "Hallo " + name + "." f.visible = true today = new Date() println( today ) month = today.getMonth() + 1

Damit ein Java-Programm das Skript laden und ausführen kann, sind nur zwei Klassen nötig. Der ScriptEngineManager verschafft uns Zugang zu einer gewünschten Skript-Engine. Erfragt wird eine Skript-Engine über einen Namen (wie »JavaScript«) oder über einer Dateiendung, die die Skript-Dateien üblicherweise haben (wie »js«). Listing 19.13: com/tutego/insel/script/JavaScriptDemo.java, Teil 1 package com.tutego.insel.script; import java.io.InputStreamReader; import javax.script.*; public class ScriptDemo { public static void main( String[] args ) throws ScriptException

19

{ ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");

Die Methode getEngineByName() liefert ein ScriptEngine-Objekt, dessen eval()-Methoden die Interpretation starten. Listing 19.14: com/tutego/insel/script/JavaScriptDemo.java, Teil 2 engine.put( "name", "Christian Ullenboom" ); engine.eval( new InputStreamReader( ScriptDemo.class.getResourceAsStream( "tutego.js" ) ) ); System.out.println( engine.get( "month" ) ); } }

1307

19

Dynamische Übersetzung und Skriptsprachen

Ausgeführt stellt das Programm ein Swing-Fenster dar und gibt Folgendes auf der Konsole aus: Hallo Christian Ullenboom. Sun Jul 08 2007 15:25:12 GMT+0200 (CEST) 7.0

19.3.2

Groovy

Groovy (http://groovy.codehaus.org/) ist eine objektorientierte Skriptsprache, die auf einer JVM läuft und vollen Zugriff auf alle Java-Bibliotheken bietet. Die Sprache hat viele interessante Eigenschaften, die sie zu Java 2.0 machen – wenn wir die jetzige Version Java 1.7 nennen wollen. Groovy setzt auf der Standard-Java-Syntax auf und erweitert diese.1 Einige Höhepunkte: Groovy-Feature

Beispiel

Statt System.out.print()/

println 1 + 2

println() reicht ein print bzw.

print "Wow"

println, und dann muss das Argu-

ment auch nicht in runden Klammern stehen. Anweisungen müssen nicht immer

print "Wow"

mit einem Semikolon abgeschlos-

print "Wow"; print "Wow"

sen werden, sondern nur dann, wenn mehrere Anweisungen in einer Zeile stehen. Variablen müssen nicht mit einem

def age = 40

Typ deklariert werden, das gilt auch

def greet( name ) { print "Hallo " + name }

bei catch oder Methoden/Kon-

try { } catch ( e ) { }

struktoren. Zeichenketten lassen sich mit ein-

print "Ja" == 'Ja'

fachen oder doppelten Anfüh-

def sign = "-" as char

// true

rungszeichen angeben. Einzelne Zeichen (char/Character) müssen besonders aufgebaut werden. Tabelle 19.3: Eigenschaften von Groovy mit Beispiel

1

Bis auf sehr kleine Ausnahmen ist jedes Java-Programm ein Groovy-Programm.

1308

19.3

Groovy-Feature

Beispiel

Strings in doppelten Anführungs-

def one = 1

zeichen sind besondere GStrings:

print "$one plus $one macht ${1+1}"

Enthalten sie $variable bzw.

// 1 plus 1 macht 2

Ausführen von Skripten

${ausdruck}, so wird der Inhalt der

Variablen bzw. der ausgewertete Ausdruck eingesetzt. Strings können über mehrere Zei-

print """

len laufen, wenn sie in """ oder '''

Zeile 1

eingeschlossen sind – folgt ein

Zeile 2\

Backslash am Ende der Zeile, wird

Weiter mit Zeile 2

kein Zeilenumbruchzeichen einge-

"""

fügt. Alles ungleich 0 und null ist true.

if ( 1 ) print 'Zweig wird genommen'

Groovy definiert einen neuen Da-

def numbes = 0..9

tentyp für Bereiche (engl. ranges).

def number = 0... Falls jetzt ein Setup Alert kommt, kann dieser mit OK geschlossen werden. 7. Wähle aus dem Zweig Devel den Eintrag gcc-core (bzw. gcc-g++). (Den gdb können wir angeben, wenn C-Programme debuggt werden sollen.) Dann Weiter >. Die Wahl selektiert automatisch ein paar weitere abgeleitete Pakete und zeigt sie an. Dann Weiter >. Der tatsächliche Download beginnt. 8. Es lassen sich Icons setzen. Sie sind nicht nötig und können abgewählt werden. Weiter > schließt die Installation ab.

Übersetzen mit Ant Das Build-Tool Ant bringt in der Standard-Distribution keinen Task mit, der einen C-Compiler anstößt. Nichtsdestotrotz lassen sich mit externe Programme aufrufen. Somit sieht das ganze Build-Skript folgendermaßen aus: Listing 21.6: build.xml





1344

21.4

Nativ die Stringlänge ermitteln











Hinweis Die dynamische Bibliothek muss unter Windows die Endung .dll und unter Unix-Systemen die Endung .so haben. In der Unix-Welt beginnen die dynamischen Bibliotheken mit dem Präfix lib, sodass sich daraus für eine Datei die Namensgebung libName.so ergibt. Wer den -Task nicht verwenden mag, der kann auch die externen CC-Tasks unter http://ant-contrib.sourceforge.net/ nutzen.

21

21.4

Nativ die Stringlänge ermitteln

Wir haben unsere Funktion noch nicht zu Ende geführt. Es fehlt die Berechnung der Zeichenkettenlänge, für die wir den String zuerst von der Unicode-Implementierung in ein C-Zeichenfeld überführen müssen. Dazu dient eine JNI-Funktion GetStringUTFChars(), die wir über die Umgebung env nutzen können. const char* str = (*env)->GetStringUTFChars( env, s, NULL );

1345

21

Java Native Interface (JNI)

1

Hinweis Die Schreibweisen unterscheiden sich im Fall von C oder C++. Während es bei C const char* str = (*env)->GetStringUTFChars( env, s, NULL );

heißt, schreiben wir bei C++: const char* str = env->GetStringUTFChars( s, NULL );

In der objektorientierten C++-Variante ist env als Argument nicht mehr nötig. Wir bleiben bei unseren Buchbeispielen in der prozeduralen C-Welt und übergeben daher allen JNI-Funktionen das JNIEnv.1 Die Zeichenkettenlänge liefert die C-Funktion strlen(), die im Header string.h definiert ist: Listing 21.7: strlen.c #include #include "strlen.h" #include #include JNIEXPORT jint JNICALL Java_com_tutego_jni_StrLen_strlen( JNIEnv *env, jclass clazz, jstring s ) { if ( s == NULL ) { jclass exc = (*env)->FindClass( env, "java/lang/NullPointerException" ); if ( exc != NULL ) (*env)->ThrowNew( env, exc, "(in C code)" ); return –1; } const char* str = (*env)->GetStringUTFChars( env, s, NULL ); if ( str == NULL )

1

Siehe dazu unter http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html den Punkt »Native Method Arguments«.

1346

21.5

Erweiterte JNI-Eigenschaften

return –1; int len = strlen( str ); (*env)->ReleaseStringUTFChars( env, s, str ); return (jint) len; }

Mit JNI lassen sich auf der C-Seite Java-Objekte erzeugen und zerstören, genauso wie sich JavaMethoden aufrufen und Werte austauschen lassen. Wir nutzen das, um ein Ausnahme-Objekt zu erzeugen, wenn es keine Referenz auf ein String-Objekt gibt.

21.5

Erweiterte JNI-Eigenschaften

Im letzten Beispiel haben wir auf der Java-Seite wenig unternommen beziehungsweise lediglich eine C-Funktion aufgerufen und ein Ergebnis zurückgegeben. Nun wollen wir Objekteigenschaften auslesen, Methoden aufrufen und Objekte erzeugen. JNI bietet noch mehr als nur die Übergabe von primitiven Datentypen und Strings.

21.5.1

Klassendefinitionen

JNI repräsentiert ein Java-Objekt durch jobject. Um auf Attribute eines Java-Objekts zuzugreifen, müssen wir zunächst die Klassendefinition erfragen. Wir kennen das bereits aus Kapitel 18, »Reflection und Annotationen«:

21 Class clazz = o.getClass();

Ähnlich funktioniert das in JNI. Dort benutzen wir die JNI-Funktion GetObjectClass(): jclass jclass;

// Zuweisung mit Initialisierung in klassischem C nicht möglich

jclass = (*env)->GetObjectClass( env, obj ); obj repräsentiert das Objekt, für das wir die Klassendefinition besorgen.

Und wie auch Reflection nicht nur mit getClass() das Class-Objekt liefert, sondern auch eine Suche nach dem Klassenamen mit Class.forName() bietet, so ermöglicht JNI Ähnliches mit FindClass(). jclass = (*env)->FindClass( env, "Klassenname" );

1347

21

Java Native Interface (JNI)

Beispiel Ein Exemplar der Klasse C wird einer nativen Funktion übergeben. Die Deklaration der nativen Methode in Java ist folgende: native void foo( C c );

Die Übersetzung liefert uns in etwa: JNIEXPORT jobject JNICALL foo( JNIEnv *env, jobject in_c )

Es holt GetObjectClass() die Klassendefinition, die anschließend in jclass steht: jclass jclass = (*env)->GetObjectClass( env, in_c );

21.5.2

Zugriff auf Attribute

Um unter Reflection auf die Attribute zuzugreifen, muss das Class-Objekt ein Field-Objekt akquirieren: Field field = clazz.getField( Feldname );

Ähnlich funktioniert auch dieses wieder in JNI. Mit der JNI-Funktion GetFieldID() erhalten wir einen Zeiger auf den Speicherplatz eines Felds.

Beispiel Jetzt müssen wir nur über unsere Klasse C weitere Aussagen machen. Geben wir Folgendes vor: class C { int i; }

Um die Attribut-ID zu erlangen, schreiben wir: jfieldID jfid; jfid = (*env)->GetFieldID( env, jclass, "i", "I");

Den zweiten Parameter entlarven wir als Zeiger auf die Klassendefinition. Das dritte Argument kennzeichnet den Namen der Variablen (i in der Klasse C), und das letzte Argument bestimmt den Typ der Variablen. Das große I kennzeichnet einen Integer, und die anderen Typen haben wir schon einmal beleuchtet. Zur Wiederholung:

1348

21.5

Signatur

Typ

Z

boolean

B

Byte

C

Char

S

Short

I

Int

J

Long

F

Float

D

Double

V

Void

LvollQualifizierterName;

Objekttyp

[Typ

Feld mit Typ

Erweiterte JNI-Eigenschaften

Tabelle 21.1: Kodierung von Typen

Der letzte Schritt ist das Auslesen beziehungsweise Setzen der Werte. Wiederum soll uns Reflection eine Orientierung geben: Class clazz = o.getClass(); Field field = clazz.getField( Feldname ); field.setAttribute( o, new Integer(9) );

Beispiel Die JNI-Funktion GetIntField() liest das Attribut des Objekts aus: jfieldID jfid;

21

jclass jclass; jint val; jclass = (*env)->GetObjectClass( env, in_c ); jfid

= (*env)->GetFieldID( env, jclass, "i", "I");

val

= (*env)->GetIntField( env, in_c, jfid );

Für die unterschiedlichen Typen stehen ebenfalls ganz unterschiedliche GetXXXField()-Funktionen zur Verfügung. Die Tabelle fasst sie zusammen:

1349

21

Java Native Interface (JNI)

GetField

Nativer Typ

Java-Typ

GetObjectField()

jobject

Object

GetBooleanField()

jboolean

boolean

GetByteField()

Jbyte

byte

GetCharField()

Jchar

char

GetShortField()

Jshort

short

GetIntField()

Jint

int

GetLongField()

Jlong

long

GetFloatField()

Jfloat

float

GetDoubleField()

jdouble

double

Tabelle 21.2: Zugriff auf Attribute von Java-Objekten

Die entsprechenden SetXXXField()-Funktionen lassen sich leicht ableiten. Die letzte Frage ist die nach den Datentypen. Die anschließende Tabelle zeigt, welcher Java-Typ welchem nativen Typ zugeordnet ist und wie die Wertebereiche sind: Java-Typ

Nativer Typ

Beschreibung

boolean

jboolean

8 Bit ohne Vorzeichen

byte

jbyte

8 Bit mit Vorzeichen

char

jchar

16 Bit ohne Vorzeichen

short

jshort

16 Bit mit Vorzeichen

int

iint

32 Bit mit Vorzeichen

long

jlong

64 Bit mit Vorzeichen

float

jfloat

32 Bit

double

jdouble

64 Bit

void

void

Tabelle 21.3: Mapping zwischen Javas primitiven Datentypen (sowie void) und C

21.5.3

Methoden aufrufen

So wie bei Attributzugriffen eine jfieldID nötig ist, so bedarf es bei Methodenaufrufen einer jmethodID. Diese liefert die Methode GetMethodID() für Objektfunktionen und GetStaticMethodID() für statische Funktionen. Anzugeben bei der ID-Suche ist der Name der Funktion und als String kodiert der Rückgabetyp und die Parametertypen. Ist das Ergebnis des Methodenaufrufs 0, so gibt es die Methode nicht.

1350

21.5

Erweiterte JNI-Eigenschaften

id = (*env) -> GetMethodID( env, cls, "getAbsolutePath", "()Ljava/lang/String;" ); if ( id == 0 ) { /* Fehlerbehandlung */ }

Hinweis Für die nicht so intuitive String-Signatur der Methode bietet sich das Dienstprogramm javap mit dem Schalter -s an. $ javap -s java.io.File

Der relevante Ausschnitt in unserem Fall lautet: public class java.io.File extends java.lang.Object implements java.io.Serializable,java.lang.Comparable{ public java.lang.String getAbsolutePath(); Signature: ()Ljava/lang/String; }

Die Funktionen CallObjectMethod() bzw. CallStaticMethod() rufen mit der jmethodID die JavaFunktion auf. Sie sind für alle Funktionen gedacht, die ein Objekt (also auch Felder) zum Ergebnis haben. id = (*env) -> GetMethodID( env, cls, "getAbsolutePath", "()Ljava/lang/String;" ); obj = (*env) -> CallObjectMethod( env, file, id );

Ist das Ergebnis ein primitiver Typ, steht der Typ der Rückgabe im Funktionsnamen – der Bauplan für die Namen ist CallTypMethod() bzw. CallStaticTypMethod(), also etwa CallBooleanMethod(). Im Fall keiner Rückgabe steht für den Typ einfach Void, wie CallStaticVoidMethod(). In unserem Beispiel mit getAbsolutePath() vom File-Ojekt hat die Methode keinen Parameter. Die C-Methoden CallMethod() bzw. CallStaticMethod() sind so definiert, dass sie Argumente per Varargs annehmen: CallMethod( JNIEnv *env, jclass clazz, jmethodID methodID, ... ); CallStaticMethod( JNIEnv *env, jclass clazz, jmethodID methodID, ... );

Neben den mit ... definierten Varargs gibt es zwei weitere Varianten für alle Funktionen, die auf "A" beziehungsweise auf "V" enden. Þ CallStaticMethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); Þ CallMethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); Þ CallStaticMethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); Þ CallObjectMethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

1351

21

21

Java Native Interface (JNI)

Die Funktionen mit der Endung "A" nehmen die Argumente für die Java-Funktion über einen Verweis auf ein jvalue-Feld an, und die Methode mit der Endung "V" nimmt die Argumente in einer Struktur vom Typ va_list an.

21.5.4

Threads und Synchronisation

Die Struktur JNIEnv bietet zur Synchronisation die zwei Funktionen, um das Betreten und Verlassen eines synchronisierten Blocks nachzubilden. Þ jint MonitorEnter(JNIEnv *env, jobject obj); Þ jint MonitorExit(JNIEnv *env, jobject obj);

Das zweite Argument ist genau das Lock-Objekt, an dem synchronisiert wird. Während in purem Java die Laufzeitumgebung bei einer Exception den Lock wieder freigibt, müssten wir das in C selbst überwachen.

21.6

Einfache Anbindung von existierenden Bibliotheken

Die Arbeit mit JNI ist nicht besonders komfortabel, sodass im Laufe der Zeit einige Vereinfachungen entstanden sind. Auf der einen Seite stehen Quellcodegeneratoren, auf der anderen Seite sehr dynamische Lösungen.

21.6.1

Generieren von JNI-Wrappern aus C++-Klassen und C-Headern

cxxwrap (http://cxxwrap.sourceforge.net/) liest Header-Dateien von C++ ein und generiert automatisch für die Klassen Stellvertreter auf der Java-Seite sowie Delegates in JNI, die auf die tatsächliche Implementierung weiterleiten. Aus den Beschreibungen der Header-Dateien nimmt cxxwrap nicht nur die Klassen mit Methoden, sondern auch die Konstanten. Auch versucht cxxwrap, so gut wie möglich die Parametertypen zu übersetzen, etwa char unsigned char in byte, und Pointer auf primitive Zellen in Arrays umzusetzen. Die Konstruktoren, Destruktoren und Methoden werden von der Java-Seite auf die C++-Seite weitergeleitet. Das Projekt wurde schon lange nicht mehr gepflegt, aber da sich die JNI-API auch nicht groß verändert hat, kann cxxwarp ein erster Start sein. Eine aktuellere Alternative ist gluegen (https:// gluegen.dev.java.net/). gluegen wird verwendet, um die Open GL-Schnittstellen nach Java zu bringen. Dazu liest es die C-Header-Dateien und macht so die Methoden für Java-Programme zugänglich.

1352

21.7

Invocation-API

Ein weiterer Generator ist SWIG (Simplified Wrapper and Interface Generator) unter http:// www.swig.org/. Mit einer zu schreibenden Interface-Datei generiert SWIG den JNI-Code für den Zugriff auf existierende C(++)-Programme. Das Projekt ist sehr aktiv.

21.6.2

COM-Schnittstellen anzapfen

Sind die nativen Methoden über COM zugänglich, ermöglichen die Bibliotheken Jawin (http:// jawinproject.sourceforge.net/) und com4j (https://com4j.dev.java.net/) mit einer kleinen nativen Bibliothek einfachen Zugriff auf diese Ressourcen; besonders praktisch ist das für MS Office, da es sich so praktisch fernsteuern lässt. com4j ist besonders nützlich, denn ein Generator erzeugt Java-Wrapper, sodass typsicheres Arbeiten möglich ist. Damit Programme mit com4j übersetzt und zum Laufen gebracht werden können, muss sich com4j.jar im Klassenpfad befinden und com4j.dll im Suchpfad für externe native Bibliotheken stehen. Ein Beispiel für Word liegt der Distribution bei. Jawin wurde schon länger nicht mehr aktualisiert, tut jedoch seine Dienste. com4j ist aktueller, aber auch seit drei Jahren ohne Update.

21.7

Invocation-API

Bisher haben wir JNI nur als Einbahnstraße gesehen (nicht im negativen Sinne). Doch anstatt nur aus Java heraus ein C(++)-Programm anzubinden, bietet JNI auch den anderen Weg: Ein C(++)-Programm kann die gesamte JVM einbetten. Das ist über die Invocation API möglich. Im Prinzip recht Folgendes aus: #include int main()

21

{ JavaVM *jvm; JNIEnv *env; JavaVMInitArgs jvmargs; jint r; jvmargs.nOptions = 0; jvmargs.version = JNI_VERSION_1_7; r = JNI_CreateJavaVM( &jvm, (void**)&env, &jvmargs ); if ( r < 0 )

1353

21

Java Native Interface (JNI)

return –1; // Ab hier ist über env alles möglich wie vorher (*jvm)->DestroyJavaVM( jvm ); }

Wer das Programm übersetzt, der sollte daran denken, die dynamische Bibliothek jvm.dll im Suchpfad zu haben; die Datei befindet sich im Java-Verzeichnis unter jre7\bin\client.

21.8

Zum Weiterlesen

Die Java Native Interface Specification unter http://download.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html ist eine der wichtigsten Quellen für diejenigen, mit intensiver mit JNI arbeiten wollen. Im Tutorial-Stil sind http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jniref.html und http://java.sun.com/docs/books/tutorialNB/ download/tut-native1dot1.zip gute Lernquellen. Eine JNI-FAQ bietet die Webseite von JGuru: http://tutego.de/go/jnifaq und http://mindprod.com/jgloss/jni.html. Eine Bibliothek zum Ansprechen von dynamischen Bibliotheken ist JNA (Java Native Access) unter https:// jna.dev.java.net/. Mit JNI kommen Entwickler hier gar nicht in Kontakt. Mehr Informationen zur Invocation API gibt http://download.oracle.com/javase/7/docs/technotes/guides/jni/spec/ invocation.html.

1354

Kapitel 22 Sicherheitskonzepte »Vorsicht ist die Einstellung, die das Leben sicherer macht, aber selten glücklich.« – Samuel Johnson (1709–1784)

22.1

Zentrale Elemente der Java-Sicherheit

Damit Java-Programme sicher arbeiten, greift eine Reihe von Elementen ineinander. Einige Dinge gibt schon die Sprache selbst vor (wie fehlende Pointerarithmetik, Sichtbarkeitsbereiche, Überwachung der Feldgrenzen), und andere ergeben sich durch die Laufzeitumgebung selbst. Zunächst ist da der Bytecode Verifier, der grob sicherstellt, dass der Java-Bytecode korrekt geformt ist. Zu den Prüfungen zählen zum Beispiel, dass Bytecode nicht in der Mitte enden darf, dass der Index auf Variablen korrekt ist und dass Sprünge nicht außerhalb des Programmcodes erfolgen. Der Klassenlader ist die nächste Einheit, und sein Einfluss auf die Sicherheit ist nicht offensichtlich. Er stellt jedoch sicher, dass sich Klassen in Paketen nicht überschreiben können und dass ein selbst geschriebenes java.lang.Object nicht plötzlich das Original überdeckt; beide befinden sich in unterschiedlichen Runtime Packages. Durch eine Hierarchie der Klassenlader kommt eine Anforderung an die Klasse java.lang.Object auch zuerst an den obersten Vater-Klassenlader, den Bootstrap Class Loader. Sind die Klassen geladen, überwacht zur Laufzeit der Sicherheitsmanager gültige Aufrufe; er sitzt immer zwischen unserer Java-Applikation und dem Betriebssystem. Der Sicherheitsmanager existiert seit Java 1.0, gibt aber die Arbeit seit Java 1.2 an eine verallgemeinerte Einheit, den Access Controller, weiter. Er nutzt Tricks wie Stack-Überprüfung, um herauszufinden, ob Aufrufer einer Methode schon gewisse Rechte erworben haben, um die Operation ausführen zu können. Der Access Controller stellt sicher, dass Programmcode nur dann ausgeführt werden kann, wenn die passenden Rechte vorhanden sind.

22.1.1

Security-API der Java SE

Die Gesamtheit aller Bibliotheken, die sich in Java um die Sicherheit kümmern, wird SecurityAPI genannt. Sie gesteht aus unterschiedlichen Teilen:

1355

22

22

Sicherheitskonzepte

Þ Verschlüsselung und Nachrichten-Authentifizierung. Seit Java 1.1 gibt es die Java-Crypto-

graphy-API. Die Java Cryptography Architecture (JCA) beschreibt, wie diese API benutzt werden kann. Ihr ist das Paket java.security gewidmet. In Java 1.4 ist dann die Java Cryptography Extension (JCE) hinzugekommen, die vorher nur als optionales Paket seit 1.2 erhältlich war. Sie erweitert die Möglichkeiten der JCA. Die Klassen und Schnittstellen sind an dem Paket javax.crypto zu erkennen. Þ Authentifizierung und Zugriffskontrolle. Die Java Authentication and Authorization Ser-

vice (JAAS) ist eine API mit Benutzerauthentifizierung etwa über ein Kerberos-System. Þ Public-Key-Infrastruktur. Sie dient zum Verwalten von Schlüsseln in Keystores und zum

Prüfen von digitalen X.509-Zertifikaten.

22.1.2

Cryptographic Service Providers

Die Security-API ist so entworfen worden, dass über sogenannte Cryptographic Service Provider Implementierungen ausgewechselt werden können. Die Security-API von Java ist völlig unabhängig von der Implementierung der kryptografischen Algorithmen und bietet zunächst Schnittstellen an. Die konkreten Algorithmen wie RSA oder DES realisieren Provider – Oracle ist einer von ihnen und bringt einen Standard-Provider für grundlegende Operationen mit.

Beispiel Welche Provider Java 7 bietet, zeigt folgender Zweiteiler: for ( Provider p : Security.getProviders() ) System.out.println( p + ": " + p.getInfo() );

Das Ergebnis sind zehn Einträge: SUN version 1.7: SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore; PKIX CertPathValidator; PKIX CertPathBuilder; LDAP, Collection CertStores, JavaPolicy Policy; JavaLoginConfig Configuration) SunRsaSign version 1.7: Sun RSA signature provider SunEC version 1.7: Sun Elliptic Curve provider (EC, ECDSA, ECDH) SunJSSE version 1.7: Sun JSSE provider(PKCS12, SunX509 key/trust factories, SSLv3, TLSv1) SunJCE version 1.7: SunJCE Provider (implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, Diffie-Hellman, HMAC)

1356

22.2

Der Sandkasten (Sandbox)

Beispiel (Forts.) SunJGSS version 1.7: Sun (Kerberos v5, SPNEGO) SunSASL version 1.7: Sun SASL provider(implements client mechanisms for: DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5, NTLM; server mechanisms for: DIGEST-MD5, GSSAPI, CRAM-MD5, NTLM) XMLDSig version 1.0: XMLDSig (DOM XMLSignatureFactory; DOM KeyInfoFactory) SunPCSC version 1.7: Sun PC/SC provider SunMSCAPI version 1.7: Sun's Microsoft Crypto API provider

Jeder Provider implementiert einen oder mehrere Algorithmen. Neue Provider lassen sich jederzeit einbringen, insbesondere dann, wenn uns die Amerikaner nicht erlauben, eine starke Verschlüsselung zu verwenden. Eine genauere Übersicht liefert http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136007.html und http://download.oracle.com/javase/7/ docs/technotes/guides/security/crypto/CryptoSpec.html.

22.2 Der Sandkasten (Sandbox) Seit den ersten Java-Versionen gibt es das Prinzip der Sandbox, wonach Applikationen nur ganz bestimmte Rechte haben und sich nicht mehr ergaunern können. Das gilt insbesondere für Applets im Browser. Sie dürfen nicht das Gleiche tun wie Applikationen: auf Festplatten zugreifen, die Zwischenablage auslesen und vieles mehr. Die Sandbox besteht aus zwei Teilen, die sehr wichtig für die Java-Sicherheit sind: dem Klassenlader (Klasse ClassLoader) und dem Sicherheitsmanager (SecurityManager). Der Klassenlader lädt die .class-Dateien und erzeugt (unter anderem) die dazugehörigen Class-Exemplare. Wir wollen uns in diesem Kapitel intensiver mit dem zweiten Teil, dem Sicherheitsmanager, auseinandersetzen.

1357

22

22

Sicherheitskonzepte

Normale Applikationen benutzen den Standardklassenlader und verwenden keinen Sicherheitsmanager. Applets aus dem Internet fordern besondere Sicherheit, sodass Browser einen besonderen Klassenlader und Sicherheitsmanager nutzen. Der Security-Manager für Applets erlaubt zum Beispiel kein Ausführen von externen Programmen, da auf diese Weise die Festplatte formatiert werden könnte. Auch darf ein Applet keine Klassen von einem fremden Server laden; zulässig ist nur der Server, von dem das Applet selbst geladen wurde. Diese Vorgabe stellt der Klassenlader sicher.

22.3 Sicherheitsmanager (Security Manager) Über Applets wissen wir bereits, dass sie auf einige Ressourcen des Rechners nicht zugreifen dürfen. Zwischen dem Aufrufer einer Bibliotheksmethode und dem Betriebssystem sitzt eine Einheit, die unsere Aktionen genau kontrolliert. Dieses Zwischensystem ist der Sicherheitsmanager. Standardmäßig startet die Laufzeitumgebung für Applikationen ohne Sicherheitsmanager, für Applets gibt es jedoch spezielle Sicherheitsmanager. Jeder Sicherheitsmanager ist eine Unterklasse von SecurityManager. Den aktuellen Sicherheitsmanager liefert die statische Methode System.getSecurityManager(). Führen wir folgende Zeile in einer Applikation aus, so sehen wir, dass anfänglich kein Sicherheitsmanager gesetzt ist, da die Ausgabe null ist. public class ApplicationSecManager { static public void main( String[] args ) { System.out.println( System.getSecurityManager() ); } }

Im Gegensatz dazu sind wir bei Applets mit einem etwas anderen Bild konfrontiert. Das folgende Programm liefert eine Ausgabe wie sun.applet.AppletSecurity@123456 auf dem Bildschirm. Die Zahl ist der Hashcode des Objekts: public class Applet1 extends java.applet.Applet { public void paint( java.awt.Graphics g ) { g.drawString( System.getSecurityManager().toString(), 10, 10 ); } }

1358

22.3

Sicherheitsmanager (Security Manager)

22

Abbildung 22.1: UML-Diagramm für Security-Manager

22.3.1

Der Sicherheitsmanager bei Applets

Die beiden Beispiele machen deutlich, dass dem Applet ein Security-Manager zugewiesen ist und den Applikationen kein Sicherheitsmanager etwas verbietet. Doch was ist einem Applet eigentlich verboten?

1359

22

Sicherheitskonzepte

Þ Applets dürfen nicht auf lokale Dateien des Client-Rechners zugreifen, sie etwa erzeugen,

lesen, modifizieren oder löschen. Sonst könnten sie wichtige Systemdateien an ihren Heimatserver übermitteln, und das stellt ein ernsthaftes Risiko dar. Þ Applets können außerdem keine Netzwerkverbindungen zu anderen Computern als dem

Heimatserver aufnehmen; dies gilt für alle Verbindungen, die mit den Netzwerk-Klassen URL, Socket und DatagramSocket möglich sind – was oft einen Einschnitt darstellt, weil etwa ein Börsenticker-Applet Kursdaten von einem anderen Server holen möchte. Das Internet als verteiltes System sollte auch vollständig genutzt werden können. Da Applets nicht in der Lage sind, Verbindungen zu Fremdrechnern aufzubauen, können sie natürlich auch nicht als Server fungieren. Þ Weiterhin kann ein Applet keine Programme ausführen, die auf dem lokalen Rechner lie-

gen. Applets dürfen auch keine anderen Programme starten und keine nativen Bibliotheken laden. (Für normale System-DLLs bringt das ohnehin nichts, da sie nicht über die benötigte Namenskonvention verfügen.) Þ Threads sind ein etwas anderes Problem. Da alle Applets gemeinsam in einer JVM laufen,

muss gewährleistet sein, dass sich nur die Threads eines Applets (also die Threads in der Thread-Gruppe des Applets) beeinflussen dürfen. In der Vergangenheit traten mehrfach Sicherheitsprobleme auf, bei denen sich Threads verschiedener Applets in die Quere kamen. Auch darf kein Applet die gemeinsam genutzte virtuelle Maschine beenden. Þ Applets dürfen keinen eigenen Sicherheitsmanager installieren. Könnte ein Applet das tun,

ließen sich leicht die für Applets geltenden Beschränkungen aushebeln. Der Java-fähige Webbrowser erzeugt für uns ein spezielles ClassLoader-Objekt, das abgesichert arbeitet. Þ Die Java-Umgebung setzt automatisch einige Properties, damit unser Programm etwas

über seine Umgebung erfahren kann. Diese Variablen lassen sich über System.getProperty(String) auslesen. Applets dürfen nur manche Variablen lesen. Sie haben keinen Zu-

griff auf Informationen über das Java-Home-Directory, den Java-Klassenpfad, den User-Namen, das Home-Verzeichnis und das Arbeitsverzeichnis des Anwenders. Die anderen Daten in den Variablen sind frei. Dazu gehören etwa: java.version, java.vendor, java.vendor.url, java.class.version, os.name, os.arch, os.version, file.separator, path.separator, line.separator. Obwohl diese Daten natürlich für statistische Zwecke missbraucht werden können, sind sie doch für ein Applet unter Umständen lebensnotwendig: So kann es etwa einfach an der Versionsnummer ablesen, ob eine bestimmte Klasse mit Methoden bestimmter Versionen implementiert ist oder nicht (andernfalls muss die Implementierung dies über eine Exception testen).

1360

22.3

Sicherheitsmanager (Security Manager)

22.3.2 Sicherheitsmanager aktivieren Nehmen wir ein Programm SecTest an, das den aktuellen Sicherheitsmanager anzeigt und dann die Länge einer Datei ausgibt. Listing 22.1: com/tutego/security/SecTest.java package com.tutego.security; import java.io.File; public class SecTest { public static void main( String[] args ) { System.err.println( System.getSecurityManager() ); System.err.println( new File( "c:/address.txt" ).length() ); } }

Die Schalter -Djava.security.debug und -Djava.security.manager Wenn wir das Programm starten, läuft es durch und gibt die Länge der Datei aus. Mit dem Schalter -Djava.security.debug=all können Sicherheitsprüfungen geloggt werden, und wir bekommen Ausgaben wie diese: $ java -Djava.security.debug=all com.tutego.security.SecTest scl:

getPermissions ProtectionDomain (file:/S:/25_Sicherheitskonzepte/

)

22

sun.misc.Launcher$AppClassLoader@11b86e7

java.security.Permissions@1a46e30 ( (java.io.FilePermission \S:\25_Sicherheitskonzepte\- read) (java.lang.RuntimePermission exitVM) ) scl: null 42924

1361

22

Sicherheitskonzepte

Geben wir beim Start den Schalter -Djava.security.manager an, so meldet die Laufzeitumgebung einen Standard-Sicherheitsmanager an. Ein println(System.getSecurityManager()) liefert dann eine Ausgabe wie »java.lang.SecurityManager@26b249«. (Auch mit -Djava.security. manager=MeineSecManagerKlasse lässt sich arbeiten.) Soll eine potenziell kritische Operation wie das Erfragen der Dateilänge ausgeführt werden, steigt die Laufzeitumgebung mit einer Exception aus, weil der Sicherheitsmanager das standardmäßig nicht zulässt: $ java -Djava.security.manager com.tutego.security.SecTest java.lang.SecurityManager@26b249 Exception in thread "main" java.security.AccessControlException: access denied ( java.io.FilePermission c:\address.txt read) at java.security.AccessControlContext.checkPermission(AccessControlContext .java:270) at java.security.AccessController.checkPermission(AccessController.java:401) at java.lang.SecurityManager.checkPermission(SecurityManager.java:542) at java.lang.SecurityManager.checkRead(SecurityManager.java:887) at java.io.File.length(File.java:790) at SecTest.main(SecTest.java:9) Wie nutzen die Java-Bibliotheken den Sicherheitsmanager?

Ein Sicherheitsmanager hat die Kontrolle über alle problematischen (also potenziell gefährlichen) Methoden in der Java-Bibliothek. Alle Methoden, die irgendetwas mit Sicherheit zu schaffen haben, fragen vorher den Sicherheitsmanager, ob sie überhaupt zu der kritischen Aktion berechtigt sind. Sehen wir uns dazu die Methode list() aus der Klasse java.io.File an: public String[] list() { SecurityManager security = System.getSecurityManager(); if ( security != null ) security.checkRead( path ); return fs.list( this ); }

Wir erkennen, dass die Methode zunächst den Sicherheitsmanager konsultiert und dann erst die wahre Aktion ausführt. Betrachten wir das Beispiel, so ist dies typisch für alle anderen Methoden aus der File-Klasse und macht deutlich, dass es keine Möglichkeit gibt, um diesen Sicherheitsmanager herumzukommen; es sei denn, die Methode list() vom internen FileSystem-Objekt ließe sich direkt aufrufen – was aber nicht möglich ist, weil fs fest als privates

1362

22.3

Sicherheitsmanager (Security Manager)

statisches Attribut in der Klasse File verankert ist. Sogar Unterklassen können fs also weder sehen noch nutzen. So wie die File-Klasse mit dem SecurityManager arbeitet, rufen auch andere Klassen Prüfmethoden auf, um zu entscheiden, ob der Zugriff auf eine Ressource erlaubt ist. Verweigert der Sicherheitsmanager eine Operation, löst er eine SecurityException aus. Der Sicherheitsmanager kann zum Beispiel folgende Klassen beschränken: Þ Toolkit: Können wir mit getSystemEventQueue() die Systemschlange für Ereignisse abfragen? Þ Window: Erzeugt in einem Applet zusätzlich die Einblendung »Warning: Applet Window«. Þ FileInputStream: Dürfen wir ein solches Objekt überhaupt erzeugen? Þ Class: Dürfen wir auf Elemente der Klasse zugreifen? Þ ClassLoader: Können wir einen neuen Klassenlader definieren? Þ Runtime: Können wir mit exit() aussteigen oder externe Programme ausführen?

22.3.3

Rechte durch Policy-Dateien vergeben

Zwar könnte ein Programm einen eigenen Sicherheitsmanager vom Typ SecurityManager installieren, doch das ist in Java in der Regel nicht nötig. Es gibt bessere Möglichkeiten, Sicherheitseinstellungen vorzunehmen. Um einem Programm die passenden Rechte zu geben – und damit in unserem Fall Zugriff auf die Dateilänge zu bekommen –, werden die zugesicherten Rechte in einer Policy-Datei gesammelt. Bekommt die Laufzeitumgebung beim Start einen Verweis auf die Policy-Datei, wird der Security-Manager auf die vergebenen Berechtigungen Rücksicht nehmen. Bei der Vergabe von Rechten können zusätzlich angegeben werden: Þ Codebase: Vergibt Rechte für Klassen, die von einem bestimmten Ort kommen. Þ Signierung: Es wird nur das Recht eingeräumt, wenn Programmcode signiert ist. Þ Principal: Gewährt bestimmte Rechte für authentifizierte Benutzer.

22 Policy-Dateien mit grant-Anweisungen Die Policy-Dateien bestehen aus einer Reihe von grant-Anweisungen. Sehen wir uns die Datei myPol.policy an, die für alle Dateien eine Leseberechtigung vergibt: Listing 22.2: myPol.policy grant { permission java.io.FilePermission

"", "read";

};

1363

22

Sicherheitskonzepte

java.security.manager Nun muss diese Berechtigungsdatei mit dem Sicherheitsmanager verbunden werden. Das geschieht am komfortabelsten von der Kommandozeile aus: $ java -Djava.security.manager -Djava.security.policy=myPol.policy com.tutego.security.SecTest java.lang.SecurityManager@26b249 28

Wir sehen, dass das Programm nun durchläuft und die Dateilänge ausgibt. Der Schalter -Djava.security.policy gibt einen Pfad auf die Berechtigungsdatei an. Die Datei muss im Pfad gefunden oder absolut adressiert werden.

Standard-Policy-Dateien Neben diesen benutzerdefinierten Rechteregeln gibt es vom System vergebene PolicyDateien. Sie werden von Java standardmäßig in der Datei java.policy im Unterverzeichnis lib/security (etwa C:\Program Files\Java\jre7\lib\security, aber auch C:\Program Files\Java\ jdk1.7.0\jre\lib\security) der Java-Installation gespeichert. Diese Rechtedatei definiert damit die »Standardberechtigungen«. Listing 22.3: C:\Program Files\Java\jre7\lib\security\java.policy, Ausschnitt // Standard extensions get all permissions by default grant codeBase "file:${{java.ext.dirs}}/*" { permission java.security.AllPermission; };

Alle Bibliotheken, die in das ext-Verzeichnis gelegt werden, bekommen alle Rechte. Das liegt an den Systemrechten. Das zeigt auch die Verwendung einer Code-Base: Sie spezifiziert den genauen Pfad, in dem die Rechte gelten. Also werden nur für das lib/ext-Verzeichnis alle Rechte vergeben, aber nicht für alle anderen. (Das ext-Verzeichnis ist nicht direkt angegeben, sondern über eine Variable. In älteren Java-Versionen wurde es als "file:${java.home}/lib/ ext/*" geschrieben.) Eigene, systemunabhängige Rechtedateien können wir unter dem Namen java.policy im Benutzerverzeichnis ablegen. Auch diese Dateien kann der Systemverwalter anpassen. Zunächst werden die Standardsicherheitsdateien genutzt und die Benutzerdateien »nachgeladen«. Eine weitere Datei java.security im Verzeichnis security der JRE und des JDK beschreibt, welche Rechtedateien genutzt werden. Genau dort befinden sich die beiden Dateien.

1364

22.3

Sicherheitsmanager (Security Manager)

Listing 22.4: C:\Program Files\Java\jre7\lib\security\java.security, Ausschnitt # The default is to have a single system-wide policy file, # and a policy file in the user’s home directory. policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy

Die Datei ist ebenso wichtig, wenn neue Provider, also Implementierungen der KryptografieAPI, angemeldet werden. Da Rechte nur vergeben, aber bisher nicht genommen werden können, besteht das Sicherheitsproblem darin, dass eine eigene Policy-Datei alle Rechte vergibt, wogegen die Systemdatei Einschränkungen vorsieht. In diesem Fall kann der Programmverwalter auch dem Benutzer das Recht nehmen, eigene Rechtedateien anlegen zu können. Dazu editiert er die Datei java.security. Der Eintrag allowSystemProperty muss false sein. Standardmäßig ist er true, wie der Ausschnitt zeigt: Listing 22.5: C:\Program Files\Java\jre7\lib\security\java.security, Ausschnitt # whether or not we allow an extra policy to be passed on the command line # with -Djava.security.policy=somefile. Comment out this line to disable # this feature. policy.allowSystemProperty=true

22.3.4 Erstellen von Rechtedateien mit dem grafischen Policy-Tool Das grafische Dienstprogramm policytool gibt uns die Möglichkeit, Applikationen und signierten Applets spezielle Rechte einzuräumen oder zu verweigern. Das Policy-Tool nimmt uns die Arbeit ab, die Rechtedateien von Hand zu editieren. Nach dem Aufruf des Programms policytool öffnet sich ein Fenster, das uns einige Menüpunkte bereitstellt, über die wir bestehende Rechtedateien editieren oder auch neue anlegen können.

Abbildung 22.2: Das grafische Policy-Tool

1365

22

22

Sicherheitskonzepte

Neue Einträge für die Zugeständnisse der Laufzeitumgebung an das Programm werden über das Menü Add Policy Entry vorgenommen. Über das Dialogfenster können wir anschließend eine Reihe von erlaubten Eigenschaften sowie Permissions auswählen. Die folgende Tabelle zeigt einige Permissions und ihre Bedeutungen: Permission

Bedeutung

AllPermission

Die Anwendung oder das Applet darf alles.

FilePermission

Zugriff auf Dateien und Verzeichnisse

NetPermission

Zugriff auf Netzwerkressourcen

PropertyPermission

Zugriff auf Systemeigenschaften

ReflectPermission

Zugriff über Reflection auf andere Objekte erlauben

RuntimePermission

Einschränkungen von Laufzeitsystemen wie Klassenlader

SecurityPermission

Einstellen eines allgemeinen Sicherheitskonzepts, etwa für den Zugriff auf Policies

SerializablePermission

Beschränkung der Serialisierung

SocketPermission

Spezielle Einschränkungen an einer Socket-Verbindung

Tabelle 22.1: Auswahl einiger Permissions

22.3.5

Kritik an den Policies

Die Policies sind eine nette Sache, um für eine Applikation die Rechte einzuschränken oder zu vergeben, doch sie sind nicht unproblematisch.

Format der Policy-Dateien Policy-Dateien sind standardmäßig Textdateien auf der Clientseite des Anwenders. Ein Anwender kann diese Textdateien ohne große Probleme ändern und mehr Rechte zugestehen. Die Rechtedateien sind durch keine Prüfsumme gesichert, um Änderungen gegebenenfalls zu erkennen. Zum anderen ist das Textformat nicht mehr so »modern«, und XML-basierte Textformate lösen proprietäre Dateiformate immer mehr ab. Da sich das Sicherheitssystem von Java jedoch beliebig erweitern lässt, lassen sich die Standardtextformate durch ein neues System ersetzen, das etwa XML-Dateien liest und eine (mit einer digitalen Signatur gesicherte) Prüfsumme speichert, die nicht mehr so leicht veränderbar ist.

1366

22.3

Sicherheitsmanager (Security Manager)

Kein Refresh Wurden Policy-Dateien einmal vom System eingelesen, können sie zwar nachträglich verändert werden, doch das Java-System erkennt diese Änderung nicht. Als Konsequenz muss die Applikation neu gestartet werden. Das ist für Benutzerapplikationen kein großes Dilemma, doch für serverseitige Applikationen ein großes Problem. Es ist unmöglich, für eine kleine Änderung an den Policy-Dateien etwa den EJB-Server herunterzufahren und wieder neu zu starten. Angenommen, das System ist ein Five-Nine-System, weist also eine Verfügbarkeit von 99,999 % auf, so würde dies eine erlaubte Ausfallzeit von etwa fünf Minuten ausmachen – was bei laufender Änderung der Policies kaum zu erreichen ist.

Keine Rollen bei der Rechtevergabe Der Zweck des Policy-Verfahrens besteht darin, einem Programm Rechte zuzuordnen. Die Rechte können weiterhin für Programme vergeben werden, die von einem bestimmten Ort kommen (CodeSource) und von einem bestimmten Anwender signiert sind (SignedBy). Was wäre, wenn wir einem bestimmten Benutzer Rechte zuordnen wollten? Das ist nicht so einfach möglich. Jeder Benutzer müsste dann das Jar-Archiv signieren, und der Policy-Datei wäre zu entnehmen, was jedem Benutzer zustünde. Doch das taugt nichts! Käme ein Benutzer hinzu, müsste das Archiv neu signiert werden – bei 10.000 Benutzern ein undenkbares Unterfangen. Des Weiteren könnte der Benutzer selbst seine Rechte erweitern, was nicht sinnvoll wäre. Wir brauchen also nicht nur ein Verfahren, das nach den Quellen, sondern auch nach Rollen unterscheidet. Dieses Verfahren heißt rollenbasierte Rechtevergabe. Jedem Benutzer ist eine Rolle zugeordnet, und anhand dieser Rolle werden ihm Rechte zugewiesen. Das Betriebssystem Windows nutzt zum Beispiel diesen Typ der Rechtevergabe.

JAAS (Java Authentication and Authorization Service) Für ein rollenbasiertes Sicherheitssystem gibt es JAAS (Java Authentication and Authorization Service). Es ist Bestandteil seit Java 1.4. Die beiden Hauptteile sind: Þ Authentifikation/Authentifizierung. Gibt die Identität einer Person oder eines Programms

gegenüber einem Kommunikationspartner an. Þ Autorisierung. Sicherstellen der Rechte, sodass ein Benutzer Aktionen durchführen kann

beziehungsweise ihm Aktionen verwehrt bleiben. Das JAAS ist damit eine Java-Version eines Pluggable Authentication Module (PAM). Wichtig in diesem Zusammenhang sind Login-Module. Sie erlauben die Anmeldung an eine Instanz, sodass sich der Benutzer authentifizieren kann. Dies kann ein komplexes System wie

1367

22

22

Sicherheitskonzepte

Kerberos sein, aber auch eine Smart-Card oder ein biometrisches System. Einige Login-Module liefert das JDK mit jeder Laufzeitumgebung mit aus. Hat sich der Benutzer (das Subject) mit dem Login-Modul authentifiziert, verbindet JAAS ihn mit authentifizierten Benutzerkennungen, die Principal heißen. Bei den Policy-Dateien haben wir nun gesehen, wie die Rechte auch für eine Codebase, einen Signierer und auch einen Principal vergeben werden können. Der Sicherheitsmanager ermöglicht also einem Programmstück nur dann die Ausführung, wenn ein Benutzer angemeldet ist und die nötigen Rechte hat. Der Programmcode muss also keine Implementierung mit Fallunterscheidungen für diverse Rollen aufweisen, sondern kann einfach in der Policy-Datei mit einem Principal assoziiert werden.

22.4 Signierung Wir wollen uns in diesem Kapitel mit einigen Werkzeugen beschäftigen, die zur Signierung von Java-Archiven vom JDK angeboten werden. Zum Signieren von Applikationen sowie von Applets und zur Vergabe der Zugriffsrechte und -beschränkungen stellt Oracle die Dienstprogramme keytool, jarsigner und policytool bereit.

22.4.1

Warum signieren?

Die Sandbox einer Java VM ist sinnvoll, damit Amok laufende Applikationen keine ernsthaften Schäden anrichten können. Es gibt aber genauso gut Szenarien, in denen es nützlich ist, Applets mehr Freiräume einzuräumen. Gründe können sein: Eingebundene native Bibliotheken sollen eine Authentifizierung über das Ohrläppchen eines Anwenders vornehmen oder auf die Festplatten zum Caching zurückgreifen können.

22.4.2 Digitale Ausweise und die Zertifizierungsstelle Wenn nun das Java-Programm diesen Zugriff eigentlich nicht machen darf, aber möchte, was ist die Lösung? Die Antwort ist, Programme mit einem Autor zu verbinden und die Programme dann auszuführen, wenn wir dem Autor vertrauen. Zentral bei diesem Spiel ist die sichere Identifizierung des Autors. Das übernimmt eine Zertifizierungsstelle (Certificate Authority, CA), die digitale Signaturen ausstellt, um eine Person oder Organisation zu identifizieren. Ein Programmstück wird dann mit einer Signatur verbunden, sodass immer der Autor bekannt ist, wenn kritische Programmstellen Unheil anrichten und ich dem Autor so richtig meine Meinung sagen möchte.

1368

22.4

Signierung

Die Zertifizierungsstelle ist ein kleiner Schwachpunkt in diesem Szenario, denn erst einmal hindert uns keiner daran, selbst die Zertifizierungsstelle zu spielen und das Zertifikat auf Mickey Mouse auszustellen – das machen wir auch gleich. Dem Anwender obliegt die Verantwortung, nur Zertifikate von wirklich autorisierten Stellen anzunehmen. Die Bundesnetzagentur akkreditiert Zertifizierungsstellen. Hier gibt es einige bekannte CAs wie VeriSign, CAcert oder Thawte, weitere sind unter http://www.pki-page.org/ vermerkt. Das Zertifikat selbst verbindet die Person mit einem kryptografischen Schlüssel und weiteren Informationen wie Seriennummer, Aussteller und Lebensdauer. Dem Schlüssel kommt die größte Bedeutung zu, denn damit wird das Java-Archiv signiert. Der wichtigste Standard für Zertifikate ist der ITU-T-Standard X.509. Die Zertifikate sind in ASN.1 (Abstract Syntax Notation One) kodiert.

22.4.3 Mit keytool Schlüssel erzeugen Das Programm keytool erzeugt öffentliche und private Schlüssel und legt sie in einer passwortgeschützten und verschlüsselten Datei ab. Die Datei hat standardmäßig den Namen .keystore und befindet sich im Benutzerverzeichnis des Anwenders. Mit dem Programm keytool lassen sich neben der Schlüsselgenerierung auch Zertifikate importieren, Zertifikatsanforderungen ausstellen und Schlüssel als vertrauenswürdig festlegen. Möchten wir einen Schlüssel erstellen, rufen wir das Programm keytool mit der Option -genkey auf. Daneben gibt die Option -alias einen Namen für den Schlüsselinhaber an. Nehmen wir an, dass das JDK-Programm keytool (zum Beispiel im Verzeichnis C:\Program Files\Java \jdk1.7.0\bin) im Suchpfad eingebunden ist. $ keytool -genkey -alias CUllenboom

Anschließend fragt keytool nach dem Passwort des Schlüsselspeichers und erfragt weitere Angaben zum Inhaber. Mit diesen Informationen erzeugt das Programm ein Schlüsselpaar mit einem selbstzertifizierenden Zertifikat. Bei diesem speziellen Zertifikat sind Aussteller und Inhaber identisch. Option

Bedeutung

-genkey

Erzeugung eines Schlüsselpaars

-import

Importieren eines Zertifikats

-selfcert

Erstellung eines selbstinitiierten Zertifikats

-certreq

Export einer Zertifikatsanforderung; sie kann als Datei an eine CA geschickt werden.

Tabelle 22.2: Optionen von »keytool«

1369

22

22

Sicherheitskonzepte

Option

Bedeutung

-export

Export eines Zertifikats. Dieses kann dann von einem anderen Benutzer importiert und als vertrauenswürdig deklariert werden.

-list

Listet alle Zertifikate und Schlüssel auf.

-delete

Entfernt ein Schlüsselpaar.

-help

Zeigt eine kurze Hilfe an.

Tabelle 22.2: Optionen von »keytool« (Forts.)

Die Angabe der Optionen ist immer dann sinnvoll, wenn die Standardeinstellungen unpassend sind. Über die Optionen lassen sich die Algorithmen zur Verschlüsselung und Schlüsselgenerierung ebenso angeben wie eine Pfadangabe des Schlüsselspeichers oder die Gültigkeitsdauer.

22.4.4 Signieren mit jarsigner Das Dienstprogramm jarsigner signiert und verifiziert Jar-Archive. Dazu erstellt jarsigner zunächst einen Hashcode des Archivs und verschlüsselt ihn anschließend mit dem privaten Schlüssel des Nutzers. Das Zertifikat und der öffentliche Schlüssel werden dem Archiv beigelegt.

Beispiel Signiere das Archiv archiv.jar mit dem Schlüsselspeicher (Keystore) von CUllenboom. $ jarsigner archiv.jar CUllenboom

Die Anwendung mit jarsigner fügt dem Archiv zwei weitere Dateien hinzu: eine SignaturDatei mit der Endung .sf und eine Signaturblock-Datei mit der Endung .dsa. Die SignaturDatei enthält eine Liste aller Dateien im Archiv und speichert zudem den Hash-Wert zu einer mit aufgeführten Hash-Funktion. Diese Signatur-Datei wird dann mit dem privaten Schlüssel des Signierers signiert und zusammen mit dem verschlüsselten Zertifikat des Signierers in der Signaturblock-Datei gespeichert.

Beispiel Wir möchten wissen, ob ein Archiv verifiziert ist: $ jarsigner -verify archiv.jar

1370

22.5

22.5

Digitale Unterschriften *

Digitale Unterschriften *

Ein Message-Digest, kurz MD, definiert ein Verfahren zur Erzeugung digitaler Unterschriften für Dokumente. Der berechnende Algorithmus ist eine Einwegfunktion und liefert aus einer Botschaft beliebiger Länge einen »Fingerabdruck« in Form einer Zahl. Die Fingerabdrücke dienen dazu, veränderte Botschaften zu bemerken. Hängen wir einer Rechnung etwa eine 0 an, soll dies nicht unbemerkt bleiben. Wir haben ähnliche Funktionen schon beim Hash-Verfahren kennengelernt. Die meisten Klassen überschreiben die Methode hashCode() der Oberklasse Object, um einen int (also 32 Bit) als Identifizierer zu liefern. Für die Begriffe Message-Digest und Fingerabdruck gibt es weitere Synonyme: Kompressionsfunktion, Kontraktionsfunktion, kryptografische Prüfsumme (falls die Unterschrift zusätzlich verschlüsselt ist), Integritätsprüfung von Nachrichten (Message Integrity Check, MIC) oder Erkennung von Manipulationen (Manipulation Detection Code, MDC). Ist dem Fingerabdruck ein Schlüssel beigefügt, fällt auch der Begriff Message Authentication Code (MAC).

22.5.1

Die MDx-Reihe

Die Firma RSA1 Data Security entwickelte mit der Reihe MD2, MD4 und MD5 drei Verfahren zur Berechnung kryptografischer Prüfsummen. MD5 produziert 128-Bit-Hash-Werte; das ist eine 32-stellige Hexadezimalzahl. Während bei MD2 (1989) und MD4 (1990) sich relativ schnell Schwächen zeigten, dauerte es beim MD5-Verfahren (1991) mehr als 10 Jahre, bis im August 2004 von der Arbeitsgruppe um Xiaoyun Wang Methoden zur Kollision und von Patrick Stach2 ein Kollisionsgenerator entwickelt wurden. Wir haben uns schon beim Hashing in Kapitel 3 mit Kollisionen beschäftigt. Da ein MD5-Hash-Wert 128 Bit lang ist, kann er 2^128 verschiedene Ausgabewerte annehmen. Zwangsläufig sind unter 2^128 + 1 verschiedenen Nachrichten mindestens zwei Texte mit gleichem Hash-Wert. Die Wahrscheinlichkeit, dass eine beliebige Nachricht den gleichen Hash-Wert wie eine vorgegebene Nachricht hat, liegt bei 0,000 000 000 000 000 000 000 000 000 000 000 000 029 % (1/2^128). (Die Wahrscheinlichkeit, dass zwei beliebig gewählte unterschiedliche Nachrichten den gleichen HashWert haben, ist jedoch deutlich höher. Dieses Phänomen heißt Geburtstagsparadoxon.) Die chinesische Arbeitsgruppe führte einen erfolgreichen Kollisionsangriff vor und generierte zwei Dokumente mit dem gleichen MD5-Hash. Am 1. März 2005 konnten zwei X.509Zertifikate erstellt werden, die den gleichen MD5-Hash besaßen, aber unterschiedliche öffentliche Schlüssel. Dabei hat die Forschergruppe lediglich Kommentarfelder mit unterschiedli-

1

Programmiert wurde es von Ron Rivest, einem der Mitentwickler des RSA-Public-Key-Verfahrens. Daher auch das »R«. Die Kollegen waren Adi Shamir und Leonard Adleman.

2

http://www.stachliu.com/collisions.html

1371

22

22

Sicherheitskonzepte

chen Werten belegt. Dabei ist ein Kollisionsangriff der einfachere Fall, denn hier müssen lediglich zwei verschiedene Nachrichten mit gleichem MD5-Hash gefunden werden. Spannender ist ein Preimage-Angriff, bei dem zu einem gegebenen Hash-Wert (etwa von einem X.509-Zertifikat) ein neues Dokument (also wieder ein X.509-Zertifikat) mit gleichem MD5-Hash-Wert erzeugt wird. Doch ein Preimage-Angriff ist für X.509-Zertifikate bisher nicht bekannt, und daher geht von MD5 im Moment auch keine echte Gefahr für digitale Signaturen aus.

22.5.2

Secure Hash Algorithm (SHA)

Das National Institute of Standards and Technology (http://www.nist.gov) entwickelte im Secure Hash Standard (SHS) den Secure Hash Algorithm (SHA)3 mit einem Hash-Wert von 160 Bit. Der Secure-Hash-Standard ist Teil des Digital-Signature-Standards (DSS), und dieser wiederum ist Teil des Capstone-Projekts. Dieses Projekt definiert Standards für öffentlich verfügbare Kryptografie. Es besteht aus vier Hauptkomponenten. Dazu gehören ein symmetrischer Verschlüsselungsalgorithmus (Skipjack, auch Clipper genannt), ein Schlüsselaustauschprotokoll (bisher nicht öffentlich, ein Diffie-Hellman-Verfahren), ein Hash-Algorithmus (SHA) und ein Algorithmus für die digitale Unterschrift (DSA, die SHA benutzt). Die Auswahl der Algorithmen treffen das NIST und die NSA. Die erste Version von SHA hatte eine undokumentierte Schwachstelle, die in einer neuen Implementierung, SHA-1, nicht mehr vorhanden ist. Wir nennen SHA-1 im Folgenden einfach SHA und beziehen uns damit auf die neueste Version. Im Vergleich zu MD5 generiert SHA einen Hash-Wert von 160 Bit, was Brute-Force-Angriffe erschwert. Die Analysen von Xiaoyun Wang, Yiqun Lisa Yin und Hongbo Yu zeigten jedoch auch Schwächen von SHA-1 auf. Das Team konnte die Anzahl der Kollisionsberechnungen von Brute-Force-Angriffen von 2^80 auf 2^69 und später auf 2^63 senken – Security-Guru Bruce Schneier vermeldet auf seiner Webseite4, dass SHA-1 damit mehr oder weniger geknackt sei.

22.5.3

Mit der Security-API einen Fingerabdruck berechnen

Der Einstiegspunkt für Fingerabdrücke ist eine statische Methode, MessageDigest.getInstance(), die ein Exemplar eines konkreten kryptografischen Algorithmus liefert. Das Argu-

ment ist ein Name für den Algorithmus, etwa MD5 oder SHA. Das JDK implementiert den 3

Eine Beschreibung des Algorithmus ist unter http://www.itl.nist.gov/fipspubs/fip180-1.htm abgelegt. FIPS ist die Abkürzung für Federal Information Processing Standards Publication – ich glaube nicht, dass es eine Verwandtschaft mit Heinz Erhardts Fips gibt.

4

http://www.schneier.com/blog/archives/2005/08/new_cryptanalyt.html und http://www.schneier.com/blog/ archives/2005/02/sha1_broken.html

1372

22.5

Digitale Unterschriften *

DSA-Algorithmus NIST Digital Signature Algorithm und für digitale Signaturen SHA-1 und MD5. Da wir uns etwas näher mit Signaturen beschäftigt haben, wollen wir zunächst die Klasse MessageDigest für digitale Fingerabdrücke untersuchen.

22.5.4 Die Klasse MessageDigest Mit der statischen Methode getInstance() bekommen wir einen Algorithmus, der eine bestimmte Berechnungsfunktion implementiert. getInstance() gibt es in zwei Varianten. Beiden gemeinsam ist der Name des Algorithmus im übergebenen Argument. Die folgende Anweisung erzeugt ein MessageDigest-Objekt für SHA: MessageDigest digest = MessageDigest.getInstance( "SHA" );

Eine zweite Variante spezifiziert den Hersteller näher: MessageDigest digest = MessageDigest.getInstance( "SHA", "SUN" );

Ist der Algorithmus nicht bekannt, bekommen wir eine NoSuchAlgorithmException. Ein unbekannter Hersteller führt zu einer NoSuchProviderException.

22

Abbildung 22.3: UML-Diagramm der Klasse MessageDigest

Den Fingerabdruck berechnen Nun können wir eine Nachricht kodieren. Dafür gibt es zwei Möglichkeiten: Die Nachricht wird als Ganzes übergeben, oder sie wird Schritt für Schritt kodiert. Die Funktionsweise erinnert sehr an die Schnittstelle java.util.zip.Checksum.

1373

22

Sicherheitskonzepte

Beispiel Die folgenden Zeilen berechnen den Hash-Wert für eine Zeichenkette: MessageDigest md = MessageDigest.getInstance( "SHA" ); md.update( "Hello".getBytes() );

Die update()-Methode ist auch für ein einzelnes Byte deklariert. Für große Dateien ist es besser, immer wieder update() auf größeren Blöcken aufzurufen, anstatt die ganze Datei auf einmal zu übergeben, da dafür die Datei vollständig im Speicher stehen müsste. Schritt für Schritt nehmen wir mit update() immer mehr von der Nachricht hinzu. Ein Beispiel: Berechne den Hash-Wert für alle Daten eines Eingabestroms: Listing 22.6: com/tutego/security/digest/MessageDigestDemo.java, messageDigest() static byte[] messageDigest( InputStream in, String algo ) throws Exception { MessageDigest messageDigest = MessageDigest.getInstance( algo ); byte[] md = new byte[ 8192 ]; for ( int n = 0; (n = in.read( md )) > –1; ) messageDigest.update( md, 0, n ); return messageDigest.digest(); }

Den Fingerabdruck auslesen Nach dem Sammeln berechnet die Methode digest() die Signatur. Sie hat eine bestimmte Länge in Byte, die wir mit getDigestLength() erfragen können. Da digest() ein Byte-Array zurückliefert, ist der Wert von getDigestLength() mit der Länge des Arrays identisch. digest() lässt sich auch mit einem Byte-Feld aufrufen. Ein einfaches SHA-Programm für den String sieht daher so aus: Listing 22.7: com/tutego/security/digest/SHATest.java, Ausschnitt MessageDigest md = MessageDigest.getInstance( "SHA" ); byte[] digest = md.digest( "abd".getBytes() ); for ( byte b : digest ) System.out.printf( "%02x", b );

Das Programm erzeugt die Ausgabe:

1374

22.6

Verschlüsseln von Daten(-strömen) *

a9 99 3e 36 47 6 81 6a ba 3e 25 71 78 50 c2 6c 9c d0 d8 9d

Schon eine einfache Veränderung wirkt sich global aus. Statt »abc« kodieren wir jetzt »Abc« und »abd«. Einmal ändert sich der Kleinbuchstabe in einen Großbuchstaben, und im anderen Fall nehmen wir einfach das nachfolgende Zeichen im Alphabet. Kein Byte bleibt gleich: 91 58 58 af a2 27 8f 25 52 7f 19 20 38 10 83 46 16 4b 47 f2

// Abc

cb 4c c2 8d f0 fd be 0e cf 9d 96 62 e2 94 b1 18 9e 2a 57 35

// abd

22.6 Verschlüsseln von Daten(-strömen) * Kryptografie unterscheidet zwischen asymmetrischer und symmetrischer Verschlüsselung. Ist die Kommunikation asymmetrisch, sind zwei Schlüssel nötig: ein öffentlicher und ein privater. Bei der symmetrischen Verschlüsselung ist nur ein Schlüssel erforderlich, der bei der Ver- und Entschlüsselung gleich ist. Bekanntester Vertreter der symmetrischen Verschlüsselung ist DES (Data Encryption Standard), der allerdings wegen seiner geringen Schlüssellänge nicht mehr aktuell ist. DES wurde 1981 in den USA als ANSI-Standard normiert. Bei den asymmetrischen Verfahren ist die RSA-Verschlüsselung die bekannteste. Ihre Sicherheit basiert auf dem mathematischen Problem, für eine große Ganzzahl eine Primfaktorzerlegung zu finden. Asymmetrische Verschlüsselung ist im Vergleich zu symmetrischen Verschlüsselungen langsam.

22.6.1

Den Schlüssel, bitte

Jeder Schlüssel, sei er privat oder öffentlich, implementiert die Basisschnittstelle java.security.Key. Von dieser Schnittstelle gibt es Unterschnittstellen, etwa PublicKey, PrivateKey für die asymmetrischen Schlüssel oder SecretKey für den symmetrischen Schlüssel. Von diesen Schnittstellen existieren weitere Unterschnittstellen.

Abbildung 22.4: UML-Diagramm der Klasse Key

1375

22

22

Sicherheitskonzepte

Schlüssel aus der Fabrik Um Schlüssel zu erzeugen, gibt es zwei Fabriken: KeyGenerator erzeugt symmetrische Schlüssel und KeyPairGenerator asymmetrische. Der Fabrikmethode getInstance() ist dabei eine Kennung zu übergeben, die für den Algorithmus steht. Listing 22.8: com/tutego/security/crypto/KeyGeneratorDemo.java, main() KeyGenerator kg = KeyGenerator.getInstance( "DES" ); KeyPairGenerator kpg = KeyPairGenerator.getInstance( "RSA" );

Der nächste Schritt sieht eine Initialisierung des Schlüssels mit zufälligen Werten vor. Ohne Initialisierung kann jeder Provider unterschiedlich verfahren. kg.init( 56 );

// nicht größer als 56!

kpg.initialize( 1024 );

Nicht besonders schlau ist, dass einmal die Methode init() heißt und dann einmal initialize(). Beiden Methoden lässt sich noch ein Zufallszahlengenerator mitgeben, doch intern ist das SecureRandom schon sehr gut. Kryptografische Angaben kann ein Objekt vom Typ AlgorithmParameterSpec einführen. Der letzte Schritt besteht im Erfragen der Schlüssel: SecretKey secKey = kg.generateKey(); KeyPair

keyPair = kpg.genKeyPair();

Bei einer Ausgabe des symmetrischen Schlüssels über System.out.println() kommt nicht viel Sinnvolles heraus, doch bei den privaten und öffentlichen Schlüsseln, die keyPair mit getPublic() und getPrivate() offenlegt, implementieren PublicKey und PrivateKey eine ansehnliche toString()-Methode. System.out.println( keyPair.getPublic() );

Liefert: SunJSSE RSA public key: public exponent: 010001 modulus: a8186ac3 03b9417e c0247c70 d225ae75 04d2fa3b 9b21e009 ca32a1f3 3cc7404f aeb6df52 0aa4d9ab ae35a5d5 d7b30f38 ce670895 3234fab2 c67f1211 b9dab8d2 edda3a7b 710fbf86 0274a2a6 842c4d73 76fc2166 80ef1e82 36a949f9 8180c5c7 004cffdd c103b42b 9abf216d 5f797440 20b8ec52 afe44407 a871e1f7 0e27fec9

1376

22.6

Verschlüsseln von Daten(-strömen) *

System.out.println(keyPair.getPrivate()) liefert eine noch längere Ausgabe mit Exponent,

Modulo usw.

SecretKeySpec Schlüssel für die symmetrische Verschlüsselung sind nichts anderes als Binärfelder. Die Klasse javax.crypto.spec.SecretKeySpec dient zum Erzeugen eines symmetrischen Schlüssels und erwartet im Konstruktor das Byte-Feld und den Algorithmus. Key k = new SecretKeySpec( "01234567".getBytes(), "DES" );

Für andere Typen existieren wiederum andere Klassen. Zum Beispiel erzeugt DSAPrivateKeySpec einen privaten Schlüssel aus einem anderen Schlüssel, zwei Primzahlen und einer Basis,

die als BigInteger-Objekte angegeben sind.

22.6.2 Verschlüsseln mit Cipher Die Klasse javax.crypto.Cipher bildet das Zentrum der JCE. Nachdem init() das Objekt mit einem Modus und Schlüssel initialisiert hat, lassen sich mit update(byte[]) Daten durchschleusen. doFinal() rundet das Ganze dann ab. Die Rückgabe ist immer ein verschlüsselter Block von byte. Cipher cipher = Cipher.getInstance( "DES" ); cipher.init( Cipher.ENCRYPT_MODE, key ); byte[] verschlüsselt = cipher.doFinal( unverschlüsselt );

Beim Entschlüsseln wird der Cipher einfach in den Modus Cipher.DECRYPT_MODE gesetzt.

22.6.3 Verschlüsseln von Datenströmen

22

Zum Verschlüsseln von Datenströmen bietet die Java-Bibliothek die praktischen Klassen javax.crypto.CipherInputStream und CipherOutputStream an. Sie erwarten ein Cipher-Objekt, das eine DES-Verschlüsselung durchführt. Listing 22.9: com/tutego/security/crypto/ReadWriteDES.java package com.tutego.security.crypto; import java.io.*; import java.security.Key; import javax.crypto.*;

1377

22

Sicherheitskonzepte

import javax.crypto.spec.SecretKeySpec; import sun.misc.*; public class ReadWriteDES { static void encode( byte[] bytes, OutputStream out, String pass ) throws Exception { Cipher c = Cipher.getInstance( "DES" ); Key k = new SecretKeySpec( pass.getBytes(), "DES" ); c.init( Cipher.ENCRYPT_MODE, k ); OutputStream cos = new CipherOutputStream( out, c ); cos.write( bytes ); cos.close(); } static byte[] decode( InputStream is, String pass ) throws Exception { Cipher c = Cipher.getInstance( "DES" ); Key k = new SecretKeySpec( pass.getBytes(), "DES" ); c.init( Cipher.DECRYPT_MODE, k ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); CipherInputStream cis = new CipherInputStream( is, c ); for ( int b; (b = cis.read()) != –1; ) bos.write( b ); cis.close(); return bos.toByteArray(); } public static void main( String[] args ) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); encode( "Das wird anders werden".getBytes(), out, "01234567" );

1378

22.7

Zum Weiterlesen

String s = new BASE64Encoder().encode( out.toByteArray() ); System.out.println( s ); // qJYN+8Hd5dXsgMl1akQnw4iCbRN3EUbK byte[] decode = new BASE64Decoder().decodeBuffer( s ); InputStream is = new ByteArrayInputStream( decode ); System.out.println( new String( decode( is, "01234567" ) ) ); } }

22.7

Zum Weiterlesen

Da jeder etwas anderes unter Sicherheit versteht, sind auch die Möglichkeiten zum Weiterlesen nahezu unbeschränkt. Die Verifizierung ist sehr trickreich, und insbesondere ein finallyBlock verlangt einiges an Aufmerksamkeit. Der Abschnitt 4.9 der »Java Language Specification« beschreibt die Validierung genauer. Auf die Probleme geht der Artikel »The Problem of Bytecode Verification in Current Implementations of the JVM« (http://citeseer.ist.psu.edu/ 484520.html) etwas genauer ein. Das Zusammengreifen der einzelnen Elemente wie Klassenlader, Verifier, Sicherheitsmanager ist gut im Online-Kapitel von »Inside the Java Virtual Machine« (http://www.artima.com/insidejvm/ed2/security.html) nachzulesen.

22

1379

Kapitel 23 Dienstprogramme für die Java-Umgebung »Die glücklichen Sklaven sind die erbittertsten Feinde der Freiheit.« – Marie von Ebner-Eschenbach (1830–1916)

23.1

Programme des JDK

Während das JRE nur eine Laufzeitumgebung enthält, besteht eine JDK-Installation aus dem JRE plus einer größeren Zahl Tools im bin-Ordner. Während beim JDK 1.0 im Wesentlichen nur der Compiler und die Laufzeitumgebung Teil des Pakets waren, erhöhte sich die Anzahl der Werkzeuge auf etwa 50. Mit den Werkzeugen lassen sich zum Beispiel die gestarteten Java-Programme auflisten oder lässt sich ein kleiner CORBA-Namensdienst-Server starten. Wie andere Dienstprogramme auch, dokumentiert Oracle die Programme unter http://tutego.de/go/jdktools.

23.2 Monitoringprogramme vom JDK Java ist eine beliebte Technologie auf der Serverseite, und da ist das Überwachen einer JVM sehr wichtig.

23.2.1

23

jps

Das Programm jps ist das Java Virtual Machine Process Status Tool und liefert alle laufenden Java-Programme mit einem lokalen VM-Identifizierer. c:\Program Files\Java\jdk1.7.0\bin>jps -mlvV 11372 -Xms40m -Xmx384m -XX:MaxPermSize=256m 12876 sun.tools.jps.Jps -mlvV -Dapplication.home=c:\Program Files\Java\jdk1.7.0 -Xms8m

1381

23

Dienstprogramme für die Java-Umgebung

Die Optionen sind: Þ

-l listet den Paketnamen der Klasse mit dem main() auf.

Þ -m zeigt die an die main()-Methode übergebenen Argumente an. Þ –v und –V zeigen die an die JVM übergebenen Parameter an.

Mehr Details gibt es unter http://download.oracle.com/javase/7/docs/technotes/tools/share/ jps.html. Unter Java 6 zeigt jps bei Eclipse noch »org.eclipse.equinox.launcher_....jar« an, bei Java 7 ist die Ausgabe leider leer. jps selbst ist auch in der Liste. In dieser Sitzung ist die ID von Eclipse 11372. Die folgenden Beispiele nutzen die Eclipse-ID für zusätzliche Anfragen.

23.2.2 jstat Mit jstat, dem Java Virtual Machine Statistics Monitoring Tool, ist es möglich, PerformanceStatistiken zu erfragen: c:\Program Files\Java\jdk1.7.0\bin>jstat -gcutil 11372 S0

S1

E

0,00

0,00

0,41

O

P

46,19

YGC

99,43

42

YGCT 0,575

FGC 278

FGCT

GCT

56,867

57,442

Die Ausgaben zeigen zum Beispiel mit FGC die Anzahl der GC-Ereignisse an. Die ID 11372 erfahren wir vom Tool jps. Die Seite http://download.oracle.com/javase/7/docs/technotes/tools/ share/jstat.html schlüsselt die Abkürzungen auf.

23.2.3

jmap

Das Memory-Map-Tool namens jmap zeigt eine Liste mit der Anzahl der Exemplare von JavaObjekten und zeigt auch, wie viel Hauptspeicher sie verbrauchen. Für Eclipse ist die Anzahl der Objekte sehr groß, sodass die Ausgabe hier gekürzt ist: c:\Program Files\Java\jdk1.7.0\bin>jmap -histo 11372 num

#instances

#bytes

class name

---------------------------------------------1:

103936

16083192

2:

98630

9517600

[C

3:

103936

8327696

1382

23.2

4:

10042

6929984

5:

10042

4533928

6:

8560

4067552

7:

85434

3901168

[B

8:

76434

1834416

java.lang.String

Monitoringprogramme vom JDK

9:

64731

1553544

java.util.HashMap$Entry

10:

32035

1416784

[I

11:

10953

1292104

java.lang.Class

4409:

1

8

org.eclipse.jdt.internal.debug.ui....

4410:

1

8

org.eclipse.jdt.ui.actions.Organize... org.eclipse.jdt.internal.corext.refactoring....

...

4411:

1

8

Total

818672

68628952

Ein paar Details mehr sind unter http://download.oracle.com/javase/7/docs/technotes/tools/ share/jmap.html zu finden.

23.2.4 jstack Das Stack-Trace-Programm jstack zeigt laufende Threads an, zusammen mit Informationen über den durch Monitore erzwungenen Wartezustand. Ein Ausschnitt für die Eclipse-Threads: c:\Program Files\Java\jdk1.7.0\bin>jstack

11372

2011-08-12 15:51:37 Full thread dump Java HotSpot(TM) Client VM (21.0-b17 mixed mode, sharing): "org.eclipse.jdt.internal.ui.text.JavaReconciler" daemon prio=2 tid=0x06214000 nid=0x30c4 in Object.wait() [0x069df000] java.lang.Thread.State: TIMED_WAITING (on object monitor)

23 at java.lang.Object.wait(Native Method) – waiting on (a org.eclipse.jface.text.reconciler.DirtyRegionQueue) at ... "Worker-28" prio=6 tid=0x06211400 nid=0xad0 in Object.wait() [0x0711f000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) – waiting on (a org.eclipse.core.internal.jobs.WorkerPool) ...

1383

23

Dienstprogramme für die Java-Umgebung

at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50) ... "[ThreadPool Manager] – Idle Thread" daemon prio=6 tid=0x06211c00 nid=0x295c in

ð

Object.wait() [0x08d7f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) ... ... "Service Thread" daemon prio=6 tid=0x01f52000 nid=0x2d78 runnable [0x00000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread0" daemon prio=10 tid=0x01f36c00 nid=0x2b38 waiting on condition

ð

[0x00000000] java.lang.Thread.State: RUNNABLE "Attach Listener" daemon prio=10 tid=0x01f35800 nid=0x1788 waiting on condition

ð

[0x00000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" daemon prio=10 tid=0x01f32400 nid=0x2ef8 runnable [0x00000000] java.lang.Thread.State: RUNNABLE "Finalizer" daemon prio=8 tid=0x01f2a400 nid=0x2004 in Object.wait() [0x044df000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) – waiting on (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(Unknown Source) – locked (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(Unknown Source) at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source) "Reference Handler" daemon prio=10 tid=0x01f25000 nid=0x2ca8 in Object.wait() [0x043df000] java.lang.Thread.State: WAITING (on object monitor)

1384

23.2

Monitoringprogramme vom JDK

at java.lang.Object.wait(Native Method) – waiting on (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source) – locked (a java.lang.ref.Reference$Lock) "main" prio=6 tid=0x01e19c00 nid=0x2b34 runnable [0x0012f000] java.lang.Thread.State: RUNNABLE at org.eclipse.swt.internal.win32.OS.WaitMessage(Native Method) at org.eclipse.swt.widgets.Display.sleep(Display.java:4652) ... at org.eclipse.equinox.launcher.Main.run(Main.java:1410) "VM Thread" prio=10 tid=0x01f23c00 nid=0x28ec runnable "VM Periodic Task Thread" prio=10 tid=0x01f5c000 nid=0x2898 waiting on condition JNI global references: 373

Zum Stack-Strace der Threads gibt es weitere Informationen unter http://download.oracle.com/javase/7/docs/technotes/tools/share/jstack.html.

23.2.5

VisualVM

VisualVM ist eine grafische Oberfläche mit einfachen Profiling-Möglichkeiten und bettet Kommandozeilenwerkzeuge wie jstack ein. VisualVM ist Teil von Java ab dem JDK 6, aber eine aktuelle Version findet sich immer unter https://visualvm.dev.java.net/. Im JDK befindet es sich wie die anderen Tools im bin-Verzeichnis und hört auf den Namen jvisualvm. Wir wollen die mitgelieferte Version nutzen. Beim ersten Mal müssen wir eine Kalibrierung starten, doch dann öffnet sich schon die grafische Oberfläche. Wählen wir links im Baum Local 폷 VisualVM aus, so schauen wir uns die Zustände, etwa den Speicherbedarf und die Thread-Auslastung des Programms VisualVM selbst an (siehe Abbildung 23.1).

Durch den Speicher wühlen: Heap-Dump Visual VM bietet die großartige Möglichkeit, sich während der Laufzeit zu einem Programm zu verbinden und über die Objektverweise zu navigieren. Unser Beispiel soll ein kleines Programm HeapUser sein, von dem wir später die vier Objektvariablen untersuchen wollen:

1385

23

23

Dienstprogramme für die Java-Umgebung

Abbildung 23.1: Screenshot der VisualVM Listing 23.1: HeapUser.java import java.util.*; public class HeapUser { String string = "Hallo Welt"; Date

date

= new Date();

ArrayList list = new ArrayList( Arrays.asList( string, date.toString() ) ); HeapUser heapUser; public static void main( String[] args ) { HeapUser h = new HeapUser(); h.heapUser = h; new Scanner( System.in ).next(); System.out.println( h.string ); } }

1386

23.2

Monitoringprogramme vom JDK

Starten wir das Programm und VisualVM läuft noch im Hintergrund, so erkennt VisualVM automatisch das gestartete Program und aktualisiert die Baumansicht unter local. Im Kontextmenü lässt sich über HeapUser der Heap-Dump erfragen.

Abbildung 23.2: Heap-Dump anzeigen

Nach dem Aktivieren des Schalters Classes sind alle geladenen Klassen aufgeführt und es ist zu sehen, wie viele Exemplare es von den Klassen gibt.

23

Abbildung 23.3: Anzeige der geladenen Klassen mit Statistik

1387

23

Dienstprogramme für die Java-Umgebung

Unten gibt es ein Suchfeld, in dem wir »HeapUser« eintragen. Es bleibt eine Klasse in der Liste.

Abbildung 23.4: Suchen nach »HeapUser«

Im Kontextmenü lässt sich nun Show in Instances View aufrufen.

Abbildung 23.5: Exemplare vom Typ »HeapUser« anfordern

Die folgende Ansicht bildet den Ausgangspunkt für exploratives Arbeiten.

1388

23.2

Monitoringprogramme vom JDK

Abbildung 23.6: Attribute und Belegungen einsehen

Links ist die Instanz abgebildet, die wir untersuchen. Das ist HeapUser, von dem es genau ein Exemplar gibt (#1). Rechts gibt es zwei Einteilungen. In der oberen Einteilung können wir die Objekteigenschaften vom links ausgewählten Objekt sehen und durch die Baumansicht tiefer reinzoomen. So enthält this, also das ausgewählte Objekt, die Variablen heapUser, list, date und string. An den auf sich selbst verweisenden Pfeilen an heapUser lässt sich – die Symbole werden in einer Art Statusleiste kurz erklärt – erkennen, dass die Variable heapUser das eigene Objekt referenziert. Falten wir list auf, so sehen wir die Objektvariablen der ArrayList-Instanz im Baum, und unter anderem lässt sich die size ablesen, also die Anzahl der Elemente in der Liste. elementData wiederum ist ein Knoten, der sich auffalten lässt, er repräsentiert das interne Feld – die eckigen Klammern deuten den Typ »Feld« an – der ArrayList. Wird er ausgefaltet, gelangen wir zu den beiden Strings. Im unteren Bereich der Einteilung, bei References, ist abzulesen, wer das selektierte Objekt referenziert. Es gibt zwei Stellen, an denen das untersuchte Objekt HeapUser referenziert wird: Einmal über die lokale Variable in der main-Methode, und einmal über die Objektvariable.

1389

23

23

Dienstprogramme für die Java-Umgebung

Profiling von Java-Applikationen Ein Profiler zeigt an, an welchen Stellen ein Programm Prozessorzeit verbraucht. Auf der Webseite https://visualvm.dev.java.net/profiler.html stellt Oracle eine Dokumentation bereit, wie VisualVM als Profiler genutzt wird.

23.3

Programmieren mit der Tools-API

Tools wie der Java-Compiler javac, aber auch jarsigner, javadoc, apt, xjc, javap und andere Kommandozeilenwerkzeuge, sind rein in Java geschrieben. Einige Tools bieten Entwicklern eine API, sodass sie erweitert und von Programmen angesprochen werden können. Das ist interessant für Tool-Anbieter, die zum Beispiel den Compiler erweitern oder die Ausgabe vom JavaDoc-Tool anpassen möchten. Alle in Java geschriebenen Programme befinden sich in einem Extra-Java-Archiv mit dem Namens tools.jar, das sich im lib-Verzeichnis des JDK befindet. Die normalen ausführbaren Programme wie javadoc oder javah haben (unter Windows) alle die gleiche Größe um 15 KiB, da sie nichts anderes machen, als die Java-Laufzeitumgebung mit tools.jar im Klassenpfad aufzurufen und dann an die entsprechende main()-Methode des Programms weiterzuleiten. Statt javadoc aufzurufen, kommt com.sun.tools.javadoc.Main.main() zum gleichen Ziel. Oracle definiert nicht für jedes Tool eine API. So ist es zum Beispiel nicht vorgesehen, in den Erzeugungsprozess von javah einzugreifen. Wohl ist es aber möglich, eigene Doclets zu schreiben – Doclets sind kleine Programme, die aus der Java-API-Dokumentation zum Beispiel verlinkte HTML-Dokumente aufbauen.

23.3.1

Eigene Doclets

Standardmäßig sind die Programme – und die API der Programme – nicht im Klassenpfad eingebunden. Um eigene Doclets zu schreiben, muss zunächst tools.jar in den Klassenpfad aufgenommen werden. Das macht neue Pakete wie com.sun.javadoc (Metadateien) und com.sun.tools.javadoc (Doclet-Tool selbst) verfügbar. Im folgenden Beispiel wollen wir ein kleines Doclet schreiben, das Klassen, Methoden und Konstruktoren ausgibt, die das Tag @since 1.7 tragen. So lässt sich leicht ermitteln, was in der Version Java 7 alles hinzugekommen ist. Doclets werden normalerweise von der Kommandozeile aufgerufen und dem javadoc-Tool übergeben. Unser Programm vereinfacht das, indem es direkt das Tool über Java mit dem passenden Parameter aufruft.

1390

23.3

Programmieren mit der Tools-API

Listing 23.2: com/tutego/tools/javadoc/SinceJava7FinderDoclet.java package com.tutego.tools.javadoc; import java.io.*; import java.util.Formatter; import com.sun.javadoc.*; import com.sun.tools.javadoc.Main; public class SinceJava7FinderDoclet { private final static Formatter formatter = new Formatter(); public static boolean start( RootDoc root ) { for ( ClassDoc clazz : root.classes() ) processClass( clazz ); return true; } private static void processClass( ClassDoc clazz ) { for ( Tag tag : clazz.tags( "since" ) ) if ( "1.7".equals( tag.text() ) ) formatter.format( "Neuer Typ %s%n", clazz ); for ( MethodDoc method : clazz.methods() ) for ( Tag tag : method.tags( "since" ) ) if ( "1.7".equals( tag.text() ) )

23

formatter.format( "Neue Methode %s%n", method ); for ( ConstructorDoc constructor : clazz.constructors() ) for ( Tag tag : constructor.tags( "since" ) ) if ( "1.7".equals( tag.text() ) ) formatter.format( "Neuer Konstruktor %s%n", constructor ); for ( FieldDoc field : clazz.fields() ) for ( Tag tag : field.tags( "since" ) )

1391

23

Dienstprogramme für die Java-Umgebung

if ( "1.7".equals( tag.text() ) ) formatter.format( "Neues Attribut %s%n", field ); } public static void main( String[] args ) { PrintStream err = System.err, out = System.out; System.setErr( new PrintStream( new OutputStream() { @Override public void write( int b ) { } } ) ); System.setOut( System.err ); String[] params = { "-quiet", // ignored!? "-doclet", SinceJava7FinderDoclet.class.getName(), "-sourcepath", "C:/Program Files/Java/jdk1.7.0/src/", //

"java.lang" "-subpackages", "java:javax" }; Main.execute( params ); System.setErr( err ); System.setOut( out ); System.out.println( formatter ); }

}

Unsere main()-Methode ruft das JDK-Doclet-Programm über Main.execute() auf und übergibt die eigene Doclet-Klasse per Parameter – die Argumente von execute() erinnern an die Kommandozeilenparameter. Das Doclet-Hauptprogramm wiederum ruft unsere start(RootDoc root)-Methode auf – das Gleiche würde auch passieren, wenn das Doclet von außen über javadoc aufgerufen würde. start() läuft über alle ermittelten Typen und übergibt zum Abarbeiten der Innereien die Verantwortung an processClass(). Die Metadaten kommen dabei über diverse XXXDoc-Typen.

1392

23.4

Ant

23.4 Ant Die Arbeit eines Softwareentwicklers ist nicht immer so spannend, wie es einem die Fantasie vorgaukelt. Der Alltag besteht aus Compilieren, dem Erstellen von Dokumentationen, dem Aktualisieren von Webseiten, dem Ausliefern von Archiven und Ähnlichem. Der schlaue Zeitgenosse schreibt sich für diese Aufgaben Skripte. Unter Unix wurde für die Sammlung dieser Skripte ein sogenanntes make-Tool benutzt. Eine besondere Aufgabe von make war es, Zusammenhänge zwischen verschiedenen Quellcodedateien zu erkennen und dann die benötigten neu zu übersetzen. Wenn sich zum Beispiel eine Header-Datei ändert, muss auch die C-Datei, die diese Header-Datei einbindet, neu übersetzt werden. Unter Java können wir mit diesem make-Tool nicht so viel anfangen, doch Aufgaben wie das Erstellen von Archiven, die Neuübersetzung aller Quellen und das Erzeugen von Dokumentationen bleiben. Für diese Aufgaben wurde von der Apache-Gruppe ein Tool mit dem Namen Ant (http://ant.apache.org/) entwickelt. Der Name selbst ist ein Akronym aus Another Neat Tool. Ein weiteres populäres Produkt der Apache Software Foundation ist Maven (http:// maven.apache.org/). Es geht einen Schritt weiter und bietet standardisiertes Erstellen von Anwendungen zusammen mit der jeweiligen Dokumentation und Tests für den gesamten Build-Prozess.

23.4.1

Bezug und Installation von Ant

Wir erhalten ein Archiv von Ant auf den Seiten der Indianer-Gruppe, genauer gesagt unter http://ant.apache.org/bindownload.cgi. Nach dem Entpacken sollten wir unsere Umgebungsvariable (PATH) so erweitern, dass sie auf das bin/-Verzeichnis von Ant zeigt. Wenn wir jetzt ant auf der Kommandozeile aufrufen, sollte folgende Ausgabe zu sehen sein: $ ant Buildfile: build.xml does not exist! Build failed

23 Die Installation liefert alle benötigten Bibliotheken gleich mit.

Beispielklasse, die Ant übersetzen soll Wir begnügen uns am Anfang mit einer einfachen Java-Klasse, die Ant verwalten soll: Listing 23.3: ant/AntDemo.java package ant; public class AntDemo

1393

23

Dienstprogramme für die Java-Umgebung

{ public static void main( String[] args ) { System.out.println( "Ant is beautiful." ); } }

Dieses Mal soll die Quellcodedatei nicht von Hand übersetzt werden, sondern ein Ant-Skript soll dies erledigen.

23.4.2 Das Build-Skript build.xml Eine XML-Datei beschreibt, welche Schritte Ant auszuführen hat. Ant nennt die Dateien BuildFiles, und eine solche Datei heißt oft build.xml. Wir wollen sie in das gleiche Verzeichnis stellen, in dem auch unsere Java-Quellcodedatei steht: Listing 23.4: build.xml





Eine DTD-Referenzierung ist für die XML-Datei nicht zwingend nötig, da der Parser die Datei nicht validierend abarbeitet. Der Eintrag definiert das Projekt unter dem Namen Insel. Das Attribut default definiert weiterhin, dass der einfache Aufruf von Ant automatisch das Ziel build aufrufen soll.

23.4.3 Build den Build Im Inneren des Eintrags folgen -Einträge. Wir definieren das Ziel build, das den Compiler starten soll. Das übernimmt der Task , der alle Dateien compiliert, die sich im aktuellen Verzeichnis befinden. Neben stellt Ant eine große Zahl zusätzlicher Tasks bereit. Wechseln wir auf der Kommandozeile in das Verzeichnis mit den Dateien, dann reicht es, Ant aufzurufen, und der Konstruktionsprozess beginnt. Ant sucht selbstständig nach der Datei build.xml. Folgendes erscheint:

1394

23.4

Ant

$ ant Buildfile: build.xml build: [javac] Compiling 1 source file BUILD SUCCESSFUL

Die Option -verbose gibt zusätzliche Informationen über den Entstehungsprozess aus. Die Datei build.xml lässt sich vielfältig anpassen. Nehmen wir uns den Eintrag noch einmal vor, und erweitern wir ihn zu:

Das weist Ant an, während der Übersetzung zu optimieren und Debug-Informationen mit aufzunehmen. Das Attribut includes befiehlt, nicht nur Quellcodedateien des aktuellen Verzeichnisses zu übersetzen, sondern auch alle Dateien aller Unterverzeichnisse. Die Notation **/ ist eine Vereinfachung, die für alle Unterverzeichnisse steht. An ihrer Stelle ist auch eine Aufzählung der Verzeichnisse zulässig.

23.4.4 Properties Eine gute Idee ist es, sich von den konkreten Pfaden im -Element zu lösen, denn Ant erlaubt es, Eigenschaften zu definieren, die Platzhaltern oder Makros ähneln. Um Programme aus einem Verzeichnis (nennen wir es src) in ein Zielverzeichnis (nennen wir es build) zu übersetzen, schreiben wir:



Bisher haben wir uns mit der Übersetzung beschäftigt. Dennoch wissen wir, dass wir auch noch andere target-Elemente verwenden können. Und obwohl die Benennung dieser Targets prinzipiell willkürlich ist, gibt es eine vorgeschlagene Namenskonvention. Eintrag

Bedeutung

init

Erstellt Verzeichnisse und Initialisierungsdateien.

build

Inkrementeller Aufbau

test

Ablaufen der Tests mit JUnit

Tabelle 23.1: Namenskonvention für Targets

1395

23

23

Dienstprogramme für die Java-Umgebung

Eintrag

Bedeutung

clean

Ausgabeverzeichnisse löschen

deploy

*.jar, *.war und sonstige Archive erstellen

publish

Veröffentlichen der Ergebnisse

fetch

Bezieht Quellcodedateien aus der Versionsverwaltung.

docs, javadocs

Erstellt die Dokumentation.

all

Abfolge von clean, fetch, build, test, docs, deploy

main

Erstellt das Projekt, in der Regel build oder build, test.

Tabelle 23.1: Namenskonvention für Targets (Forts.)

Der Eintrag init soll eine initiale Verzeichnisstruktur aufbauen. Nehmen wir an, der Compiler soll in unser oben genanntes Verzeichnis build übersetzen. Dann muss dieses Verzeichnis natürlich existieren. Das Anlegen des Verzeichnisses kann in init geschehen:



Um die erstellten Verzeichnisse in einem Target clean zu löschen, wollen wir eine Property dist für das Distributionsverzeichnis hinzunehmen:



23.4.5 Externe und vordefinierte Properties Falls sich das Versionsrad eine Nummer weiter dreht, ist es ungünstig, wenn Änderungen an der XML-Datei vorzunehmen sind. Eine gute Lösung für das Problem besteht darin, eine externe Datei anzugeben, die die Version definiert. Das kann so aussehen: Listing 23.5: version.properties version=0.7

Jetzt fehlt nur noch der Bezug zu dieser Eigenschaften-Datei in der XML-Datei. Bisher kennen wir zwar einen Eintrag property name und value, aber eine zweite Variante mit dem Attribut file bindet eine Datei ein, die Schlüssel-Werte-Paare wie bei unserer version.properties beschreibt.

1396

23.4

Ant

Jetzt lässt sich auf die Variable version ganz normal zugreifen, um zum Beispiel eine zweite Eigenschaft einzuführen:

version ist unsere Variable, und ${version} setzt den Inhalt der Variablen ein. Daneben gibt es

aber noch einige Standard-Properties: Property

Bedeutung

Basedir

absoluter Pfad zum Basisverzeichnis des Projekts; in basedir gesetzt

ant.file

absoluter Pfad der Build-Datei

ant.version

Version von Ant

ant.project.name

Name des Projekts, wie in gesetzt

ant.java.version

JVM-Version, wie von Ant entdeckt, etwa »1.6«

Tabelle 23.2: Standard-Properties

23.4.6 Weitere Ant-Tasks Ant kann Programme mit automatischen Tests mit JUnit überprüfen, Klassendateien zu einem Archiv zusammenbinden oder E-Mails verschicken. Die Archive lassen sich automatisch auf einen FTP-Server übertragen. Für Ant gibt es viele Tasks; http://ant.apache.org/ manual/tasksoverview.html gibt einen Überblick. Die wichtigsten Tasks sind: Ant-Task

Aufgabe

javac

Übersetzt mit Java-Compiler.

jar

Bündelt Dokumente in ein Java-Archiv.

manifest

Erzeugt eine Manifest-Datei.

signjar

Signiert ein Java-Archiv.

unjar

Packt Java-Archive aus.

javadoc

Erzeugt die Java-Dokumentation.

exec

Startet ein externes Programm.

copy

Kopiert Dateien.

mkdir

Legt ein Verzeichnis an.

move

Verschiebt Dateien.

23

Tabelle 23.3: Ausgewählte Ant-Tasks

1397

23

Dienstprogramme für die Java-Umgebung

Ant-Task

Aufgabe

echo

Schreibt Ausgaben auf die Konsole.

sql

Sendet SQL-Anweisungen zur Datenbank.

javah

Erzeugt C-Header Dateien für JNI.

junit

Arbeitet JUnit-Tests ab.

Tabelle 23.3: Ausgewählte Ant-Tasks (Forts.)

Neben Tasks spielen FileSets eine wichtige Rolle. Sie bilden Gruppen von Dateien. Die Gruppen werden nach bestimmen Kriterien gebildet, etwa nach der Dateiendung. Folgendes FileSet umfasst alle Java-Quellcodedateien, schließt aber alle Test-Dateien aus, die sich in Unterverzeichnissen ausgehend von der Angabe des Wurzelpfads über die Property src befinden:



Beispiel Das ClassFileSet (http://ant.apache.org/manual/OptionalTypes/classfileset.html) ist ein besonderes FileSet, das nicht einfach alle Dateien von Verzeichnissen auswählt, sondern durch Bytecode-Analyse herausfindet, welche Abhängigkeiten es wirklich gibt. So gelangen nur die Klassendateien in die Gruppe, die vom angegebenen Wurzelelement referenziert werden.

23.5

Disassembler, Decompiler und Obfuscator

Ein Disassembler ist ein Werkzeug, das den Bytecode und die Struktur einer Java-Klassendatei anzeigt. Ein Decompiler geht einen Schritt weiter und versucht aus Klassendateien wieder Quellcodedateien zu gewinnen, die, wenn sie später compiliert werden, wieder den gleichen Bytecode ergeben würden. Genau diese »leichte« Lesbarkeit von Bytecode oder eine Decompliation möchte ein anderes Werkzeug, der Obfuscator, erschweren. Das Ziel von Obfuscation ist das möglichst effektive Verschleiern aller Bytecodeinformationen, sodass Menschen den Spaß am Bytecode verlieren und Decompiler bei ihrer Rücktransformation sogar aus dem Tritt kommen. Das Eclipse-Plugin ByteCodeDeluxe unter http://www.idedeluxe.com/bytecode/ visualisiert die Struktur einer Bytecode-Datei.

1398

23.5

23.5.1

Disassembler, Decompiler und Obfuscator

Der Diassembler javap

Das JDK liefert im bin-Verzeichnis der Installation mit javap ein Werkzeug aus, das zwar nicht die Implementierung von Methoden hervorzaubert, aber immerhin die statische Struktur einer Klasse mit den Vererbungsbeziehungen, Variablen, Methoden, Parametern anzeigt. In der einfachsten Variante wird javap mit dem Klassennamen aufgerufen. Wir nehmen im Folgenden an, dass javap im Suchpfad steht und wir uns auf der Kommandozeile direkt im bin-Verzeichnis vom JDK befinden. Wir setzen zuerst den Klassenpfad und geben anschließend als Kommandozeilenargument für javap die Klasse Quadrat aus dem ersten Kapitel an, die disassembliert werden soll. $ javap -classpath Z:\Documents\Insel\programme\1_01_Intro Quadrat Compiled from "Quadrat.java" public class Quadrat extends java.lang.Object{ public Quadrat(); static int quadrat(int); static void ausgabe(int); public static void main(java.lang.String[]); }

Abzulesen sind auch Dinge, die der Compiler automatisch generiert und die im Bytecode stehen, die wir aber im Allgemeinen nicht schreiben würden, etwa der voll qualifizierte Klassenname java.lang.Object oder java.lang.String, die Vererbungsbeziehung zu Object oder der automatisch angelegte Standardkonstruktor. Das Tool javap erlaubt noch mehr Parameter, die die Option -help anzeigt. $ javap -help Usage: javap ... where options include:

23

-c

Disassemble the code

-classpath

Specify where to find user class files

-extdirs

Override location of installed extensions

-help

Print this usage message

-J

Pass directly to the runtime system

-l

Print line number and local variable tables

-public

Show only public classes and members

-protected

Show protected/public classes and members

-package

Show package/protected/public classes

1399

23

Dienstprogramme für die Java-Umgebung

and members (default) -private

Show all classes and members

-s

Print internal type signatures

-bootclasspath Override location of class files loaded by the bootstrap class loader -verbose

Print stack size, number of locals and args for methods If verifying, print reasons for failure

Hinweis Auch der Java-Compiler kann seit Java 7 mit dem neuen Schalter –Xprint ähnliche Informationen ausgeben: $ javac.exe -Xprint Quadrat.java public class Quadrat { public Quadrat(); static int quadrat(int n); static void ausgabe(int n); public static void main(java.lang.String[] args); }

Java-Bytecode am Beispiel Interessanter ist der Schalter -c, der den Bytecode der Methoden/Konstruktoren/Initialisierer anzeigt. Am Beispiel: $ javap -c -classpath Z:\Documents\Insel\programme\1_01_Intro Quadrat Compiled from "Quadrat.java" public class Quadrat extends java.lang.Object{

Steigen wir nicht chronologisch ein, sondern bei der statischen Methode quadrat(). Sie bekommt als Argument ein int und liefert es multipliziert mit sich selbst zurück: static int quadrat(int); Code: 0:

iload_0

1:

iload_0

2:

imul

3:

ireturn

1400

23.5

Disassembler, Decompiler und Obfuscator

Die Ausgabe macht die Stack-Natur des JVM sichtbar. Der Übergabeparameter n von quadrat(int n) steht auf Position 0 im Stack. Um das Quadrat zu bilden, wird der aktuelle Parameter zweimal mit iload_0 auf den Stapel gelegt und dann mit imul multipliziert. imul löscht die beiden Werte vom Stapel und ersetzt sie durch das Ergebnis der Multiplikation. ireturn liefert den obersten Stackwert als int zurück. Das Präfix »i« bei imul und ireturn zeigt, dass die Operationen auf Ganzzahlen durchgeführt werden. Andere Präfixe sind »b« für byte, »c« für char, »d« für double oder »a« für Objektreferenzen. Kommen wir zur statischen main-Methode: public static void main(java.lang.String[]); Code: 0:

iconst_4

1:

invokestatic

4:

return

#59; //Method ausgabe:(I)V

}

Im Rumpf der main()-Methode steht der Aufruf ausgabe(4), was im Bytecode dazu führt, dass mit iconst_4 der Wert 4 auf den Stack gelegt wird und dann invokestatic die Methode ausgabe() aufruft. Der Standardkonstruktor von Quadrat ruft lediglich den Standardkonstruktor der Oberklasse Object auf. public Quadrat(); Code: 0:

aload_0

1:

invokespecial

4:

return

#8; //Method java/lang/Object."":()V

Bei Konstruktoren und Objektmethoden ist automatisch an Position 0 im Stack die this-Referenz gesetzt, da die Aufrufer automatisch an Position 0 diese Referenz übergeben. (Bei statischen Methoden ist das nicht so.) Bei unserem Konstruktor Quadrat() setzt also aload_0 diese this-Referenz auf den Stack, damit der Konstruktor der Oberklasse, also Object aufgerufen werden kann. Konstrukturaufrufe werden im Bytecode mit invokespecial durchgeführt. Im Kern ist ein Konstruktor eine Methode mit dem speziellen Namen und der Rückgabe void. Die Angabe #8 ist ein Verweis auf eine interne Tabelle, doch der Java-Kommentar macht den Eintrag für uns lesbar. Am komplexesten ist die statische Methode ausgabe(). Im Original sieht sie so aus:

1401

23

23

Dienstprogramme für die Java-Umgebung

String s; int

i;

for ( i = 1; i