156 63 54MB
German Pages 1434 Year 2011
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