Verteilte Systeme Ein Tutorial über die durchgängige Anwendung

Werbung
Verteilte Systeme
Ein Tutorial über die durchgängige Anwendung
verschiedener Java-Technologien bei einer
gemeinsamen Ausgangsthematik
Dipl.-Inf. (FH) Florian Lutz
Version 3.3, April 2012
Hochschule Regensburg
Fakultät Informatik und Mathematik
Inhaltsverzeichnis
Inhaltsverzeichnis
1. Einführung ............................................................................................................ 1
1.1. Motivation ............................................................................................................................... 1
1.2. Aufbau des Tutorials .............................................................................................................. 1
1.3. Verwendete Software-Versionen ........................................................................................... 2
1.3.1. Plattform / System..................................................................................................... 2
1.3.2. Java........................................................................................................................... 2
1.3.3. Eclipse....................................................................................................................... 2
1.3.4. Frameworks / APIs.................................................................................................... 2
2. Ausgangsthematik Buchhaltung......................................................................... 3
2.1. Übersicht ................................................................................................................................ 3
2.1.1. Beschreibung ............................................................................................................ 3
2.1.2. Objektmodell ............................................................................................................. 4
2.2. Schnittstellen-Modell .............................................................................................................. 5
2.2.1. Konto-Schnittstelle .................................................................................................... 6
2.2.2. Kontotyp-Enumeration .............................................................................................. 7
2.2.3. Buchungs-Schnittstelle ............................................................................................. 7
2.2.4. Service-Schnittstelle ................................................................................................. 8
2.2.5. Proxy-Schnittstelle .................................................................................................... 9
2.2.6. Erweiterte Service-Schnittstelle ................................................................................ 9
2.3. Ausnahmen .......................................................................................................................... 10
2.3.1. Buchhaltungsfehler ................................................................................................. 10
2.3.2. Ein/Ausgabe-Fehler ................................................................................................ 10
2.3.3. Nicht implementierte Methoden .............................................................................. 11
2.3.4. Andere Ausnahmen ................................................................................................ 11
2.4. Modulares Konzept der Programmteile ............................................................................... 12
2.4.1. Client ....................................................................................................................... 12
2.4.1.1. Proxy-Instanzierung über Reflection ......................................................... 12
2.4.1.2. Laden einer Service-Klasse....................................................................... 13
2.4.2. Datenhaltung / Geschäftslogik................................................................................ 13
2.4.2.1. Daten und Persistenz ................................................................................ 13
2.4.2.2. Geschäftslogik ........................................................................................... 14
2.4.3. Transport / Middleware ........................................................................................... 14
2.4.3.1. Client.......................................................................................................... 14
2.4.3.2. Server ........................................................................................................ 14
2.4.4. Verkettung............................................................................................................... 15
-I-
Inhaltsverzeichnis
3. Benutzeroberfläche ............................................................................................ 17
3.1. Übersicht .............................................................................................................................. 17
3.2. Zugriff über eine Proxy-Klasse............................................................................................. 18
3.2.1. Laden und Verbinden eines Buchhaltungssystems................................................ 18
3.2.2. Schließen der Verbindung ...................................................................................... 20
3.3. Aufbau der Oberfläche ......................................................................................................... 21
3.3.1. Kontenliste .............................................................................................................. 21
3.3.1.1. Laden der Kontenobjekte .......................................................................... 21
3.3.1.2. Anlegen eines neuen Kontos..................................................................... 21
3.3.1.3. Ändern der Daten eines Kontos ................................................................ 22
3.3.1.4. Löschen eines Kontos ............................................................................... 22
3.3.2. Buchungsliste.......................................................................................................... 23
3.3.2.1. Laden der Buchungsobjekte...................................................................... 23
3.3.2.2. Erfassen einer neuen Buchung ................................................................. 23
3.3.2.3. Ändern der Daten einer Buchung .............................................................. 24
3.3.2.4. Löschen einer Buchung............................................................................. 24
3.3.3. Soll- und Haben-Buchungen je Konto .................................................................... 25
3.3.4. Saldo eines Kontos ................................................................................................. 25
3.4. Behandlung von Ausnahmen ............................................................................................... 26
3.4.1. Nachrichtenfenster.................................................................................................. 26
3.4.2. Statusleiste ............................................................................................................. 26
3.4.3. Direkte Einblendung................................................................................................ 27
3.5. Kapselung aller Aufrufe........................................................................................................ 28
3.5.1. Adapterschicht ........................................................................................................ 28
3.5.1.1. Swing-MVC-Pattern ................................................................................... 28
3.5.1.2. Zwischenspeicherung der Daten ............................................................... 29
3.5.2. Aufteilung in separate Vorgänge ............................................................................ 29
3.5.2.1. Vorgänge zum Zugriff auf die Buchhaltung ............................................... 30
3.5.2.2. Vorgänge zur Aktualisierung der Benutzeroberfläche............................... 31
3.5.3. Kontrolle der Vorgänge ........................................................................................... 32
3.5.3.1. Status-Benachrichtigung über Listener-Interface ...................................... 32
3.5.3.2. Auflistung der laufenden Vorgänge ........................................................... 32
3.5.3.3. Abbruch eines Vorgangs ........................................................................... 33
3.6. Threading ............................................................................................................................. 34
3.6.1. Zentraler Threading-Service ................................................................................... 34
3.6.1.1. AWT-Event-Dispatching-Thread................................................................ 34
3.6.1.2. Worker-Threads......................................................................................... 35
3.6.2. Threading-Modelle .................................................................................................. 35
3.6.2.1. Singlethreaded........................................................................................... 36
3.6.2.2. Synchron einreihen.................................................................................... 37
3.6.2.3. Asynchron mit zusätzlichem Thread.......................................................... 38
3.6.2.4. Multithreaded ............................................................................................. 38
3.6.3. Abbruch eines laufenden Vorgangs........................................................................ 39
3.7. Weitere Funktionen .............................................................................................................. 41
3.7.1. Testfunktionen ........................................................................................................ 41
3.7.1.1. Daten erzeugen und aufräumen................................................................ 41
3.7.1.2. Test der Funktionalität ............................................................................... 41
3.7.1.3. Test der Geschwindigkeit .......................................................................... 42
3.7.1.4. Test auf Threadsicherheit.......................................................................... 42
3.7.2. Logging ................................................................................................................... 43
3.7.2.1. Logger-Klasse............................................................................................ 43
3.7.2.2. Auswertung des Stacktrace....................................................................... 44
3.7.2.3. Hierarchische Methodenaufrufe ................................................................ 44
3.7.2.4. Unterscheidung nach Thread .................................................................... 45
3.7.3. Look & Feel ............................................................................................................. 45
3.7.4. Hilfe ......................................................................................................................... 47
3.8. Suchen und Laden der Proxy-Klasse .................................................................................. 48
3.8.1. Suchverzeichnis...................................................................................................... 48
3.8.2. Klassenlader für den Suchvorgang......................................................................... 48
3.8.3. Listener-Schnittstelle für den Klassenlader ............................................................ 49
- II -
Inhaltsverzeichnis
3.8.4. Eigenschaften gefundener Klassen ........................................................................ 50
3.8.5. Laden gefundener Klassen ..................................................................................... 51
3.8.6. Anmerkung.............................................................................................................. 51
3.9. Verwendung der Buchhaltung in Eclipse ............................................................................. 52
3.9.1. Neues Projekt anlegen............................................................................................ 52
3.9.2. Importieren des Package mit der Buchhaltungsoberfläche .................................... 54
3.9.3. Start der Anwendung .............................................................................................. 58
3.9.4. Erstellung eines eigenen Moduls für die Buchhaltung............................................ 59
- III -
Inhaltsverzeichnis
4. Datenhaltung....................................................................................................... 63
4.1. Übersicht .............................................................................................................................. 63
4.1.1. Datenspeicherung................................................................................................... 63
4.1.2. Geschäftslogik ........................................................................................................ 63
4.2. Einfache lokale Datenhaltung .............................................................................................. 64
4.2.1. Allgemeines ............................................................................................................ 64
4.2.2. Daten in HashMaps ................................................................................................ 64
4.2.2.1. Konten und Buchungen ............................................................................. 64
4.2.2.2. Soll- und Haben-Buchungen ..................................................................... 64
4.2.2.3. Bidirektionaler Bezug................................................................................. 64
4.2.3. Implementierung der Schnittstellen ........................................................................ 65
4.2.3.1. Service-Klasse........................................................................................... 65
4.2.3.2. Konto-Klasse ............................................................................................. 67
4.2.3.3. Buchungs-Klasse....................................................................................... 69
4.2.3.4. Proxy-Klasse.............................................................................................. 70
4.2.4. Fazit ........................................................................................................................ 70
4.3. Java-Serialisierung............................................................................................................... 71
4.3.1. Allgemeines ............................................................................................................ 71
4.3.2. Serialisierbare Klassen ........................................................................................... 71
4.3.2.1. Konto-Klasse ............................................................................................. 71
4.3.2.2. Buchungs-Klasse....................................................................................... 71
4.3.3. De-/Serialisierungs-Vorgang................................................................................... 72
4.3.3.1. Service-Klasse........................................................................................... 72
4.3.3.2. Proxy-Klasse.............................................................................................. 73
4.3.4. Fazit ........................................................................................................................ 74
4.4. XML-Serialisierung über JAXB............................................................................................. 75
4.4.1. Allgemeines ............................................................................................................ 75
4.4.2. Annotations ............................................................................................................. 75
4.4.2.1. Package-Annotations ................................................................................ 75
4.4.2.2. Klassen-Annotations.................................................................................. 76
4.4.2.3. Enum-Annotations ..................................................................................... 77
4.4.2.4. Field-Annotations....................................................................................... 77
4.4.2.5. Objekt-Fabrik-Annotations......................................................................... 79
4.4.2.6. Adapter-Annotations .................................................................................. 79
4.4.3. Anwendung der Annotations bei den Buchhaltungs-Klassen................................. 79
4.4.3.1. Service-Klasse........................................................................................... 79
4.4.3.2. Buchungs-Klasse....................................................................................... 80
4.4.3.3. Callback-Methoden.................................................................................... 81
4.4.3.4. Konto-Klasse ............................................................................................. 82
4.4.4. Erzeugung des XSD-Schemas aus den annotierten Klassen ................................ 82
4.4.4.1. Generierung durch einen Ant-Vorgang ..................................................... 82
4.4.4.2. Das Schema zu den HashMaps ................................................................ 83
4.4.4.3. Resultierende XML-Datei .......................................................................... 84
4.4.4.4. Probleme ................................................................................................... 84
4.4.5. Implementierung der Klassen nach einem vorgegebenen Schema ....................... 85
4.4.5.1. Service-Klasse........................................................................................... 85
4.4.5.2. Konto-Klasse ............................................................................................. 86
4.4.5.3. Buchungs-Klasse....................................................................................... 86
4.4.5.4. Schema und Daten .................................................................................... 87
4.4.6. Proxy- und Utility-Klasse......................................................................................... 89
4.4.6.1. Proxy-Klasse.............................................................................................. 89
4.4.6.2. Utility-Klasse .............................................................................................. 90
4.4.6.3. Speichern der Objekte............................................................................... 90
4.4.6.4. Laden der Objekte ..................................................................................... 91
4.4.7. Fazit ........................................................................................................................ 91
4.5. Direkter SQL-Datenbankzugriff mit JDBC............................................................................ 92
4.5.1. Allgemeines ............................................................................................................ 92
4.5.2. JDBC-Datenbankanbindung ................................................................................... 92
4.5.2.1. Verbindung aufbauen ................................................................................ 92
4.5.2.2. Einzelne Werte .......................................................................................... 93
- IV -
Inhaltsverzeichnis
4.5.2.3. Auflistungen ............................................................................................... 94
4.5.2.4. Anweisungen ............................................................................................. 94
4.5.2.5. PreparedStatements.................................................................................. 95
4.5.3. SQL-Anweisungen .................................................................................................. 95
4.5.3.1. Datenbank-Schema ................................................................................... 95
4.5.3.2. Service-Klasse........................................................................................... 96
4.5.3.3. Konto-Klasse ............................................................................................. 97
4.5.3.4. Buchungs-Klasse....................................................................................... 97
4.5.4. Fazit ........................................................................................................................ 98
4.6. Hibernate.............................................................................................................................. 99
4.6.1. Allgemeines ............................................................................................................ 99
4.6.1.1. Persistenz von Objekten.......................................................................... 100
4.6.1.2. Arbeitsweise mit POJOs.......................................................................... 100
4.6.1.3. OR-Mapping ............................................................................................ 101
4.6.2. Annotations ........................................................................................................... 101
4.6.2.1. Entitäten................................................................................................... 101
4.6.2.2. Identifikatoren - Schlüssel ....................................................................... 101
4.6.2.3. Einfache Daten ........................................................................................ 102
4.6.2.4. Beziehungen............................................................................................ 103
4.6.2.5. Kollektionen ............................................................................................. 104
4.6.2.6. Kaskadierung........................................................................................... 106
4.6.3. Installation von Hibernate ..................................................................................... 106
4.6.3.1. Hibernate-Einstellungen .......................................................................... 106
4.6.3.2. Datenbankanbindung .............................................................................. 107
4.6.3.3. Weitere Einstellungen.............................................................................. 107
4.6.4. Zugriff auf Hibernate ............................................................................................. 108
4.6.4.1. Erzeugen der Konfiguration..................................................................... 108
4.6.4.2. Sitzungen................................................................................................. 108
4.6.4.3. Transaktionen .......................................................................................... 109
4.6.4.4. Fehlerbehandlung.................................................................................... 110
4.6.4.5. Transaktions-Zeitlimit .............................................................................. 110
4.6.4.6. Versionskontrolle ..................................................................................... 111
4.6.4.7. Sperren .................................................................................................... 112
4.6.5. Verwendung der Objekte ...................................................................................... 112
4.6.5.1. Zustände.................................................................................................. 112
4.6.5.2. Automatische Erkennung des Objektzustandes...................................... 113
4.6.5.3. Laden der Objekte ................................................................................... 114
4.6.5.4. Abfragen .................................................................................................. 115
4.6.5.5. Modifikationen an Objekten ..................................................................... 115
4.6.5.6. Löschen persistenter Objekte.................................................................. 115
4.6.6. Buchhaltung mit einer Singleton-Service-Klasse.................................................. 116
4.6.6.1. Service-Klasse......................................................................................... 116
4.6.6.2. Konto-Klasse ........................................................................................... 118
4.6.6.3. Buchungs-Klasse..................................................................................... 119
4.6.6.4. Proxy-Klasse............................................................................................ 119
4.6.7. Buchhaltung mit einer Entity-Service-Klasse........................................................ 120
4.6.7.1. Service-Klasse......................................................................................... 120
4.6.7.2. Konto-Klasse ........................................................................................... 121
4.6.7.3. Buchungs-Klasse..................................................................................... 121
4.6.7.4. Proxy-Klasse............................................................................................ 122
4.6.7.5. Fazit ......................................................................................................... 123
-V-
Inhaltsverzeichnis
5. Transport............................................................................................................124
5.1. Übersicht ............................................................................................................................ 124
5.1.1. Prinzipieller Ablauf ................................................................................................ 124
5.1.2. Erstellung mit Eclipse............................................................................................ 125
5.2. Eigener Transport über Sockets ........................................................................................ 126
5.2.1. Allgemeines .......................................................................................................... 126
5.2.2. Anbindung an die Datenhaltung ........................................................................... 126
5.2.2.1. SocketServer ........................................................................................... 126
5.2.2.2. Parallele Server-Instanzen ...................................................................... 127
5.2.3. Aufbau und Nutzung der Verbindung ................................................................... 127
5.2.3.1. Socket-Proxy-Klasse ............................................................................... 127
5.2.3.2. Verarbeitung auf dem Server .................................................................. 128
5.2.4. Definition eines eigenes Protokolls....................................................................... 129
5.2.4.1. Anfrage .................................................................................................... 130
5.2.4.2. Anfrage-Klassen ...................................................................................... 130
5.2.4.3. Antwort..................................................................................................... 132
5.2.4.4. Antwort-Klassen....................................................................................... 133
5.2.5. Un-/Marshalling der Objekte ................................................................................. 135
5.2.5.1. Die Klasse StringUnMarshaller................................................................ 135
5.2.5.2. Verarbeitung definierter Superklassen .................................................... 135
5.2.5.3. Aufruf der Un-/Marshal-Methoden ........................................................... 136
5.2.5.4. Einfache Objekte ..................................................................................... 136
5.2.5.5. Kollektionen ............................................................................................. 137
5.2.5.6. Ausnahmen.............................................................................................. 137
5.2.6. Entfernte Objekte .................................................................................................. 138
5.2.6.1. Aufruf der Methoden über Reflection ...................................................... 138
5.2.6.2. Übertragung der entfernten Objekte........................................................ 138
5.2.6.3. Lokale Stellvertreter................................................................................. 140
5.2.6.4. Anfrage-Methoden ................................................................................... 140
5.2.7. Fazit ...................................................................................................................... 142
5.3. Remote Method Invocation (RMI) ...................................................................................... 143
5.3.1. Allgemeines .......................................................................................................... 143
5.3.2. Entfernte Objekte .................................................................................................. 144
5.3.2.1. Remote-Interface ..................................................................................... 144
5.3.2.2. Remote-Object......................................................................................... 144
5.3.3. Anbindung an die Datenhaltung ........................................................................... 144
5.3.3.1. Proxy-Instanzierung................................................................................. 144
5.3.3.2. Weitergabe der Aufrufe ........................................................................... 145
5.3.4. Server - RMI-Registry ........................................................................................... 145
5.3.4.1. Registry starten........................................................................................ 145
5.3.4.2. Registry einbinden ................................................................................... 145
5.3.4.3. Objekt registrieren ................................................................................... 146
5.3.4.4. Der vollständige Server ........................................................................... 146
5.3.5. Entfernte Buchhaltungsobjekte............................................................................. 147
5.3.5.1. Service-Klasse......................................................................................... 147
5.3.5.2. Konto-Klasse ........................................................................................... 148
5.3.5.3. Buchungs-Klasse..................................................................................... 149
5.3.6. Aufruf der entfernten Buchhaltung........................................................................ 149
5.3.6.1. Anfrage an den Namensdienst ................................................................ 150
5.3.6.2. RMI-Proxy-Klasse.................................................................................... 150
5.3.7. Codebase.............................................................................................................. 151
5.3.8. Fazit ...................................................................................................................... 151
5.4. CORBA............................................................................................................................... 152
5.4.1. Allgemeines .......................................................................................................... 152
5.4.1.1. Object Request Broker (ORB) ................................................................. 152
5.4.1.2. Interoperable Object Reference (IOR)..................................................... 152
5.4.1.3. Portable Object Adapter (POA) ............................................................... 152
5.4.1.4. Tie-Mechanismus .................................................................................... 153
5.4.1.5. CORBA-Dienste....................................................................................... 153
5.4.1.6. Implementierungen .................................................................................. 153
- VI -
Inhaltsverzeichnis
5.4.2. Interface Definition Language............................................................................... 154
5.4.2.1. Buchhaltungs-IDL .................................................................................... 154
5.4.2.2. Zuordnung der IDL-Elemente zu Java-Typen ......................................... 156
5.4.2.3. Java-Artefakte generieren ....................................................................... 156
5.4.2.4. Erzeugte Klassen und Schnittstellen ....................................................... 157
5.4.3. Verwendung der erzeugten Klassen auf der Serverseite ..................................... 158
5.4.3.1. CORBA-Server ........................................................................................ 158
5.4.3.2. Konto-Klasse ........................................................................................... 161
5.4.3.3. Buchungs-Klasse..................................................................................... 162
5.4.3.4. Der ORB-Daemon ................................................................................... 162
5.4.4. Verwendung der erzeugten Klassen auf der Clientseite ...................................... 163
5.4.4.1. Service-Klasse......................................................................................... 163
5.4.4.2. Konto-Klasse ........................................................................................... 164
5.4.4.3. Buchungs-Klasse..................................................................................... 165
5.4.4.4. Proxy-Klasse............................................................................................ 166
5.4.5. JACORB................................................................................................................ 167
5.4.5.1. Features................................................................................................... 167
5.4.5.2. Installation................................................................................................ 167
5.4.5.3. Verwendung............................................................................................. 168
5.4.5.4. Einstellungen ........................................................................................... 169
5.4.6. Fazit ...................................................................................................................... 169
- VII -
Inhaltsverzeichnis
6. Java-Enterprise..................................................................................................170
6.1. Übersicht ............................................................................................................................ 170
6.1.1. Komponenten........................................................................................................ 171
6.1.1.1. EJB-Container ......................................................................................... 171
6.1.1.2. Application-Client-Container.................................................................... 171
6.1.1.3. Web-Container......................................................................................... 171
6.1.1.4. Applet-Container...................................................................................... 171
6.1.1.5. Datenbank ............................................................................................... 171
6.1.2. Dienste .................................................................................................................. 172
6.1.2.1. JMS.......................................................................................................... 172
6.1.2.2. JMX.......................................................................................................... 172
6.1.2.3. JAAS........................................................................................................ 172
6.1.2.4. JTA .......................................................................................................... 172
6.1.2.5. JavaMail................................................................................................... 172
6.1.2.6. JAF .......................................................................................................... 172
6.1.2.7. JCA .......................................................................................................... 173
6.1.2.8. JAXR........................................................................................................ 173
6.1.2.9. JAX-RPC ................................................................................................. 173
6.1.3. Verfügbare Implementierungen ............................................................................ 173
6.1.3.1. Open Source Server ................................................................................ 173
6.1.3.2. Proprietäre Server ................................................................................... 173
6.1.3.3. Web-Container......................................................................................... 173
6.2. Der JBoss-Application-Server ............................................................................................ 174
6.2.1. Allgemeines .......................................................................................................... 174
6.2.2. Installation ............................................................................................................. 174
6.2.2.1. Aus dem beiliegenden Paket................................................................... 175
6.2.2.2. Originalinstallation von jboss.org............................................................. 175
6.2.2.3. Verzeichnisstruktur .................................................................................. 176
6.2.3. Datenbankanbindung............................................................................................ 176
6.2.3.1. Hypersonic............................................................................................... 177
6.2.3.2. MySQL..................................................................................................... 178
6.2.3.3. PostgreSQL ............................................................................................. 179
6.2.3.4. Oracle ...................................................................................................... 179
6.2.4. Verwendung.......................................................................................................... 180
6.2.4.1. Einfacher Start ......................................................................................... 181
6.2.4.2. JBoss als Windows-Systemdienst........................................................... 182
6.2.4.3. Deployment von Anwendungen............................................................... 183
6.2.5. Verwaltung über die JMX-Konsole ....................................................................... 183
6.2.5.1. System-Eigenschaften............................................................................. 184
6.2.5.2. JNDI-Übersicht ........................................................................................ 184
6.2.5.3. Hypersonic-Manager ............................................................................... 185
6.2.6. Produktiver Betrieb ............................................................................................... 186
6.2.7. Integration in die IDE ............................................................................................ 186
6.2.7.1. Eclipse-Projekt anlegen........................................................................... 186
6.2.7.2. JBoss-Konfiguration anlegen................................................................... 188
6.2.7.3. Den Server steuern.................................................................................. 189
6.2.7.4. Deployment eigener Anwendungen ........................................................ 190
6.3. Enterprise JavaBeans ........................................................................................................ 191
6.3.1. Allgemeines .......................................................................................................... 191
6.3.1.1. Annotations.............................................................................................. 191
6.3.1.2. Home-Interface ........................................................................................ 191
6.3.1.3. Business-Interface ................................................................................... 191
6.3.1.4. Life-Cycle und Callback-Methoden.......................................................... 192
6.3.2. Session-Beans...................................................................................................... 193
6.3.2.1. Zustandslose Session-Beans .................................................................. 193
6.3.2.2. Zustandsbehaftete Session-Beans.......................................................... 193
6.3.2.3. Dependency-Injection.............................................................................. 194
6.3.2.4. Interceptoren (AOP)................................................................................. 194
6.3.2.5. EntityManager.......................................................................................... 195
6.3.2.6. Service-Klasse......................................................................................... 195
- VIII -
Inhaltsverzeichnis
6.3.3. Entity-Beans.......................................................................................................... 198
6.3.3.1. Bean-Managed-Persistence .................................................................... 198
6.3.3.2. Container-Managed-Persistence............................................................. 198
6.3.3.3. FetchType................................................................................................ 199
6.3.3.4. Entity-Bean-Prinzip .................................................................................. 199
6.3.3.5. Konto-Klasse ........................................................................................... 200
6.3.3.6. Buchungs-Klasse..................................................................................... 202
6.3.4. Weitere Bean-Arten .............................................................................................. 203
6.3.4.1. JMS - Message-Driven-Beans................................................................. 203
6.3.4.2. JMX - Management-Beans...................................................................... 206
6.3.4.3. Service-Beans ......................................................................................... 207
6.3.4.4. TimerService............................................................................................ 207
6.3.4.5. Management-Service-Bean mit Timer-Service ....................................... 208
6.3.5. Deployment........................................................................................................... 209
6.3.5.1. Enterprise-Archiv ..................................................................................... 209
6.3.5.2. Archiv für Session-Beans ........................................................................ 210
6.3.5.3. Archiv für Entitiy-Beans ........................................................................... 210
6.3.5.4. Archiv für weitere Klassen ....................................................................... 211
6.3.5.5. Gemeinsame Ant-Build-Datei .................................................................. 212
6.3.5.6. Einstellungen ........................................................................................... 213
6.3.6. Client-Anwendung................................................................................................. 214
6.3.6.1. Einbinden der nötigen Bibliotheken ......................................................... 214
6.3.6.2. Aufruf der Anwendung auf dem Server ................................................... 214
6.3.6.3. Senden einer Nachricht an die MessageBean ........................................ 215
6.3.6.4. Allgemeine Client-Einstellungen.............................................................. 216
6.4. Web-Services ..................................................................................................................... 218
6.4.1. Allgemeines .......................................................................................................... 218
6.4.1.1. RPC-Style vs. Document-Style................................................................ 219
6.4.1.2. literal vs. encoded.................................................................................... 219
6.4.2. Die Buchhaltung als Web-Service-RPC-Endpunkt............................................... 220
6.4.2.1. Service-Schnittstelle ................................................................................ 220
6.4.2.2. Konto-Schnittstelle................................................................................... 221
6.4.2.3. Buchungs-Schnittstelle ............................................................................ 221
6.4.2.4. Implementierung ...................................................................................... 222
6.4.2.5. Deployment.............................................................................................. 223
6.4.3. JBoss-Web-Service-Tools .................................................................................... 225
6.4.3.1. Verwendung mit Ant ................................................................................ 225
6.4.3.2. WSDL-Datei............................................................................................. 226
6.4.3.3. Client-Artefakte ........................................................................................ 227
6.4.3.4. Erfahrungen mit den JBoss-Web-Service-Tools ..................................... 227
6.4.4. Web-Services mit AXIS......................................................................................... 228
6.4.4.1. Installation................................................................................................ 228
6.4.4.2. Ant-Vorgang............................................................................................. 228
6.4.4.3. Client-Artefakte ........................................................................................ 229
6.4.5. Client-Implementierung......................................................................................... 229
6.4.5.1. Proxy-Klasse............................................................................................ 229
6.4.5.2. Service-Klasse......................................................................................... 230
6.4.5.3. Konto-Klasse ........................................................................................... 231
6.4.5.4. Buchungs-Klasse..................................................................................... 233
6.4.6. Fazit ...................................................................................................................... 233
- IX -
Inhaltsverzeichnis
7. Buchhaltung Mobil ............................................................................................234
7.1. Übersicht ............................................................................................................................ 234
7.1.1. Pocket-PC ............................................................................................................. 234
7.1.2. Windows-Mobile.................................................................................................... 234
7.1.3. Java Micro Edition................................................................................................. 235
7.1.4. Mysaifu JVM ......................................................................................................... 235
7.2. Installation .......................................................................................................................... 236
7.2.1. Laufzeitumgebung ................................................................................................ 236
7.2.2. Aufruf der JVM ...................................................................................................... 236
7.2.3. Entwicklungsumgebung ........................................................................................ 236
7.3. Buchhaltungsoberfläche..................................................................................................... 237
7.3.1. Verfügbare Proxy-Module ..................................................................................... 237
7.3.2. Aufbau der Oberfläche.......................................................................................... 238
7.3.2.1. Konto und Buchungsliste......................................................................... 238
7.3.2.2. Konto anlegen oder ändern ..................................................................... 239
7.3.2.3. Buchung anlegen oder ändern ................................................................ 239
7.3.2.4. Konto oder Buchung löschen .................................................................. 240
7.3.2.5. Konto-Details ........................................................................................... 240
7.3.3. Ausnahmen........................................................................................................... 241
7.3.4. Menü ..................................................................................................................... 241
7.3.4.1. Schnittstellen-Menü ................................................................................. 241
7.3.4.2. Hilfe-Menü ............................................................................................... 242
7.4. Anbindung an die vorhandenen Systeme .......................................................................... 243
7.4.1. Lokale und Dummy-Klassen ................................................................................. 243
7.4.2. An das Socket-Modul............................................................................................ 243
7.4.3. Über RMI............................................................................................................... 243
7.4.4. Andere Technologien............................................................................................ 243
7.5. Erfahrungen und Probleme ................................................................................................ 243
-X-
Inhaltsverzeichnis
Schlußwort .............................................................................................................244
Verzeichnisse.........................................................................................................245
Abkürzungsverzeichnis ............................................................................................................. 245
Abbildungsverzeichnis .............................................................................................................. 248
Codeverzeichnis........................................................................................................................ 251
Resourcenverzeichnis ............................................................................................................... 254
Tabellenverzeichnis................................................................................................................... 255
Quellenverzeichnis .................................................................................................................... 256
Anhänge .................................................................................................................261
Anhang A: System-Eigenschaften des JBoss........................................................................... 261
Anhang B: Erweiterte Darstellung des asynchronen Threading ............................................... 263
Anhang C: Notwendige Bibliotheken......................................................................................... 264
Java Architecture for XML Binding (JAXB) ..................................................................... 264
Hibernate-Core................................................................................................................ 264
Hibernate-Annotations .................................................................................................... 264
Web-Services.................................................................................................................. 264
JBoss-Web-Services-Tools............................................................................................. 264
AXIS ................................................................................................................................ 265
JBoss-Client .................................................................................................................... 265
JacORB........................................................................................................................... 265
Anhang D: Performance-Messung............................................................................................ 266
Datenhaltung................................................................................................................... 266
Transport......................................................................................................................... 266
Enterprise JavaBeans..................................................................................................... 266
Anhang E: WSDL-Datei für den Buchhaltungsservice.............................................................. 267
Anhang F: Changelog der Anwendung ..................................................................................... 275
- XI -
Einführung - Motivation
1. Einführung
1.1. Motivation
Der Ausgangspunkt für dieses Tutorial ist die Vorlesung „Verteilte Systeme“. Dabei war ein
Thema die Implementierung eines verteilten Systems anhand von vorgegebenen Schnittstellen. Diese Schnittstellen wurden an die entsprechenden Vorlesung „Verteilte Systeme“ der
Fachhochschule Aargau
https://web.imvs.technik.fhnw.ch/_zope/ds
angelehnt und spezifizieren eine einfache Bankanwendung mit einem Bankobjekt und mehreren Kontenobjekten. Im Verlauf des Semesters führten diese Vorgaben eigentlich eher unbeabsichtigt zu mehreren modularen Implementierungen der Bankanwendung, die jeweils auf
unterschiedliche Weise (Sockets, RMI, CORBA) ein verteiltes System realisieren und aufeinander aufbauen und miteinander verkettet sind. Daraus entstand die Idee, gezielt ein eigenständiges und etwas umfangreiches Beispiel für die unterschiedlichen Möglichkeiten verteilter
Systeme mit Java zu erstellen.
1.2. Aufbau des Tutorials
Dieses Tutorial soll anhand einer durchgehenden thematischen Grundlage mehrere Vorgehensweisen und Technologien darstellen, die im Zusammenhang mit verteilten Systemen unter Java (zum Teil aber auch systemübergreifend) anwendbar sind. Zuerst wird die Ausgangsthematik, eine Finanzbuchhaltungsanwendung, näher erläutert und analog zur Bankanwendung die nötigen Schnittstellen definiert.
Danach wird die Clientanwendung vorgestellt, eine Swing-Benutzeroberfläche, die den
Zugriff auf die spezifizierten Buchhaltungsschnittstellen durchführt, und die Resultate auswertet und darstellt.
Als nächstes folgen verschiedene Möglichkeiten der Datenhaltung, um lokal oder auf der Serverseite die Daten zu speichern.
Dann werden übliche netzwerkbasierte Übertragungsarten betrachtet, bei denen die Anwendung nun in Client- und Serverseite aufgeteilt wird
Schließlich folgt die Betrachtung und Implementierung der Buchhaltung mit Enterprise Java
Beans und als Web-Service. Auch die Implementierung einer Web-Oberfläche mit JavaServer-Faces als zusätzliche Client-Anwendung zur Swing-GUI wäre eine Möglichkeit, die
aber nicht näher ausgeführt wird.
Vom Umfang der einzelnen Teilthemen her liegt das Hauptaugenmerk des Tutorials beim
Hibernate-Persistenz-Framework (siehe 4.6) und dem JBoss-Application-Server (siehe 6.2).
-1-
Einführung - Verwendete Software-Versionen
1.3. Verwendete Software-Versionen
1.3.1. Plattform / System
Entwickelt wird die Buchhaltungsanwendung unter dem Betriebssystem Windows XP, und
um zusätzlichen Ballast zu vermeiden, wird jeweils nicht explizit auf andere Plattformen eingegangen. Allerdings sollte es durch die prinzipielle plattformunabhängigkeit von Java leicht
möglich sein, alle angesprochenen Vorgehensweisen auch auf andere Systeme zu übertragen.
1.3.2. Java
Bei den zugehörigen Java-Programmen kommt der JDK (Java Development Kit) in der Version 6 (1.6.0) Update 11 [01] zum Einsatz. Alle implementierten Beispiele sind auf den Features dieser Java-Version aufgebaut, aber auch die Verwendung der Version 5 (1.5.0) von
Java sollte keine Probleme bereiten.
Lediglich die Web-Services (6.4) funktionieren nicht ohne besondere Modifikationen, weil
die Web-Services-Klassen im JDK nicht mit den Web-Services-Klassen, die der JBossApplication-Server (6.2) mitbringt und benötigt, kompatibel sind.
1.3.3. Eclipse
Als Entwicklungsumgebung wird die "Eclipse IDE for Java EE Developers" [38] verwendet.
Eine Variante der Version 3.4.1 (Ganymede) [30] des Eclipse SDK, die unter
http://www.eclipse.org/downloads/download.php?file=/technology/epp/downl
oads/release/ganymede/SR2/eclipse-jee-ganymede-SR2-win32.zip
heruntergeldaden werden kann und Plugins enthält, die speziell die Entwicklung von Java
Enterprise Anwendungen unterstützen.
Auch die ältere Eclipse-Version 3.3.0 (Europa) [82] kann verwendet werden, wobei ebenfalls
die Variante für "Java EE Developer" zu empfehlen ist.
1.3.4. Frameworks / APIs
Dieses Tutorial hält sich an die folgenden, zum Zeitpunkt der Erstellung (April 2009) verfügbaren, Versionen der verwendeten APIs. Zu einem späteren Zeitpunkt können sich darin noch
Änderungen ergeben, die dann unter Umständen von den aufgeführten Inhalten und Vorgehensweisen abweichen.






Hibernate-Core 3.3.1.GA [27]
Hibernate-Annotations 3.4.0.GA [27]
Hibernate-EntityManager 3.4.0.GA [27]
JWSDP 2.0 - Java Web-Services Development Pack [28]
JBoss-Application-Server 4.2.2.GA [02]
Apache AXIS 1.4 [32]
-2-
Ausgangsthematik Buchhaltung - Übersicht
2. Ausgangsthematik Buchhaltung
2.1. Übersicht
2.1.1. Beschreibung
Als Rahmen und Ausgangsbeispiel für die verschiedenen Konzepte und Technologien, die im
Folgenden vorgestellt werden, soll eine stark vereinfachte Finanzbuchhaltung [12] verwendet
werden. Dies dient dabei aber nur als durchgängige Thematik, das Hauptaugenmerk liegt auf
den Java-Technologien.
Die Buchhaltung wird durch folgende Annahmen definiert:

jeder Buchungssatz besitzt genau eine Soll- und eine Haben-Buchung.

da nur eine Soll- und eine Haben-Buchung existieren besitzt ein Buchungssatz nur genau einen Betrag.

das Buchungsdatum wird nicht erhoben

Umsatzsteuer wird grundsätzlich nicht berücksichtigt

Konten werden lediglich in fünf definierte Kontenarten unterschieden:
o
o
o
o
o
Aktiv
Passiv
Aufwand
Ertrag
Ergebnisrechnung

Es gibt folglich keine automatische Aggregation von Hilfskonten in übergeordnete
Konten

Es gibt keine automatisierte Generierung von Ergebnisrechnungen
Ansonsten verhält sich das Buchhaltungssystem passend zu den im Fach Rechnungswesen
vermittelten Buchhaltungsmethoden [13] nach HGB unter Verwendung z.B. des IndustrieKontenrahmen
-3-
Ausgangsthematik Buchhaltung - Übersicht
2.1.2. Objektmodell
Diese Annahmen führen dann zu einem vereinfachten Objektmodell, das die relevanten Daten
einer Finanzbuchhaltung abdeckt.
Abbildung 1: Objektmodell der Buchhaltung
Das Modell erlaubt es Buchungssätze in der folgenden gewohnten Form anzugeben:
Soll-Konto
6000
an Haben-Konto
4400
Betrag
1000,- €
Text
Kauf von Rohstoffen
Tabelle 1: Beispiel-Buchungsatz
Um später Rechnungen auf einem entfernten System durchzuführen, soll der Buchhaltungsdienst eine Saldierfunktion bieten, die in verteilten Szenarien jeweils auf dem Server den Saldo zu einer gegebenen Kontonummer ermittelt, auch wenn diese Funktionalität eigentlich
eher einem Konto direkt zuzuordnen wäre.
-4-
Ausgangsthematik Buchhaltung - Schnittstellen-Modell
2.2. Schnittstellen-Modell
Im Hinblick auf eine serviceorientierte Programmierung und die spätere Implementierung der
verschiedenen Technologien wird das Model des Buchhaltungssystems auf Schnittstellen mit
durchgängigen englischen Bezeichnern abgebildet.
AccountType.ACTIVE
AccountType.PASSIVE
AccountType.EXPENSE
AccountType.PROCEEDS
AccountType.AMOUNT
Account
AccountingEntry
AccountingEntry.DebitAccount
AccountingEntry.CreditAccount
AccountingEntry.Amount
Account.Balance
Aktiv-Konto
Passiv-Konto
Aufwand-Konto
Ertrag-Konto
Ergebnisrechnung (IKR Klasse 8)
Konto
Buchungssatz
Soll-Konto
Haben-Konto
Betrag der Buchung
Saldo des Kontos
Tabelle 2: Verwendete Begriffe
Abbildung 2: Schnittstellenmodell der Buchhaltung
-5-
Ausgangsthematik Buchhaltung - Schnittstellen-Modell
Dieses Modell erlaubt später eine flexible modulare Implementierung verschiedener Ausprägungen des Systems. Einige Methoden stehen unter Umständen nicht im Einklang mit bekannten realen Buchhaltungsverfahren (z.B. Buchung löschen vs. Storno-Buchung), sind aber
im Rahmen des Gesamtkonzepts der später angesprochenen Technologien sinnvoll, die hier
im Vordergrund vor einer korrekten Finanzbuchhaltung stehen.
Dies ist auch der Grund weshalb alle im Folgenden angesprochenen Schnittstellen bereits von
Remote abgeleitet wurden. So können die Schnittstellen direkt für RMI (siehe 5.3) und EJB
(siehe 6.3) verwendet werden.
2.2.1. Konto-Schnittstelle
Konto-Klassen implementieren die Konto-Schnittstelle zur Verwaltung der minimal notwendigen Daten für ein Buchungs-Konto. Die Schnittstelle liefert und ändert die Daten eines
Kontos (Nummer, Typ, Beschreibung) über Getter- und Setter-Methoden. Lediglich die
Nummer eines Kontos soll einmal festgelegt und nicht nachträglich veränderbar sein.
public interface IAccount extends Remote {
Set<Integer> getCreditEntries()
throws IOException;
Set<Integer> getDebitEntries()
throws IOException;
String getDescription()
throws IOException;
Integer getNumber()
throws IOException;
AccountType getType()
throws IOException;
void setDescription(String description)
throws IOException;
void setType(AccountType type)
throws IOException;
}
Code 1: Interface IAccount
existieren noch zwei weitere Methoden getCreditAccounts() und
getDebitAccounts(), die die Haben- und Soll-Buchungen, die dem Konto zugeordnet sind,
Dazu
zurückgeben sollen. Diese beiden Methoden werden später aufgerufen, sobald ein Konto in
der Kontentabelle der Benutzeroberfläche ausgewählt wird. Die Resultate der Methoden werden in Tabellen entsprechend angezeigt.
-6-
Ausgangsthematik Buchhaltung - Schnittstellen-Modell
2.2.2. Kontotyp-Enumeration
Für die Buchhaltung werden fünf mögliche Kontotypen in der Enumeration AccountType
festgelegt, die vom Rechnungswesen her bekannt sind.
package de.fhr.vs_iw.model;
public enum AccountType implements Serializable {
ACTIVE (1, "Aktiv"),
AMOUNT (5, "Ergebnis"),
EXPENSE (3, "Aufwand"),
PASSIVE (2, "Passiv"),
PROCEEDS(4, "Ertrag");
public int getId();
public String toString();
public static AccountType getAccountTypeById(int id);
public static List<AccountType> getAccountTypes();
}
Code 2: Enumeration AccountType
Jeder Kontotyp erhält eine ID unabhängig von seiner Einordnung in der Enumeration.
2.2.3. Buchungs-Schnittstelle
Die Buchungs-Schnittstelle bietet über Getter- und Setter-Methoden entsprechenden Zugriff
auf die Daten einer Buchung (ID, Haben-Konto, Soll-Konto, Betrag, Text).
package de.fhr.vs_iw.model;
public interface IAccountingEntry extends Remote {
Double getAmount()
throws IOException;
Integer getCreditAccount()
throws IOException;
Integer getDebitAccount()
throws IOException;
Integer getId()
throws IOException;
String getText()
throws IOException;
void setAmount(Double amount)
throws IOException;
void setCreditAccount(Integer number)
throws IOException, AccountingException;
void setDebitAccount(Integer number)
throws IOException, AccountingException;
void setText(String text)
throws IOException;
}
Code 3: Interface IAccountingEntry
Die ID muß dabei von der Implementierung des Buchhaltungssystems für jede Buchung eindeutig vergeben werden und darf nicht nachträglich geändert werden, weshalb dafür in der
Schnittstelle auch keine Setter-Methode vorgesehen ist.
-7-
Ausgangsthematik Buchhaltung - Schnittstellen-Modell
2.2.4. Service-Schnittstelle
Über die Service-Schnittstelle wird dann auf die Geschäftslogik der Buchhaltung, sowie die
Buchungen und Konten, zugegriffen.
package de.fhr.vs_iw.model;
public interface IAccountingService extends Remote {
Double calculateBalance(Integer number)
throws IOException, AccountingException;
void createAccount(Integer number, AccountType type, String description)
throws IOException, AccountingException;
Integer createAccountingEntry(Integer debit, Integer credit,
Double amount, String text)
throws IOException, AccountingException;
IAccount getAccountByNumber(Integer number)
throws IOException, AccountingException;
Set<Integer> getAccountingEntries()
throws IOException;
IAccountingEntry getAccountingEntryById(Integer id)
throws IOException, AccountingException;
Set<Integer> getAccounts()
throws IOException;
void removeAccount(Integer number)
throws IOException, AccountingException;
void removeAccountingEntry(Integer id)
throws IOException, AccountingException;
}
Code 4: Interface IAccountingService
Die
wichtigsten Methoden, sind im ersten Ansatz getAccounts() und
getAccountignEntries() um alle vorhandenen Buchungen und Konten zu erhalten. Die
zurückgegebenen Werte (Kontonummer bzw. Buchungs-ID) stellen für die Geschäftslogik der
Buchhaltung Primärschlüssel dar und müssen im jeweiligen System eindeutig sein.
Danach
kann
für
jeden
erhaltenen
Wert
eine
der
Factory-Methoden
getAccountByNumber(Integer number)
für
die
Konten
bzw.
getAccountingEntryById(Integer id) für die Buchungen aufgerufen werden, um die entsprechenden Objekte zu erhalten.
Die weiteren Methoden zum Anlegen und Löschen von Buchungen vervollständigen das
Buchhaltungssystem, damit es in seiner einfachen Form verwendbar wird. Die Methode
createAccount() erzeugt ein neues Konto, muß aber eine Buchhaltungsausnahme werfen,
wenn bereits ein Konto mit der angegebenen Nummer existiert oder die angegebene Kontonummer ungültig (z.B. negativ) ist. Beim Erfassen einer neuen Buchung durch
createAccountingEntry() muß eine Buchhaltungsausnahme geworfen werden, wenn das
angegebene Haben- und Soll-Konto identisch sind oder der angegebene Betrag ungültig ist.
Für alle entsprechenden Methoden, die einen Schlüsselwert (Nummer bzw. ID) als Parameter
erfordern, gilt, daß diese eine Buchhaltungsausnahme werfen müssen, wenn kein Objekt zum
angegebenen Schlüsselwert existiert.
-8-
Ausgangsthematik Buchhaltung - Schnittstellen-Modell
2.2.5. Proxy-Schnittstelle
Die Proxy-Schnittstelle dient als Einstiegspunkt, um überhaupt Zugriff auf eine Implementierung eines Buchhaltungssystems zu erlangen. Sie hat nichts mit der Geschäftslogik der Buchhaltung zu tun, wird aber benötigt, damit die von der Benutzeroberfläche verwendeten Implementierungen des Systems austauschbar sind.
package de.fhr.vs_iw.model;
public interface IAccountingProxy {
void close()
throws IOException;
IAccountingService connect(String[] args)
throws IOException;
}
Code 5: Interface IAccountingProxy
Durch den Aufruf von connect(String[] args) wird die Instanz einer Service-Klasse geladen. Es ist dabei der Implementierung der Proxy-Klasse überlassen, auf welche Weise diese
Instanz der Service-Klasse erzeugt wird und welche Argumente dafür erforderlich sind. Da
eine Proxy-Klasse über Reflection instanziert wird, sollte eine Implementierung der ProxyKlasse einen öffentlichen Standard-Konstruktor haben.
Die Schnittstelle ist deshalb auch nicht von Remote abgeleitet, weil sie als Einstiegspunkt auf
jeden Fall lokal vorhanden und direkt aufrufbar sein muß.
Um den Zugriff auf eine Implementierung des Buchhaltungssystems sauber abschließen zu
können gibt es schließlich noch die Methode close(). Sie wird aufgerufen, damit das System
die Möglichkeit hat Daten zu speichern oder offene Verbindungen zu schließen.
2.2.6. Erweiterte Service-Schnittstelle
Bei der Entwicklung der Beispiele hat sich gezeigt, daß einige Funktionalitäten benötigt werden, die über den Umfang der Service-Schnittstelle hinausgehen, z.B. bei der Verwendung
von Entity-Beans (siehe 6.3).
package de.fhr.vs_iw.model;
public interface IExtendedAccountingService extends IAccountingService {
IAccount accountChange(IAccount account)
throws AccountingException, IOException;
IAccountingEntry accountingEntryChange(IAccountingEntry entry)
throws AccountingException, IOException;
}
Code 6: Interface IExtendedAccountingService
Durch die beiden Methoden der erweiterten Service-Schnittstelle müssen geänderte Kontenund Buchungsobjekte an das Buchhaltungssystem zurückgegeben werden, damit die geänderten Daten verarbeitet und gespeichert werden können. Eine Implementierung der Methoden
muß die Daten der zurückgegebenen Objekte auf ihre Gültigkeit hinsichtilich der Geschäftslogik überprüfen und im Fehlerfall eine Buchhaltungssausnahme werfen (siehe 2.3.1).
-9-
Ausgangsthematik Buchhaltung - Ausnahmen
2.3. Ausnahmen
Für mögliche auftretende Fehler werden entsprechende Ausnahmen definiert, die von den
Implementierungen des Systems geworfen werden können bzw. müssen.
2.3.1. Buchhaltungsfehler
Für Buchhaltungsfehler, also für Fehler die auftreten, weil Daten nicht der Geschäftslogik der
Buchhaltung entsprechen, wird die Klasse AccountingException definiert.
package de.fhr.vs_iw.model;
public class AccountingException extends Exception {
public AccountingException();
public AccountingException(String message) {}
public AccountingException(String message, Throwable cause);
public AccountingException(Throwable cause);
}
Code 7: Class AccountingException
Ein Buchhaltungsfehler sollte in folgenden Situationen auftreten:











Anlegen eines bereits vorhandenen Kontos
Angabe einer negativen Kontonummer
Erfassen einer Buchung mit identischem Haben- und Soll-Konto
Holen eines nicht vorhandenen Kontenobjekts
Holen eines nicht vorhandenen Buchungsobjekts
Berechnen des Saldos eines nicht vorhandenen Kontos
Angabe eines nicht vorhandenen Haben-Kontos bei einer Buchung
Angabe eines nicht vorhandenen Soll-Kontos bei einer Buchung
Löschen eines nicht vorhandenen Kontos
Löschen einer nicht vorhandenen Buchung
Löschen eines Kontos, das noch für Buchungen verwendet wird
2.3.2. Ein/Ausgabe-Fehler
Alle Methoden der Buchhaltungs-Schnittstellen sind in ihrer Signatur mit einer IOException
versehen. Dies geschah im Hinblick auf die direkte Verwendung der Schnittstellen bei RMI,
wo definiert ist, daß alle Methoden, die entfernt aufrufbar sein sollen mit einer
RemoteException versehen sein müssen, welche eine Unterklasse der IOException ist.
Auch im Hinblick auf andere Technologien zur Datenübertragung ist die Methodendefiniton
mit einer IOException sinnvoll. Andere geprüfte Ausnahmen können bzw. müssen unter Zuhilfenahme einer IOException geworfen werden.
- 10 -
Ausgangsthematik Buchhaltung - Ausnahmen
try {
...
} catch (Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
Code 8: "Verpacken" einer beliebigen Exception als IOException
Dies mag zwar vielleicht etwas unsauber erscheinen, aber eine IOException unterstützt die
direkte Verkettung von Ausnahmen nicht über einen Konstruktor, was nützlich wäre und auch
schon zur Aufnahme in die Spezifikation der Java-IO-Klassen vorgeschlagen wurde.
2.3.3. Nicht implementierte Methoden
Für den Fall, daß eine in den Schnittstellen definierte Methode nicht vollständig implementiert werden soll, d.h. daß sie nicht mit der Funktionalität, die von der Geschäftslogik her vorgesehen ist, ausgestattet wird, gibt es eine NotImplemented-Ausnahme.
package de.fhr.vs_iw.model;
public class NotImplemented extends RuntimeException {
public NotImplemented();
}
Code 9: Class NotImplemented (Exception)
Die Ausnahme ist von RuntimeException abgeleitet, damit sie ungeprüft geworfen werden
kann, weil es sich dabei nicht um einen Fehler bezüglich der Geschäftslogik oder der Datenübertragung handelt.
2.3.4. Andere Ausnahmen
Weitere Ausnahmen werden nicht spezifiziert. Jedoch muß beim Zugriff auf eine Implementierung des Buchhaltungssystems damit gerechnet werden, daß weitere Ausnahmen geworfen
werden können.
- 11 -
Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile
2.4. Modulares Konzept der Programmteile
Die Idee besteht darin, nicht nur verschiedenartige Implementierungen des Buchhaltungssystems zu erstellen, sondern diese auch modular zu verbinden, damit sich die Eigenschaften der
jeweils verwendeten Technologien ergänzen.
Tabelle 3: Modulares Konzept der Programmteile
Die Module sollen unabhängig voneinander implementiert und verwendet werden können.
2.4.1. Client
Die Client-Seite ruft die Methoden eines Buchhaltungssystems auf, greift auf die Konten und
Buchungen zu und verarbeitet die zurückgelieferten Daten (siehe 3).
2.4.1.1. Proxy-Instanzierung über Reflection
Als Einstiegspunkt wird eine Klasse, die die Proxy-Schnittstelle implementiert, über Reflection instanziert. Eine solche Klasse sollte deshalb einen öffentlichen Standard-Konstruktor ohne Argumente besitzen. Für diesen Vorgang gibt es in der Utilities-Klasse die statische
Methode loadProxy(String className) bzw. loadProxy(Class clas).
public static IAccountingProxy loadProxy(String className)
throws NoInterfaceException, ClassNotFoundException,
InvocationTargetException {
return loadProxy(Class.forName(className));
}
public static IAccountingProxy loadProxy(Class clas)
throws NoInterfaceException, InvocationTargetException {
Object object = null;
try {
object = clas.newInstance(new Object[0]);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
if (!(object instanceof IAccountingProxy)) {
throw new NoInterfaceException(clas.getName(), INTERFACE_NAME);
}
return (IAccountingProxy)object;
}
Code 10: Methoden loadProxy(…) der Utilities-Klasse
- 12 -
Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile
Für diese Methode wird auch die Ausnahme NoInterfaceException eingeführt, die geworfen wird, wenn die angegebene Klasse zwar geladen werden kann, aber die benötigte ProxySchnittstelle nicht implementiert.
package de.fh.vs_iw.util;
public class NoInterfaceException extends Exception {
public NoInterfaceException();
public NoInterfaceException(String className, String interfaceName);
}
Code 11: Class NoInterfaceException
Normalerweise sollte die Methode aber ein verwendbares IAccountingProxy-Objekt zur
weiteren Verwendung zurückliefern, sofern nicht absichtlich der Name einer inkompatiblen
Klasse angegeben wird.
Dies stellt die einfachste Möglichkeit dar, um benannte Klassen zur Laufzeit nachzuladen.
Die Benutzeroberfläche verwendet darüber hinaus weitere Mechanismen, um ein flexibles
Plugin-Konzept zu realisieren (siehe 3.2 und 3.8)
2.4.1.2. Laden einer Service-Klasse
Von dem instanzierten Proxy-Objekt wird dann die connect()-Methode aufgerufen, die modulspezifische Argumente in einem String[]-Array übernimmt. Dieses Objekt ist nun dafür
zuständig eine Instanz einer IAccountingService-Klasse zu erzeugen und zurückzugeben.
Auf welche Weise diese Instanz erstellt wird ist jeweils der konkreten Implementierung einer
Proxy-Klasse überlassen. Dies ist auch der Grund, warum die Proxy-Schnittstelle spezifiziert
wurde, damit immer ein einheitlicher lokaler Einstiegspunkt für jede Implementierung vorhanden ist, unabhängig von der konkreten Funktion eines Moduls
2.4.2. Datenhaltung / Geschäftslogik
Bei der ersten Gruppe der vorgestellten Technologien geht es um die Implementierung einer
Datenhaltungsschicht zur konsistenten Verwaltung und persistenten Speicherung der Daten
des Buchhaltungssystems. Diese Module stellen die Grundlage dar, um später damit verteilte
Systeme zu erstellen (siehe 4).
2.4.2.1. Daten und Persistenz
Der zentrale Punkt der Datenhaltungsschicht ist die Speicherung der Daten des Systems in
Java-Objekten, die Verwaltung dieser Objekte und der Zugriff auf die Daten über die Konto(siehe 2.2.1) bzw. Buchungsschnittstelle (siehe 2.2.3). Hinzu kommt die Anforderung der
Persistenz, der dauerhaften Speicherung der Daten auch über das Anwendungsende hinaus
und das Laden vorhandener Daten beim Start der Anwendung.
- 13 -
Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile
2.4.2.2. Geschäftslogik
Damit die Konsistenz und Integrität der gespeicherten Daten sichergestellt werden kann, muß
vor deren Speicherung die Gültigkeit bezüglich der vorgegebenen Geschäftslogik (siehe 2.1.1
und 2.3.1) überprüft werden. Dazu kann ein Modul der Datenhaltung die Geschäftslogik direkt implementieren, so daß sich ingesamt ein System mit drei Schichten ergibt (3-TierArchitecture). Die Geschäftslogik könnte aber auch in einem separaten Modul ausgelagert
werden, das der Datenhaltungsschicht als zusätzliche Schicht vorgeschoben wird (4-TierArchitecture) und über eine weitere definierte Schnittstelle von mehreren Datenhaltungsmodulen gemeinsam verwendet werden könnte.
2.4.3. Transport / Middleware
Die weiteren angesprochenen Technologien bauen auf der Datenhaltungsschicht auf und dienen dann der Datenübertragung. Sie stellen den Kernpunkt der Thematik „Verteilte Systeme“
dar (siehe 5) und werden als austauschbare Schicht zwischen der Benutzeroberfläche und der
Datenhaltung eingebunden. Ein Modul der Transportschicht besteht immer aus zwei Teilen,
die untereinander eine durch ein Protokoll definierte Kommunikation aufbauen und die Daten
des Systems übertragen.
2.4.3.1. Client
Der Client-Teil eines Transport-Moduls stellt eine Implementierung der Proxy-Schnittstelle
zur Verfügung, die den Zugriff duch die Benutzeroberfläche gestattet, und gibt alle Daten
unverändert an den zugehörigen Server weiter. Die Antworten des Servers werden wiederum
direkt an die Benutzeroberfläche zurückgegeben.
2.4.3.2. Server
Der Server-Teil läuft als eigenständiger Prozess und wartet auf Verbindungen von den
Clients. Er übernimmt dann die Daten von den Client zur weiteren Verarbeitung und schickt
die Antworten zurück. Geschäftslogik und Datenhaltung könnten dierekt im Server implementiert werden, was aber der gewünschten Modularität des Systems widersprechen würde.
Ein Server sollte so ausgelegt sein, daß er mit mehreren Clients parallel umgehen kann.
- 14 -
Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile
2.4.4. Verkettung
Über die jeweiligen Proxy-Klassen werden die verschiedenen Module bzw. Implementierungen untereinander verkettet. So kann die Benutzeroberfläche oder ein anderer Client im ersten
Ansatz direkt auf die Datenhaltung zugreifen, oder aber der Client benutzt eine Implementierung der Transportschicht, um sich mit einem entfernten System zu verbinden, das wiederum
auf die Datenhaltung zugreift. Der Server-Teil eines Transport-Moduls wird dabei seinerseits
wieder zum Client des nächsten Moduls der Kette.
Abbildung 3: Vekettung der Module des Buchhaltungssystems
- 15 -
Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile
Durch die einheitliche Proxy-Schnittstelle können auch mehrere Module der Transportschicht
hintereinandergeschaltet werden, und es können sich verschiedene Szenarien ergeben, wie die
Module des System miteinander interagieren und letztendlich das verteilte Buchhaltungssystem realisieren.
Abbildung 4: Mögliche Szenarien der Verkettung von Implementierungen
- 16 -
Benutzeroberfläche - Übersicht
3. Benutzeroberfläche
3.1. Übersicht
Der Ausgangspunkt für das Buchhaltungssystem ist die Swing-Benutzeroberfläche, welche
die Methoden des Schnittstellen-Modells aufruft und so die Buchhaltungsdaten anzeigt.
Abbildung 5: Hauptansicht der Buchhaltungsoberfläche
Das Buchhaltungssystem kann damit vollständig angesprochen werden. Somit dient die Benutzeroberfläche als Client für alle konkreten Implementierungen des Systems mit verschiedenen Technologien. Die Oberfläche nicht unbedingt auf eine praktische Nutzung zur Buchhaltung ausgelegt, sondern eher technisch auf den Aufruf der einzelnen Methoden der Schnittstellen des Buchhaltungsmodell, und die direkte und detaillierte Verarbeitung und Anzeige
der Rückgabewerte bzw. Ausnahmen.
- 17 -
Benutzeroberfläche - Zugriff über eine Proxy-Klasse
3.2. Zugriff über eine Proxy-Klasse
Gestartet wird das System über die Client-Klasse, die das Hauptfenster der Benutzeroberfläche anzeigt:
java de.fhr.vs_iw.Client [[proxyclass] arguments ...]
oder aus dem Buchhaltungs-Package (siehe 3.9.2):
java -jar vs_iw_fibu_main_3.0.jar [[proxyclass] arguments ...]
Auf der Kommandozeile kann der Name einer Proxy-Klasse und weitere Argumente zum
Laden der Service-Klasse angegeben werden. Das Buchhaltungssystem wird durch die angegebene Proxy-Klasse sofort geladen, und die angegebenen Argumente werden als StringArray unverändert an die connect()-Methode übergeben.
Bei diesem direkten Aufruf wird der Java-System-Klassenlader verwendet, und darum muß
sich die angegebene Proxy-Klasse im aktuellen CLASSPATH befinden.
Besser ist es jedoch, die Buchhaltungsanwendung nicht direkt von der Kommandozeile, sondern zusammen mit der Eclipse-Entwicklungsumgebung zu verwenden (siehe 3.9).
3.2.1. Laden und Verbinden eines Buchhaltungssystems
Wenn auf der Kommandozeile keine Proxy-Klasse angegeben wurde, kann das Buchhaltungssystem im Menü unter Schnittstelle->Service-Klasse laden... geladen werden.
Abbildung 6: Schnittstellen-Menü
Dazu sucht die Benutzeroberfläche ausgehend vom aktuellen Anwendungs-Verzeichnis
(System.getProperty("user.dir")) in allen Unterverzeichnissen nach class-Dateien, und
auch in jar- und zip-Archiven nach Klassen, die die Proxy-Schnittstelle implementieren (siehe
auch 3.8). Das Suchverzeichnis kann über die Schaltfläche "Verzeichnis..." auch manuell eingestellt werden.
Jede gefundene Klasse, die als Proxy-Klasse in Frage kommt, wird direkt aus dem Dateisystem geladen und in der aktuellen VM verfügbar gemacht. Dabei kommt ein eigener Klassenlader (de.fh.vs_iw.util.ClassFileLoader) zum Einsatz, der auch unabhängig vom eingestellten CLASSPATH, zusammen mit den Informationen über die beim Suchvorgang gefundenen Klassen, eine Klasse mit allen referenzierten Klassen laden kann (siehe 3.8).
- 18 -
Benutzeroberfläche - Zugriff über eine Proxy-Klasse
Abbildung 7: Service-Klasse laden
Die gefundenen Proxy-Klassen werden aufgelistet, und in der Statuszeile wird eine Statistik
über den Suchvorgang angezeigt. Wie auch auf der Kommandozeile können Argumente zum
Laden eingegeben werden. Durch die Schaltfläche "Datei-Argument…" kann eine Datei als
Argument ausgewählt und angegeben werden.
Wenn die ausgewählte Klasse einen Hinweis auf ihre benötigten Argumente enthält, wird
dieser in der Statuszeile des Fenters und als Tooltip in der Klassenliste angezeigt.
Abbildung 8: Hinweisanzeige bei der Auswahl der Proxy-Klasse
Dazu wird über Reflection, die Klassenvariable ARGS ausgelesen, sofern diese vorhanden ist,
und der darin enthaltene String angezeigt.
package de.fhr.vs_iw.jaxb.v1;
public final class JAXBProxy1 implements IAccountingProxy {
...
private static final String ARGS = "Datei [Schema]";
- 19 -
Benutzeroberfläche - Zugriff über eine Proxy-Klasse
}
Code 12: Klassenvariable ARGS
Mit "Ok" wird anschließend die ausgewählte Proxy-Klasse geladen und instanziert. Von der
Instanz der Proxy-Klasse wird dann die Methode connect(String[] args) aufgerufen, die
eine Instanz der Service-Klasse zurückliefern muß.
Hier werden die angegebenen Argumente übergeben, damit die Service-Klasse entsprechend
geladen werden kann (Socket-Verbindung aufbauen, RMI-Remote-Objekt holen, usw.). Welche Argumente angegeben werden müssen ist dabei der Implementierung der Proxy-Klasse
überlassen, sie werden von der Benutzeroberfläche lediglich in ein String-Array umgewandelt
und dann direkt weitergegeben.
Der Name der aktuell geladenen Proxy-Klasse wird im Titelbalken des Hauptfensters angezeigt.
3.2.2. Schließen der Verbindung
Wenn die Anwendung beendet wird, oder aus dem Menü "Schnittstelle" explizit der Punkt
"Schnittstelle schließen" gewählt wird, so wird die Methode close() der Proxy-Schnittstelle
aufgerufen. Auch wenn direkt eine neue Proxy-Klasse geladen werden soll, so wird vorher
von der aktuellen Proxy-Instanz die close()-Methode aufgerufen und deren korrekte Ausführung abgewartet.
Durch die Methode wird der Zugriff auf eine Implementierung des Buchhaltungssystems beendet, und es können noch Daten gespeichert oder offene Netzwerkverbindungen geschlossen
werden. Die genaue Funktionalität der Methode bleibt der konkreten Implementierung überlassen.
- 20 -
Benutzeroberfläche - Aufbau der Oberfläche
3.3. Aufbau der Oberfläche
3.3.1. Kontenliste
Abbildung 9: Kontenliste
Die Kontenliste gibt die einzelnen Daten (Nummer, Typ, Beschreibung) aller in einem Buchhaltungssystem vorhandenen Konten als Tabelle aus. In der Liste kann ein Konto zur Bearbeitung ausgewählt werden.
3.3.1.1. Laden der Kontenobjekte
Zum Aufbau der Kontenliste werden alle vorhandenen Kontonummern über die Methode
getAccounts() ermittelt und für jede Nummer mit getAccountByNumber() das zugehörige
Kontenobjekt geholt. Diese werden dann der Tabelle hinzugefügt. Von jedem Kontenobjekt
werden die Methoden getNumber(), getType() und getDescription() zur Anzeige der
Kontendaten aufgerufen.
3.3.1.2. Anlegen eines neuen Kontos
Über die entsprechende Schaltfläche unterhalb der Kontenliste wird ein Dialogfenster zum
Anlegen eines neuen Kontos geöffnet. Dort werden die nötigen Daten eingegeben, wobei das
Textfeldes für die Kontonummer auf einen numerischen Inhalt hin überprüft wird.
Abbildung 10: Neues Konto anlegen
- 21 -
Benutzeroberfläche - Aufbau der Oberfläche
Mit den eingegeben Daten wird dann direkt die Methode createAccount() aufgerufen, um
das neue Konto im System anzulegen.
3.3.1.3. Ändern der Daten eines Kontos
Das gleiche Dialogfenster wie zum Anlegen eines Kontos wird auch zum Ändern der Daten
eines Kontos verwendet, nur daß die Kontonummer unveränderbar ist.
Abbildung 11: Konto bearbeiten
Mit den neuen Daten werden die Methoden setType() und setDescription() des ausgewählten Kontenobjekts aufgerufen. Wenn die geladene Instanz einer Service-Klasse die erweiterte Service-Schnittstelle implementiert wird mit dem geänderten Kontenobjekt die Methode changeAccount() aufgerufen.
3.3.1.4. Löschen eines Kontos
zum Löschen eines Kontos wird vorher in einem Dialogfenster noch die Bestätigung des Benutzers eingeholt.
Abbildung 12: Konto löschen
Bei einer positiven Bestätigung wird die Methode removeAccount() des Buchhaltungssystems mit der Nummer des ausgewählten Kontos aufgerufen und das Konto entfernt.
- 22 -
Benutzeroberfläche - Aufbau der Oberfläche
3.3.2. Buchungsliste
Abbildung 13: Buchungsliste
Analog zur Kontenliste werden in der Buchungsliste die Daten (ID, Haben-Konto, SollKonto, Betrag, Text) aller vorhandenen Buchungen tabellarisch angezeigt. Auch hier kann
eine Buchung ausgewählt und bearbeitet werden.
3.3.2.1. Laden der Buchungsobjekte
Zuerst
werden
die
IDs aller vorhandenen Buchungen über die Methode
getAccountingEntries() ermittelt und anschließend für jede einzelne der IDs durch
getAccountingEntryById() das zugehörige Buchungsobjekt geholt. Für die Anzeige der
Daten werden die Methoden getID(), getDebitAccount(), getCreditAccount(),
getAmount() und getText() von jedem der Kontenobjekte aufgerufen.
3.3.2.2. Erfassen einer neuen Buchung
Mit der zugehörigen Schaltfläche über der Buchungsliste kann eine neue Buchung erfaßt werden. In dem dafür vorgesehenen Dialogfenster wird das Soll- und Haben-Konto der neuen
Buchung aus einer Liste der vorhandenen Konten ausgewählt und dazu ein Betrag und eine
Beschreibung eingegeben. Der eingegebene Wert für den Betrag der Buchung wird auf seine
numerische Gültigkeit geprüft.
Abbildung 14: Neue Buchung erfassen
- 23 -
Benutzeroberfläche - Aufbau der Oberfläche
Die eingegebenen Daten werden direkt dem Aufruf der Methode createAccountingEntry()
zugeführt, womit die neue Buchung vom System erfaßt wird. Die ID einer Buchung wird allerdings nicht vom Benutzer angegeben, sondern von der Methode zurückgeliefert.
3.3.2.3. Ändern der Daten einer Buchung
Auch die Daten einer Buchung können nach Auswahl der Buchung und der Schaltfläche "Buchung bearbeiten" in dem Dialogfenster geändert.
Abbildung 15: Buchung bearbeiten
ausgewählte Kontenobjekt werden die Methoden setDebitAccount(),
setCreditAccount(), getAmount() und getText() entsprechend mit den neuen Daten aufgerufen. Wenn die geladene Instanz einer Service-Klasse die erweiterte Service-Schnittstelle
implementiert
wird
mit
dem
geänderten
Buchungsobjekt
die
Methode
changeAccountingEntry () aufgerufen.
Für
das
3.3.2.4. Löschen einer Buchung
Auch vor dem Löschen einer ausgewählten Buchung über die Schaltfläche wird eine Bestätigung eingeholt.
Abbildung 16: Buchung löschen
Zum Löschen wird dann die Methode removeAccountingEntry() des Systems aufgerufen
und die Buchung entfernt.
- 24 -
Benutzeroberfläche - Aufbau der Oberfläche
3.3.3. Soll- und Haben-Buchungen je Konto
Sobald man in der Kontenliste ein Konto auswählt, werden die zugehörigen Soll- und HabenBuchungen für dieses Konto geladen und in Tabellen angezeigt.
Abbildung 17: Liste der Haben- und Soll-Buchungen eines Kontos
Der Aufbau erfolgt ähnlich wie bei der Haupt-Buchungsliste. Über die Methoden
getDebitAccounts() und getCreditAccounts() des ausgewählten Kontos werden die entsprechenden IDs der Buchungen ermittelt, anschließend wieder die Buchungsobjekte geholt
und die Daten von jedem einzelnen Buchungsobjekt geladen.
Wenn in einer dieser beiden Buchungslisten eine Buchung ausgewählt wird, so wird dieselbe
Buchung auch in der Haupt-Buchungsliste ausgewählt, und auch umgekehrt, damit die Verwendung der Schaltflächen für Buchungen konsistent und logisch ist.
3.3.4. Saldo eines Kontos
Der Saldo eines Kontos wird bei Auswahl in der Kontenliste ebenfalls ermittelt. Dazu wird
die Methode calculateBalance() des Buchhaltungssystems mit der Nummer des Kontos
aufgerufen. Der Saldo wird unterhalb der Kontenliste angezeigt.
Abbildung 18: Der Saldo eines Kontos
- 25 -
Benutzeroberfläche - Behandlung von Ausnahmen
3.4. Behandlung von Ausnahmen
Im Allgemeinen werden von der Benutzeroberfläche alle Ausnahmen, auch Laufzeitfehler
(abgeleitet von RuntimeException) und kritische Fehler (abgeleitet von Error), gefangen
und angezeigt, und zum Teil auch speziell ausgewertet.
3.4.1. Nachrichtenfenster
Abbildung 19: Nachrichtenfenster bei Ausnahmen
Wenn dann bei den Aktionen und Methodenaufrufen Ausnahmen auftreten, werden diese in
einem Nachrichtenfenster angezeigt. Wie auf der Konsole wird im Textbereich des Fensters
der Stacktrace der Ausnahme ausgegeben.
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
pw.close();
textArea.setText(sw.toString());
Code 13: Ausgabe des Stacktrace mit Hilfe von StringWriter und PrintWriter
3.4.2. Statusleiste
Auf der Statusleiste werden kurze Informationen über alle Vorgänge, welche die Oberfläche
ausführt, und deren Ergebnis angezeigt. Bei eher sekundären, rein informativen Meldungen
bleibt die Farbe der Leiste grau.
Abbildung 20: Statusleiste grau
Bei Aufrufen von Methoden des Buchhaltungssystems und deren erfolgreicher Beendigung
wird die Leiste grün hinterlegt.
- 26 -
Benutzeroberfläche - Behandlung von Ausnahmen
Abbildung 21: Statusleiste grün
Falls eine der Buchhaltungsmethoden nicht implementiert wurde und die entsprechende Ausnahme wirft, wird die Statusleiste gelb.
Abbildung 22: Statusleiste gelb
Bei einem fehlerhaften Methodenaufruf wird die Leiste rot hinterlegt.
Abbildung 23: Statusleiste rot
Außerdem wird am linken Ende der Statusleiste angezeigt in einem Fortschrittsbalken angezeigt, ob gerade asynchrone Vorgänge im Hintergrund laufen, sowie deren Anzahl.
3.4.3. Direkte Einblendung
Wenn eine Methode bei einer konkreten Implementierung des Buchhaltungssystems gezielt
nicht implementiert wurde, und dementsprechend eine NotImplemented-Ausnahme wirft, so
wird diese speziell behandelt und in dem Anzeigebereich, der dem Ergebnis der Methode
normal zugeordnet ist, eingeblendet.
Abbildung 24: Einblendung bei nicht implementierten Methoden (einzelne Werte)
Abbildung 25: Einblendung bei nicht implementierten Methoden (ganze Auflistung)
Dies dient dazu, daß der Programmablauf nicht durch ein Nachrichtenfenster unterbrochen
wird, weil eine solche Ausnahme eben gezielt geworfen wird, und keinen Fehler an sich darstellt.
- 27 -
Benutzeroberfläche - Kapselung aller Aufrufe
3.5. Kapselung aller Aufrufe
Die Methoden eines Buchhaltungssystems werden durch Aktionen des Benutzers auf der
Oberfläche nicht direkt aufgerufen, um die korrekte Funktion und Verwendbarkeit der Oberfläche nicht von der konkreten (unter Umständen unbekannten) Implementierung der Buchhaltungsmethoden und dabei möglicherweise auftretenden Fehlern abhängig zu machen.
3.5.1. Adapterschicht
Deshalb wird zwischen Benutzeroberfläche und Buchhaltungsimplementierung eine Adapterschicht (Package de.fhr.vs_iw.adapter) eingezogen. Wie das Buchhaltungsmodell besteht
die Schicht aus drei Kern-Komponenten jeweils für die Schnittstellen/Objekte des Modells:



IAccountingService →
IAccount
→
IAccountingEntry
→
ServiceAdapter
AccountAdapter
AccountingEntryAdapter
Jede Adapter-Instanz kapselt genau ein Objekt der Buchhaltung, und die Adapter-Objekte,
sowie die Original-Objekte, werden entsprechend in HashMaps verwaltet, damit immer die
richtige Zuordnung besteht. Die Klassen der Adapterschicht sind generisch aufgebaut und
dann an die Erfordernisse der jeweiligen Objekte (Konten und Buchungen) angepaßt. Außerdem sind hier zusätzliche Vorgangsklassen vorhanden, um den Zugriff auf die Objekte der
Buchhaltung in separaten Threads ausführen zu können (siehe 3.6).
3.5.1.1. Swing-MVC-Pattern
Die Swing-Komponenten zur Anzeige von Daten in einer Benutzeroberfläche sind nach dem
Model-View-Controller-Prinzip (MVC) aufgebaut. Für jede der vier vorhandenen Tabellen
der Klasse JTable wird ein eigenes Tabellen-Modell verwendet, das von der Swing-Klasse
AbstractTableModel abgeleitet ist. Zusammen mit der generischen Schnittstelle
RowTableModel ergibt sich die Klasse AbstractRowTableModel zur zeilenweisen Verwaltung von Objekten eines beliebigen Typs T.
package de.fhr.vs_iw.util;
public abstract class AbstractRowTableModel<T>
extends AbstractTableModel implements RowTableModel<T> {
public final void addDistinctRows(List<T> rows);
public final void addRow(T row);
public final void addRows(List<T> rows);
public final boolean contains(T row);
public final Class<?> getColumnClass(int column);
public abstract int getColumnCount();
public final T getRow(int row);
public final int getRowCount();
public final int getRowIndex(T object);
public abstract String getToolTipAt(int row, int column, boolean ellip);
public abstract Object getValueAt(int row, int column);
public final void removeAllRows();
}
Code 14: Class AbstractRowTableModel<T>
- 28 -
Benutzeroberfläche - Kapselung aller Aufrufe
Damit werden die Adapter-Objekte direkt den Tabellen-Modellen als Zeilen hinzugefügt und
verwaltet. Die konkreten Modell-Klassen sind dann von dieser abstrakten Klasse abgeleitet
und geben die Spaltendaten entsprechend der zugehörigen Tabelle aus.
package de.fhr.vs_iw.gui;
public final class AccountTableModel
extends AbstractRowTableModel<AccountAdapter> {
public int getColumnCount();
public String getColumnName(int column);
public String getToolTipAt(int row, int column, boolean ellip);
public Object getValueAt(int row, int column);
}
Code 15: Class AccountTableModel
Da die Basis-Klasse AbstractRowTableModel entsprechend generisch ist, kann sie für Konten (AccountAdapter) und für Buchungen (AccountingEntryAdapter) gleichermaßen verwendet werden kann.
package de.fhr.vs_iw.gui;
public final class AccountingEntryTableModel
extends AbstractRowTableModel<AccountingEntryAdapter> {
public int getColumnCount();
public String getColumnName(int column);
public String getToolTipAt(int row, int column, boolean ellip);
public Object getValueAt(int row, int column);
}
Code 16: Class AccountingEntryTableModel
Die beiden weiteren Tabellen-Modelle für die Soll- und Haben-Buchungen eines Kontos verhalten sich analog zum diesem Buchungs-Tabellen-Modell.
3.5.1.2. Zwischenspeicherung der Daten
Weil bei jeder Aktualisierung der Anzeige ein Zugriff auf die Daten des Tabellen-Modells,
und damit auch der Adapterschicht, erfolgt, werden die Daten der Buchungen und Konten in
der Adapterschicht zwischengespeichert, sonst müßte bei jeder Aktualisierung der Anzeige
auf das Buchhaltungssystem zugegriffen werden, was ineffizient und nicht unbedingt notwendig ist. Die Klassen AccountAdapter und AccountingEntryAdapter beinhalten ein Statusflag für das gekapselte Objekt, mit dem der Stand der zwischengespeicherten Daten beschrieben wird. So können die Daten bei Bedarf und bei Änderungen erneut aus dem gekapselten
Objekt geladen werden.
3.5.2. Aufteilung in separate Vorgänge
Aber auch über die Adapterschicht erfolgt nicht einfach ein Aufruf der Methoden des Buchhaltungssystems. Jeder Methodenaufruf wird in einer eigenen Vorgangsklasse gekapselt, und
dann entsprechend einer dieser Vorgänge angestoßen. Der Grund für diese Aufteilung und
den Aufbau der Vorgänge wird später unter Punkt 3.6 näher erläutert.
- 29 -
Benutzeroberfläche - Kapselung aller Aufrufe
3.5.2.1. Vorgänge zum Zugriff auf die Buchhaltung
Jede
der
drei
Adapter-Klassen
(ServiceAdatper,
AccountingEntryAdapter,
AccountAdapter) ist an entsprechende Vorgänge für den Zugriff auf die zugehörigen Methoden der Buchhaltungsschnittstellen gebunden. Diese Vorgangsklassen sind von der gemeinsamen Basis-Klasse AbstractServiceTask abgeleitet.
package de.fhr.vs_iw.threading;
public abstract class AbstractServiceTask implements RunnableServiceTask {
public final String getDuration();
public final String getName();
public final boolean isCanceled();
public final void run();
public final void start(final RunnableSwingTask n);
public final void throwNow();
protected abstract void task();
}
Code 17: Class RunnableServiceTask
Die Klasse implementiert die Schnittstelle RunnableServiceTask, und definitert die abstrakte Methode task(), die von einer Unterklasse implementiert werden muß, um separat ausgeführt werden zu können. Ein Vorgang wird mit der Methode start() gestartet. Die run()Methode der Klasse ruft dann die task()-Methode der Unterklasse auf. Da die run()Methode der Schnittstelle Runnable keine Ausnahmen werfen darf, werden Ausnahmen gefangen und gespeichert, und können durch den Aufruf von throwNow() zu einem beliebigen
Zeitpunkt erneut geworfen werden. Die generischen Vorgänge der Adapterschicht werden von
der Klasse abgeleitet.
package de.fhr.vs_iw.adapter;
final class MethodCallTask<P, R> extends AbstractServiceTask {
...
protected void task() throws Throwable {
try {
Logger.entering();
if (isCanceled()) {
return;
}
Logger.calling(getName(), param);
result = caller.callMethod(method, param, this);
if (result == null) {
Logger.returnedNull(getName());
}
}
} finally {
Logger.leaving();
}
}
Code 18: Class MethodCallTask (Adapterschicht)
- 30 -
Benutzeroberfläche - Kapselung aller Aufrufe
Die run()-Methode läuft entsprechend dem angegebenen Verhalten ab und ruft die Methode
calculateBalance() auf. Das Ergebnis der Methode (Rückgabewert oder Ausnahme) wird
in der Adapterschicht zwischengespeichert. Außerdem enthalten die implementierten Vorgangsklassen weitere Methoden um die spezifischen Parameter für den jeweiligen Aufruf des
Buchhaltungssystems anzugeben, wie hier die Methode setNumber().
3.5.2.2. Vorgänge zur Aktualisierung der Benutzeroberfläche
Bei
Aufruf
der
Methode
start(RunnableSwingTask task)
der
Klasse
AbstraktServiceTask wird ein weiterer Vorgang angegeben, der nach dem Ende des Service-Vorgangs gestartet wird. Dies ist dann ein Vorgang zur Aktualisierung der Anzeige der
Daten in der Benutzeroberfläche, der von der Basis -Klasse AbstractSwingTask abgeleitet
ist und die Schnittstelle RunnableSwingTask implementiert..
package de.fhr.vs_iw.threading;
public abstract class AbstractSwingTask implements RunnableSwingTask {
public final void addTaskListener(SwingTaskListener listener) {
public final void cancel();
public final String getName();
public final boolean isCanceled();
public final boolean isStarted();
public final void notifyServiceTaskRunning();
public final void removeTaskListener(SwingTaskListener listener);
public final void run();
public final void setThread(Thread thread);
public final void start();
protected final void setName(String name);
protected abstract void task();
}
Code 19: Class AbstractSwingTask
Auch hier muß von einer Unterklasse die task()-Methode implementiert werden, die dann
durch den Vorgang aufgerufen wird.
private final class Caller<T> extends AbstractSwingTask {
protected void task() throws Throwable {
try {
Logger.entering();
if (serviceAdapter == null) {
Logger.println(STR_SERVICE_NULL);
return;
}
T result = serviceAdapter.resultMethod(method);
}
afterMethodSuccess(method, result);
} finally {
afterMethodAlways(method);
Logger.leaving();
}
}
Code 20: Class Caller (GUI-Vorgang)
- 31 -
Benutzeroberfläche - Kapselung aller Aufrufe
Ein solcher Vorgang wird als Nachfolgevorgang eines Service-Vorgangs gestartet. Er holt das
zwischengespeicherte Ergebnis des Methodenaufrufs aus der Adapterschicht und zeigt es in
der Benutzeroberfläche an.
3.5.3. Kontrolle der Vorgänge
Die Vorgänge sollen zwar grundsätzlich eigenständig ablaufen (siehe 3.6.2), aber dennoch
sollte die Oberfläche den Status der Vorgänge anzeigen, und einen Vorgang gegebenenfalls
abbrechen können.
3.5.3.1. Status-Benachrichtigung über Listener-Interface
Deshalb kann jeder GUI-Vorgang entsprechende Empfänger (die Benutzerpberfläche) über
seinen Status informieren. Der Empfänger muß dafür die SwingTaskListener-Schnittstelle
implementieren.
package de.fhr.vs_iw.threading;
public
void
void
void
void
}
interface SwingTaskListener {
notifyError(RunnableSwingTask task, Throwable e);
notifyFinish(RunnableSwingTask task);
notifyServiceTaskRunning(RunnableSwingTask task);
notifyStart(RunnableSwingTask task);
Code 21: Interface SwingTaskListener
Ein Empfänger, der die Schnittstelle implementiert, wird dann bei einem zugehörigen Vorgang über die Methode addTaskListener() registriert, und der Vorgang ruft die Methoden
der Schnittstelle am entsprechenden Punkt seines Ablaufs auf:




wenn der Vorgang durch seine start()-Methode gestartet wird
notifyServiceTaskRunning()
wenn der zugehörige Service-Vorgang abläuft
notifyFinish()
am Ende der run()-Methode
notifyError()
bei einer Ausnahme durch den Methodenaufruf
notifyStart()
3.5.3.2. Auflistung der laufenden Vorgänge
Die Klasse des Hauptfensters der Benutzeroberfläche (de.fhr.vs_iw.gui.MainWindow) implementiert die SwingTaskListener-Schnittstelle und registriert sich als Empfänger bei den
Vorgängen, welche die Benutzeroberfläche aktualisieren. Durch die daraus resultierenden
Informationen über den Beginn und das Ende der einzelnen Vorgänge wird eine Liste der
jeweils laufenden Vorgänge vom Typ RunnableSwingTask, die einem Service-Vorgang zugeordnet sind, erstellt.
- 32 -
Benutzeroberfläche - Kapselung aller Aufrufe
Abbildung 26: Liste der laufenden Vorgänge
Die Vorgänge werden entsprechend ihrem Ablauf der Liste hinzugefügt oder entfernt. Wenn
der zugeordnete Service-Vorgang gerade abläuft, wird die Anzeige gelb hinterlegt. Alle anderen aufgelisteten Vorgänge befinden sich in der Warteschlange.
Die Anzahl laufender Vorgänge in der Statusleiste stimmt nicht unbedingt mit der Anzahl
Vorgänge in der Liste überein, da es zusätzliche Vorgänge gibt, die nicht auf die Buchhaltungsschnittstellen bzw. die Adapterschicht zugreifen, sondern nur mit den Komponenten der
Oberfläche arbeiten, aber auch durch das Threading-System ausgeführt werden.
Abbildung 27: Ansicht-Menü
Über das Ansicht-Menü kann eingestellt werden, wie die Liste der laufenden Vorgänge angezeigt werden soll:

Vorgangsliste automatisch:


Vorgangsliste anzeigen.
Vorgangsliste ausblenden:
Das Fenster wird ein- und ausgeblendet, sobald Vorgänge laufen und länger als eine Sekunde aktiv bzw. inaktiv
sind.
Das Fenster wird immer angezeigt.
Das Fenster wird niemals angezeigt.
3.5.3.3. Abbruch eines Vorgangs
Neben der Bezeichnung des Vorgangs wird eine Schaltfläche angezeigt, mit dem der Benutzer
den jeweiligen Vorgang abbrechen kann. Dazu wird die Methode cancel() der Klasse
RunnableSwingTask aufgerufen (siehe 3.6.3).
- 33 -
Benutzeroberfläche - Threading
3.6. Threading
Diese Art der Trennung in eigenständige Vorgänge, die alle die Schnittstelle Runnable und
eine run()-Methode implementieren, dient schließlich dazu, daß die Vorgänge vollständig
entkoppelt und gezielt von separaten Threads ausgeführt werden können.
3.6.1. Zentraler Threading-Service
Jeder Vorgang wird über eine Methode (execute(), run(), swing()) der Klasse
ThreadingService ausgeführt.
package de.fhr.vs_iw.threading;
public final class ThreadingService {
public static enum Mode {
ASYNCHRONOUS, MULTITHREADED, SINGLETHREADED, SYNCHRONOUS
}
public static void execute(RunnableServiceTask task);
public static void execute(RunnableSwingTask task);
public static Mode getMode();
public static void run(Runnable run)
public static void setMode(Mode mode);
public static void shutdown();
public static void swing(Runnable task);
}
Code 22: Class ThreadingService
Die Implementierung der Klasse ThreadingService sorgt dafür, daß jeder Vorgang passend
ausgeführt wird (siehe auch 3.6.2). Die im Menü einstellbare Option zur Log-Ausgabe von
Debug-Informationen der Threads wird hier verwaltet und angewandt.
3.6.1.1. AWT-Event-Dispatching-Thread
Um das Design der Swing-Implementierung einfach zu halten sind die Swing-Komponenten
nicht für den parallelen Zugriff durch mehrere Threads ausgelegt. Deshalb gibt es die Single-Thread-Regel [29]:
"Once a Swing component has been realized, all code that might affect or depend
on the state of that component should be executed in the event-dispatching thread."
Dieser eine Thread ist normalerweise der AWT-Event-Dispatch-Thread, und Anwendungen
mit einer Benutzeroberfläche, die trotzdem mehrere Threads benötigen, sollten dafür sorgen,
daß Zugriffe auf Elemente der Oberfläche nur durch den diesen Thread erfolgen. Dazu gibt es
in der Klasse SwingUtilities die statische Methode
SwingUtilities.invokeLater(Runnable doRun);
wodurch Runnable-Objekte in der AWT-Event-Queue für den AWT-Thread eingereiht und
dann passend durch diesen Thread ausgeführt werden können. Der ThreadingService übergibt grundsätzlich alle Vorgänge der Klasse RunnableSwingTask an diese Methode.
- 34 -
Benutzeroberfläche - Threading
3.6.1.2. Worker-Threads
Für Vorgänge die länger dauern oder sogar blockieren können, sollten eigene Threads benutzt werden, weil sonst die Benutzeroberfläche nicht reagiert, solange der AWT-EventDispatch-Thread mit der Ausführung eines Vorgangs beschäftigt ist. Deshalb gibt es für die
Vorgänge, die innerhalb der Adapterschicht ablaufen, separate Threads.
Dazu werden im ThreadingService die Möglichkeiten von ExecutorService aus dem Paket java.util.concurrent verwendet.
private static final ExecutorService MULTI;
private static final ExecutorService SINGLE;
static {
MULTI = Executors.newFixedThreadPool(5);
SINGLE = Executors.newSingleThreadExecutor();
}
Code 23: Verwendung von ExecutorService im ThreadingService
Über die Hilfs-Klasse Executors werden zwei Instanzen von ExecutorService erzeugt,
einmal mit einem festen Pool von 5 parallelen Threads, und dann noch mit nur einem einzelnen Thread und einer Warteschlange zur Ausführung von Vorgängen.
3.6.2. Threading-Modelle
Der zentrale ThreadingService sorgt grundsätzlich dafür, daß jeder Vorgang
(RunnableSwingTask bzw. RunnableServiceTask) durch den richtigen Thread bzw.
ExecutorService ausgeführt wird. Er bietet dafür die Möglichkeit, die Threads auf unterschiedliche Weise den verschiedenen Vorgängen zuzuweisen (execute()) oder aber ganz auf
zusätzliche Threads zu verzichten.
Abbildung 28: Threading-Menü
So gibt es vier unterschiedliche Threading-Modelle, die in der Benutzeroberfläche ausgewählt
werden können, und dann beim ThreadingService über die Methode setMode() entsprechend mit einer der folgenden Enum-Konstanten eingestellt werden.




ThreadingService.Mode.SINGLETHREADED;
ThreadingService.Mode.SYNCHRONOUS;
ThreadingService.Mode.ASYNCHRONOUS;
ThreadingService.Mode.MULTITHREADED;
- 35 -
Benutzeroberfläche - Threading
3.6.2.1. Singlethreaded
Bei diesem Modell wird ganz auf zusätzliche Threads verzichtet und alles wird durch den
AWT-Event-Dispatch-Thread ausgeführt. Dabei reagiert allerdings die Benutzeroberfläche
solange nicht, bis die Ausführung abgeschlossen ist.
Abbildung 29: Ablauf im Modus Singlethreaded
Der ThreadingService ruft die run()-Methoden der beteiligten Vorgänge direkt auf
task.run();
so wie jede andere Methode, egal um welche Art von Vorgang es sich handelt. Hierfür wäre
es allerdings unnötig gewesen, jede Funktionalität in einem eigenen separaten Vorgang zu
kapseln und die Vorgänge auch noch in zwei Arten zu unterscheiden, deshalb folgen nun die
Modelle, die dieses Prinzip der separierten Vorgänge wirklich ausnutzen.
- 36 -
Benutzeroberfläche - Threading
3.6.2.2. Synchron einreihen
Auch hier werden alle Vorgänge, unabhängig von ihrer Art, vom AWT-Event-DispatchThread ausgeführt, allerdings nicht wie im vorigen Modus, durchgängig in einem Aufruf,
sondern über die oben angesprochene Möglichkeit (siehe 3.6.1.1) durch
SwingUtilities.invokeLater(Runnable doRun);
nacheinander, jeder für Vorgang für sich abgeschlossen, eingereiht in der AWT-Event-Queue.
Abbildung 30: Ablauf im Modus Synchron einreihen
Dadurch hat der AWT-Event-Dispatch-Thread prinzipiell die Möglichkeit zwischen den
Vorgängen des Buchhaltungssystems andere Events auszuführen. In der Praxis bedeutet das
zwar, daß die Oberfläche Benutzereingaben nicht wirklich gut annimmt, aber zumindest noch
teilweise aktualisiert wird, solange wie eben Vorgänge ablaufen.
- 37 -
Benutzeroberfläche - Threading
3.6.2.3. Asynchron mit zusätzlichem Thread
Hier kommt ein zusätzlicher Worker-Thread zum Einsatz. Wenn der AWT-Event-DispatchThread eine Benutzereingabe verarbeitet, die zum Zugriff auf das Buchhaltungssystem führt,
wird ein Service-Vorgang (RunnableServiceTask) durch den ThreadingService in eine
separate Queue eingereiht, und dann von einem eigenen Worker-Thread ausgeführt.
Abbildung 31: Ablauf im Modus Asynchron und Multithreaded
Hier kehrt der Aufruf des Vorgangs gleich zurück und der AWT-Thread ist sofort wieder verfügbar, so daß die Benutzeroberfläche nicht einfriert. Der Worker-Thread führt den Aufruf
einer Methode des Buchhaltungssystems aus, speichert das Ergebnis in der Adapterschicht
und startet einen Nachfolgevorgang. Dabei handelt es sich um einen Vorgang zur Aktualisierung der Benutzeroberfläche (RunnableSwingTask) der vom ThreadingService mit
invokeLater() wieder in der AWT-Event-Queue eingereiht und dann vom AWT-EventDispatch-Thread ausgeführt wird, um die Single-Thread-Regel (siehe 3.6.1.1) einzuhalten.
Anhang B stellt den genauen Ablauf in erweiterter Form dar.
3.6.2.4. Multithreaded
Der Unterschied zum asynchronen Modus (siehe 3.6.2.3) besteht lediglich darin, daß mehrere
Service-Vorgänge nicht in einer Queue eingereiht, und dann von einem Worker-Thread sequentiell, sondern gleichzeitig durch mehrere Worker-Threads parallel ausgeführt werden.
- 38 -
Benutzeroberfläche - Threading
3.6.3. Abbruch eines laufenden Vorgangs
Durch die in den späteren Kapiteln aufgezeigten, verschiedenartigen Möglichkeiten das Buchhaltungssystem zu implementieren, kann es beim Zugriff auf die Methoden der Buchhaltungsschnittstellen technologiebedingt zu Problemen oder Verzögerungen kommen, oder der Methodenaufruf blockiert und kehrt gar nicht zurück. Deshalb kann ein Vorgang prinzipiell
durch den Benutzer abgebrochen werden (siehe 3.5.4.3), was aber nur im Asynchronen oder
Multithreaded-Modus möglich ist, weil nur dabei der AWT-Thread frei ist, um die Benutzereingabe zu verarbeiten.
Abbildung 32: Ablauf beim Abbruch eines Vorgangs
- 39 -
Benutzeroberfläche - Threading
Sobald ein Service-Vorgang (RunnableServiceTask) vom ThreadingService durch einen
Thread ausgeführt wird, teilt er dem zugehörigen GUI-Vorgang (RunnableSwingTask) diesen Thread durch den Aufruf von setThread() mit, bevor er die Methode des Buchhaltungssystems aufruft.
Wenn nun der Benutzer die Schaltfläche "Abbrechen" im Dialogfenster der laufenden Vorgänge betätigt, wird die Methode cancel() des GUI-Vorgangs aufgerufen. Dieser kennt den
(evtl. blockierten) Thread und unterbricht ihn mit interrupt(). Der GUI-Vorgang wird dann
sofort für abgeschlossen erklärt (finish()).
Wenn der Methodenaufruf durch den Service-Vorgang z.B. wegen Ein-/Ausgabe blockiert
hatte, sollte er zurückkehren und der Service-Vorgang zu Ende laufen. Eine Implementierung
des Buchhaltungssystems sollte unter Umständen durch
Thread.currentThread().isInterrupted();
auch manuell prüfen, ob der ausführende Thread unterbrochen wurde. Wenn der Thread auf
etwas wartet, kann an der entsprechenden Stelle eine InterruptedException oder
ClosedByInterruptedException geworfen werden, die folgendermaßen weitergegeben (aber dabei nicht erneut geworfen) werden soll [31].
try {
...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Da der zugehörige GUI-Vorgang durch den Abbruch für abgeschlossen erklärt wurde, wird er
nicht wie üblich nach dem Service-Vorgang aufgerufen, und das Ergebnis des Methodenaufrufs wird nicht angezeigt.
Außerdem wird noch durch die Klasse javax.Swing.Timer ein Timer gestartet, der eine Aktion (ActionListener, actionPerformed()) verzögert ausführen kann.
Timer timer = new Timer(5000, this);
timer.start();
Falls ein Thread bis zum Ablauf der Verzögerungszeit nicht auf interrupt() reagiert hat,
wird er durch den Timer mit
public void actionPerformed(final ActionEvent e) {
thread.stop();
}
auf die "harte Tour" beendet. Die Verwendung dieser Methode ist zwar problematisch und
wird abgelehnt [31], weshalb sie auch nur verzögert als Notlösung ausgeführt wird, und im
Menü erst aktiviert werden muß.
- 40 -
Benutzeroberfläche - Weitere Funktionen
3.7. Weitere Funktionen
3.7.1. Testfunktionen
Über das Test-Menü bietet die Benutzeroberfläche die Möglichkeit verschiedene Testfunktionen zur Validierung der Funktionalitäten einer Implementierung des Buchhaltungssystems
aufzurufen.
Abbildung 33: Test-Menü
In Gegensatz zu allen anderen Aufrufen umgehen die Testfunktionen dabei die Adapterschicht und greifen in separaten Threads direkt auf die Methoden der Buchhaltung zu.
3.7.1.1. Daten erzeugen und aufräumen
Diese beiden Funktionen sind keine Testfunktionen an sich. Durch den Menüpunkt "Daten erzeugen" wird von der Utilities-Klasse ein Satz Buchhaltungsdaten generiert und mit
createAccount() bzw. createAccountingEntry() an das Buchhaltungssystem übergeben,
damit ein Benutzer das System zu nicht manuell mit Testdaten füllen muß. Der Menüpunkt
"Rückstände aufräumen" ist für die folgenden Tests gedacht und entfernt Konten
(removeAccount()) und Buchungen (removeAccountingEntry()), die nach Abschluß einer
der Testfunktionen aus irgendeinem Grund übriggeblieben sind.
3.7.1.2. Test der Funktionalität
Der Funktionstest prüft die Methoden der Buchhaltung auf eine korrekte Ausführung der im
Modell spezifizierten Geschäftslogik. Dazu werden der Reihe nach Konten und Buchungen
angelegt, geprüft ob diese danach in den Auflistungen erscheinen, und wieder gelöscht. Außerdem werden die Methoden des Buchhaltungssystems auch gezielt mit ungültigen Werten
aufgerufen, um zu prüfen, ob auch die entsprechenden Ausnahmen geworfen werden.
Abbildung 34: Funktions-Test-Dialog
- 41 -
Benutzeroberfläche - Weitere Funktionen
Nach Abschluß der Tests wird die erfolgreiche Durchführung angezeigt. Wenn allerdings
Fehler auftreten oder ein ungültiges Verhalten des Buchhaltungssystems festgestellt wird, so
wird der genaue Testverlauf in einem Nachrichtenfenster angezeigt.
3.7.1.3. Test der Geschwindigkeit
Der Geschwindigkeits-Test soll die Dauer von 2000 Methodenaufrufen bei einem Buchhaltungssystem ermitteln. Dazu werden zuerst 1000 Buchungen angelegt, und danach diese 1000
Buchungen wieder gelöscht. Durch die Schaltfläche "Schließen" kann der Testlauf bei zu langer Dauer vorzeitig abgebrochen werden.
Abbildung 35: Geschwindigkeits-Test-Dialog
Angezeigt werden "Minuten:Sekunden:Millisekunden" der Testdauer. Dieser Test kann
zur Bewertung der verschiedenen Technologien, mit denen das Buchhaltungssystem später
implementiert, wird dienen.
3.7.1.4. Test auf Threadsicherheit
Hierbei wird eine Implementierung des Buchhaltungssystems auf ihre Threadsicherheit getestet. Dazu werden gleichzeitig 10 Threads gestartet, die auf zwei Testkonten parallel Buchungen anlegen und löschen. Am Ende wird der Saldo der Testkonten auf den korrekten Wert
geprüft.
Abbildung 36: Threadsicherheits-Testdialog
Jede Implementierung des Buchhaltungssystems sollte entsprechend threadsicher sein, und
diesen Test bestehen. Zum Einen sollte am Ende bei den Testkonten der richtige Saldo berechnet werden, und zum Anderen sollte die Anwendung bei den parallelen Zugriffen auch
nicht hängenbleiben (Deadlock).
- 42 -
Benutzeroberfläche - Weitere Funktionen
3.7.2. Logging
Zur Ausgabe des Programmablaufs der Benutzeroberfläche auf der Konsole gibt es eine eigene Logger-Klasse, die über das Logging-Menü gesteuert wird.
Abbildung 37: Logging-Menü
Das Logging kann im Allgemeinen an- oder ausgeschaltet werden. Weiterhin kann man einzeln nur den Ablauf des AWT-Thread, der Worker-Threads oder von Beiden ausgeben lassen.
Für den Asynchronen und Multithreaded-Modus besteht die Möglichkeit die Ausgaben für
jeden Thread separat zu puffern und gesammelt auszugeben, damit sich die Ausgaben der
verschiedenen Threads nicht beliebig vermischen.
3.7.2.1. Logger-Klasse
Die Logger-Klasse verwaltet die Logging-Einstellungen der Benutzeroberfläche aus dem
Logging-Menü und erlaubt über ihre Methoden eine gut lesbar formatierte Ausgabe von Debug-Informationen.
package de.fhr.vs_iw.util;
public final class Logger {
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
public static synchronized
}
void calling(String method, Object... args);
void caught(Throwable e);
void enableLogging(boolean log);
void entering(Object... args);
boolean getAutoSeparator();
boolean getBuffering();
boolean getLogEventThread();
boolean getLogWorkerThreads(),
void leaving();
boolean loggingEnabled();
void println(Object object);
void println(String text);
void registerClass();
void registerClass(Class<?> clas);
void returnedNull(String method);
void separator();
void setAutoSeparator(boolean auto);
void setBuffering(boolean buffering);
void setLogEventThread(boolean log);
void setLogWorkerThreads(boolean log);
Code 24: Class Logger
- 43 -
Benutzeroberfläche - Weitere Funktionen
Eine Klasse registriert sich zuerst durch registerClass() beim Logger, der den Namen der
Klasse feststellt, um die Breite der Spalte, die die Klassennamen ausgibt anzupassen. Danach
kann die Klasse beliebige Logger-Objekte instanzieren.
Es wird gezielt nicht das verbreitete Logging-Framework "Log4j" verwendet, damit die Anwendung einzeln für sich ohne zusätzliche notwendige Bibliotheken verteilt und verwendet
werden kann.
3.7.2.2. Auswertung des Stacktrace
Um Ordnung und Übersicht in die Ausgaben zu bringen, greift die Logger-Klasse auf den
Stacktrace des aufrufenden Thread zurück. Wenn eine Klasse einen Logger instanziert, dann
wird ihr Name über den Stacktrace ermittelt. Das vierte Element des Stacktrace stellt dabei
die aufrufende Methode dar, die Elemente davor sind die Aufrufe der Logger-Methoden
selbst.
StackTraceElement[] stack
= Thread.currentThread().getStackTrace();
String className = stack[4].getClassName();
String threadName = Thread.currentThread().getName();
Code 25: Ermittlung des Klassennamens und des Threadnamens aus dem Stacktrace
So wird bei jeder Ausgabe der Name der aufrufenden Klasse und des aufrufenden Threads in
einer formatierten Spalte mit ausgegeben.
11:45:45|AWT-EventQueue-0|MainWindow$AccountsLoader|...
11:45:45|AWT-EventQueue-0|RunnableSwingTask
|...
11:45:45|AWT-EventQueue-0|MainWindow
|...
11:45:45|AWT-EventQueue-0|RunnableSwingTask
|...
11:45:45|worker-0
|ServiceAdapter
|...
11:45:45|worker-0
|RunnableServiceTask
|...
Code 26: In Spalten formatierte Log-Ausgabe
Außerdem wird zur Orientierung, vor allem im gepufferten Logging-Modus (siehe 3.8.2.4),
noch die Uhrzeit vorangestellt.
3.7.2.3. Hierarchische Methodenaufrufe
Der Logger soll die Aufrufe aller relevanten Methoden hierarchisch eingerückt ausgeben. Der
Name der Methode wird ebenfalls aus dem Stacktrace ermittelt.
StackTraceElement[] stack
= Thread.currentThread().getStackTrace();
String methodName = stack[4].getMethodName();
Code 27: Ermittlung des Methodennamens aus dem Stacktrace
Jede der Methoden teilt dem Logger über den Aufruf von entering() ihren Beginn mit.
Wird die Methode normal (mit einem Rückgabewert) beendet, muß vorher leaving() aufgerufen werden. Die Beendigung einer Methode durch eine Ausnahme wird durch den Aufruf
von exception() beim Logger angezeigt.
- 44 -
Benutzeroberfläche - Weitere Funktionen
...|AccountCreator
|+run()
...|RunnableSwingTask | +running()
...|MainWindow
|
+notifyRunning(<AccountCreator@23756>)
...|MainWindow
|
-notifyRunning
...|RunnableSwingTask | -running
...|ServiceAdapter
| +resultCreateAccount()
...|RunnableServiceTask|
+throwException()
...|RunnableServiceTask|
-throwException, throw AccountingException
...|ServiceAdapter
|
caught AccountingException
...|ServiceAdapter
| -resultCreateAccount, throw AccountingException
...|RunnableSwingTask | +error()
...|MainWindow
|
+notifyError(<AccountCreator@23756>)
...|MainWindow
|
-notifyError
...|RunnableSwingTask | -error
...|RunnableSwingTask | +finish()
...|MainWindow
|
+notifyFinish(<AccountCreator@23756>)
...|MainWindow
|
-notifyFinish
...|RunnableSwingTask | -finish
...|AccountCreator
|-run
Code 28: Logger-Ausgabe der aufgerufenen Methoden
Wie hier am Beispiel des Aufrufs der Methoden zum Anlegen eines Kontos (mit ungültiger
Kontonummer und dadurch geworfener Ausnahme) erkennbar wird, entsteht eine übersichtliche Auflistung der Methodenaufrufe in den verschiedenen Klassen.
3.7.2.4. Unterscheidung nach Thread
Der Logger ermittelt nicht nur den Namen des aufrufenden Threads, auch die Einrückung
wird auf den jeweiligen Thread bezogen gespeichert. Je nach Einstellung
(siehe Abbildung 31) erfolgt für bestimmte Threads überhaupt keine Ausgabe. Außerdem
wird für jeden Thread ein eigener Ausgabepuffer verwendet, damit wie o.g. die Ausgaben
nach Thread zusammengefaßt und gesammelt ausgegeben werden können.
3.7.3. Look & Feel
Eines der Features von Swing ist die Verfügbarkeit verschiedener Look & Feel Klassen zur
Darstellung der Benutzeroberfläche, damit die Oberfläche jeweils zum aktuellen System paßt.
Standardmäßig wird auch die zugehörige Look & Feel Klasse des Systems initialisiert und
verwendet.
try {
String systemLF = UIManager.getSystemLookAndFeelClassName();
UIManager.setLookAndFeel(systemLF);
} catch (Exception e) {
...
}
Code 29: Look & Feel Initialisierung
Diese API erlaubt aber den einfachen Austausch der Look & Feel Klasse, auch dynamisch zur
Laufzeit eines Programms. Durch den Aufruf der Methode
UIManager.getInstalledLookAndFeels();
- 45 -
Benutzeroberfläche - Weitere Funktionen
erhält man eine Auflistung der verfügbaren Look & Feel Klassen. Zu demonstrationszwecken
wird die Liste um zusätzliche, nicht standardmäßig mitgelieferte, Varianten erweitert.
Abbildung 38: Look & Feel Menü
Über das Look & Feel Menü kann nun jederzeit eine Look & Feel Klasse ausgewählt werden,
welche die Oberfläche dann entsprechend darstellt.
Abbildung 39: Benutzeroberfläche im "Metal" Look & Feel
- 46 -
Benutzeroberfläche - Weitere Funktionen
Der Einstellung der im Menü gewählten Klasse erfolgt durch den Aufruf der
setLookAndFeel()-Methode der Utilities-Klasse mit dem Index des gewählten Menüeintrags, welche die gleichnamige Methode des UIManager aufruft.
public static void setLookAndFeel(int index) throws Exception {
final LookAndFeelInfo[] l = UIManager.getInstalledLookAndFeels();
if (index >= 0 && index < l.length) {
UIManager.setLookAndFeel(l[index].getClassName());
}
}
Code 30: Utilities-Methode setLookAndFeel()
Im Anschluß muß aber die Darstellung von bereits angezeigten Swing-Komponenten auf den
neu eingestellten Look & Feel aktualisiert werden. Dafür erbt jede Komponente die Methode
updateUI() bei deren Aufruf die Darstellung auf Grundlage des aktuellen Look & Feel neu
initialisiert wird. Durch Aufruf von
SwingUtilities.updateComponentTreeUI(this);
im Hauptfenster der Anwendung wird der gesamte Komponenten-Baum des Fensters durch
den Aufruf der updateUI()-Methode bei allen Sub-Komponenten aktualisiert. Bei Zusatzfenstern muß die Methode auch für diese Fenster aufgerufen werden, z.B.
SwingUtilities.updateComponentTreeUI(taskListDialog);
und bei eigenen Komponenten muß unter Umständen die updateUI()-Methode überschrieben und selbst implementiert werden.
3.7.4. Hilfe
Abbildung 40: Hilfe-Menü
Über das Hilfe-Menü können Informationen zur Buchhaltung direkt aufgerufen werden:



Beschreibung:
Öffnet die HTML-Kurzbeschreibung zur Buchhaltung in einem
externen Web-Browser, sofern diese im Unterverzeichnis "description" vorhanden ist.
JavaDoc anzeigen: Sucht nach einer vorhandenen JavaDoc-Beschreibung und zeigt
diese im einem Web-Browser an.
Info:
Öffnet ein Fenster mit einigen Angaben und Kontaktinformationen zur Benutzeroberfläche.
- 47 -
Benutzeroberfläche - Suchen und Laden der Proxy-Klasse
3.8. Suchen und Laden der Proxy-Klasse
Wie bereits in Abschnitt 3.2 beschrieben, kann von der Benutzeroberfläche eine beliebige
Proxy-Klasse, welche die vorgegebene Proxy-Schnittstelle implementiert, geladen werden.
3.8.1. Suchverzeichnis
Standardmäßig werden die Klassen ausgehend vom aktuellen Verzeichnis gesucht. Man kann
aber jederzeit über die Schaltfläche "Verzeichnis..." ein anderes Verzeichnis auswählen.
Abbildung 41: Verzeichnis-Auswahl-Dialog
Dann wird der Verzeichnisbaum ausgehend vom gausgewählten Verzeichns rekursiv durchlaufen, und jede gefundene class-Datei, auch innerhalb von Archiven, wird verarbeitet.
3.8.2. Klassenlader für den Suchvorgang
Der Klassenlader de.fhr.vs_iw.util.FileClassLoader führt zum einen den Suchvorgang
durch und ist zum Anderen von der Klasse ClassLoader abgeleitet, damit er die Möglichkeit
hat, mit den überschriebenen Methoden findClass() und loadClass() Klassen von der
JVM nachladen zu lassen.
public final class ClassFileLoader extends ClassLoader {
public Class<?> findClass(String name);
public Class<?> loadClass(String name);
public static void cancel();
public static boolean isSearching();
public static void search(String dir, ClassFileLoaderListener listener);
}
- 48 -
Benutzeroberfläche - Suchen und Laden der Proxy-Klasse
Code 31: Class ClassFileLoader
Die weiteren Methoden kontrollieren den Suchvorgang. Um Inkonsistenzen bei mehreren
Suchvorgängen über mehrere Verzeichnisse zu vermeiden sind diese Methoden statisch und
der Klassenlader wird nach dem Singleton-Design-Pattern nur einmal instanziert und gekapselt. Außerdem ist der Suchvorgang threadsicher synchronisiert.
3.8.3. Listener-Schnittstelle für den Klassenlader
Die aufrufende Anwendung wird mit Hilfe der ClassFileLoaderListener-Schnittstelle vom
Klassenlader über die Ereignisse beim Suchvorgang informiert.
package de.fhr.vs_iw.util;
import java.io.File;
public interface ClassFileLoaderListener {
boolean classFound(ClassFileDecoder cfd);
void classLoaded(ClassFileEntry entry);
boolean classUpdate(ClassFileEntry entry, ClassFileDecoder cfd);
boolean errorOccured(File file, Throwable e);
void otherPath(File directory);
}
Code 32. Interface ClassFileLoaderListener
Die Anwendung wird dadurch über die folgenden Ereignisse informiert und kann das weitere
Vorgehen beim Suchvorgang durch einen entsprechenden Rückgabewert beeinflussen.

otherPath

classFound

classUpdate

classLoaded

errorOccured
wird aufgerufen, wenn das aktuelle Suchverzeichnis gewechselt
wurde, oder in innerhalb eines Archivs gesucht wird
wird aufgerufen, wenn eine class-Datei gefunden wurde; der
Rückgabewert gibt an ob die Klasse in die JVM geladen werden
soll
wird aufgerufen, wenn eine neu gefundene Klasse den gleichen
Namen hat, wie eine bereits zuvor gefundene; der Rückgabewert gibt
an, ob nun die neue Klasse verwendet werden soll
wird aufgerufen, wenn eine Klasse erfolgreich in die JVM geladen
wurde, die Klasse darf aber hier noch nicht verwendet werden,
sondern erst nach dem Abschluß des Suchvorgangs
wird aufgerufen, wenn bei der Suche eine Ausnahme auftritt; der
Rückgabewert gibt an, ob der Vorgang fotgesetzt werden soll
Eine gefundene Klasse wird aber nicht unbedingt sofort geladen, auch wenn dies von der Anwendung gewünscht wird. Es kann vorkommen, daß referenzierte Klassen noch nicht gefunden wurden, weshalb das Laden verzögert wird, bis alle Abhängigkeiten aufgelöst werden
können. Deshalb sind die gewünschten Klassen gezielt erst nach dem Abschluß des Suchvorgangs verwendbar.
Anders als bei üblichen Listener-Schnittstellen kann hier nur der eine Empfänger benachrichtigt werden, der den Suchvorgang gestartet hat, und der Suchvorgang kann auch nur einmal
und nicht mehrfach parallel gestartet werden.
- 49 -
Benutzeroberfläche - Suchen und Laden der Proxy-Klasse
3.8.4. Eigenschaften gefundener Klassen
Die Eigenschaften der gefundenen Klassen werden ausgelesen, ohne daß diese von der JVM
geladen werden müssen. Die Schnittstelle de.fh.vs_iw.util.ClassFileReflection stellt
dann entsprechende Auswertungsmethoden zur Verfügung, um diese Eigenschaften gezielt
abzufragen.
package de.fhr.vs_iw.util;
public interface ClassFileReflection {
String[] getFields();
File getFile();
String[] getInterfaces();
String[] getMethods();
String getName();
String getSuperclass();
ZipEntry getZipEntry();
boolean hasField(String field);
boolean hasInterface(String iface);
boolean hasMethod(String method);
boolean isAbstract();
boolean isFinal();
boolean isInterface();
boolean isPublic();
}
Code 33: Interface ClassFileReflection
Ein Objekt der Klasse ClassFileDecoder dekodiert die Informationen in einer class-Datei
und stellt sie über die o.g. Schnittstelle zur Verfügung. Die Datei kann auf eine beliebige
Weise eingelesen werden, und wird der Klasse dann als byte[]-Array übergeben. Die ByteDaten der Klasse werden anhand der Format-Spezifikation für class-Dateien [83] dekodiert
und strukturiert aufbereitet.
package de.fhr.vs_iw.util;
public final class ClassFileDecoder implements ClassFileReflection {
private final ZipEntry entry;
private final File file;
ClassFileDecoder(byte[] classbytes);
ClassFileDecoder(byte[] classbytes, File file, ZipEntry entry);
public
public
public
public
public
public
}
String getErrors();
long getMagic();
int getMajorVersion();
int getMinorVersion();
int getSize();
boolean isValid();
<interface methods>
Code 34: Class ClassFileDecoder
Zusätzlich zu den Eigenschaften der Java-Klasse an sich, wird auch noch die Herkunft der
Daten (class-Datei bzw. Teil eines Archivs) festgehalten.
- 50 -
Benutzeroberfläche - Suchen und Laden der Proxy-Klasse
3.8.5. Laden gefundener Klassen
In Listener-Methode classFound() prüft die Anwendung, ob eine gefundene Klasse als
Proxy-Klasse in Frage kommt.
public boolean classFound(final ClassFileDecoder clas) {
if (clas.hasInterface("de.fhr.vs_iw.model.IAccountingProxy")) {
return true;
}
return false;
}
Code 35: Method classFound() in ServiceSelectDialog
Wenn dies durch den Rückgabewert true bestätigt wird, dann wird die gefundene Klasse in
die JVM geladen. Der Methode defineClass() von ClassLoader werden die eingelesen
Byte-Daten übergeben, damit die JVM daraus eine verwendbare Laufzeit-Java-Klasse
(Class-Objekt) erstellt.
protected final Class<?> defineClass(String name, byte[] b,
int off, int len) throws ClassFormatError
In einem Objekt von ClassFileEntry wird die geladene Klasse dann zusammen mit den Informationen zu ihrer Herkunft verwaltet, für den Eintrag in der Liste der gefundenen ProxyKlassen und auch intern für den Klassenlader.
public final class ClassFileEntry implements ClassFileReflection {
private Class<?> clas;
private File file;
private String name;
private ZipEntry zipe;
ClassFileEntry(String name, File file, ZipEntry zipe);
public Class<?> getTheClass();
void setTheClass(Class< ? > c);
<interface methods>
}
Code 36: Class ClassFileEntry
Diese Klasse implementiert auch wieder die Schnittstelle ClassFileReflection zur Abfrage
der Eigenschaften der gekapselten Klasse. Hier erfolgt die Abfrage dann aber mit JavaReflection und nicht mehr über die selbst dekodierten Byte-Daten der Klasse.
Alle gefundenen und erfolgreich geladenen Proxy-Klassen werden beim Aufruf der ListenerMethode classLoaded() jeweils in die Tabelle im Auswahldialog aufgenommen
3.8.6. Anmerkung
Der Klassenladevorgang ist hier eine durchaus komplizierte Angelegenheit, da Java intern nur
Klassen kennt, aber keine Dateien im Dateisystem. Das Laden einer Klasse aus einer beliebigen vorliegenden Datei ohne weitere Strukturierung und Pfade war so nicht vorgesehen.
- 51 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
3.9. Verwendung der Buchhaltung in Eclipse
Gearbeitet wird mit der Buchhaltungsoberfläche in der freien Java-Entwicklungsumgebung
Eclipse [30]. Zu Beginn ist keine spezielle Version davon oder besondere zusätzliche Plugins
notwending. Die aktuelle Version 3.3.1.1 (Europa) des Eclipse-SDK kann unter
http://www.eclipse.org/downloads/
heruntergeladen werden. Das Paket muß nur in ein beliebiges Verzeichnis entpackt und unter
Windows die eclipse.exe ausgeführt werden.
Es kann aber zusammen mit dem JBoss-Application-Server eine spezielle Eclipse-Version
[38] verwendet werden, die von den JBoss-Entwicklern zur Verfügung gestellt wird und unter
http://java.fh-regensburg.de/download/eclipse-SDK-3.4.1-win32.zip
heruntergeladen werden kann (siehe auch 6.2.7). Alternativ kann man die JBoss-Plugins später auch bei einem Standard-Eclipse nachträglich hinzufügen.
3.9.1. Neues Projekt anlegen
Zuerst muß in Eclipse ein neues Projekt für die Arbeit mit der Buchhaltungsanwendung angelegt werden. Dazu wird unter
File -> New -> Project...
der Projekt-Assistent aufgerufen. Im ersten Dialog wird ein "Java Project" ausgewählt.
Abbildung 42: Eclipse-Dialog "New Project"
- 52 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Im nächsten Dialog wird ein Name für das Projekt (z.B. VS_IW_FiBu) vergeben und die
Grundeinstellungen vorgenommen.
Abbildung 43: Eclipse-Dialog "New Java Project"
Die Einstellung "Create separate folders for source and class files" ist sinnvoll, damit das Projekt besser strukturiert wird. Mit der Schaltfläche "Finish" wird der Dialog geschlossen.
Abbildung 44: Eclipse Package Explorer
- 53 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Das Projekt wird erzeugt und im "Package Explorer" angezeigt. Der Ordner "src" wurde automatisch für die eigenen Packages und Klassen erstellt.
3.9.2. Importieren des Package mit der Buchhaltungsoberfläche
Um die Strukturierung des Projekts weiterzuführen, wird nun ein Ordner für eigene Bibliotheken angelegt. Der entsprechende Dialog wird im Menü unter
File -> New -> Folder
aufgerufen. Der neue Ordner erhält den Namen "lib".
Abbildung 45: Eclipse-Dialog "New Folder"
Der neue Ordner wird im Package-Explorer angezeigt und auch gleich ausgewählt.
Abbildung 46: Eclipse Package Explorer
- 54 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Anschließend wird das Package für die Buchhaltung benötigt. Es kann als jar-Datei unter
http://java.fh-regensburg.de/download/fibu-3.3/vs_iw_fibu_main_3.3.jar
heruntergeladen werden, und enthält die Benutzeroberfläche, die Buchhaltungsschnittstellen,
einige Hilfsklassen und Dummy-Proxy-Klassen zu Testzwecken.
Über die Import-Funktion von Eclipse, die im Menü unter
File -> Import...
zu finden ist, wird die Datei importiert. Dazu wird im ersten Dialog unter "General" als Quelle das "File System" ausgewählt.
Abbildung 47: Eclipse-Dialog "Import" - Select
Im zweiten Dialog wird dann durch die Schaltfläche "Browse…" oder über manuelle Eingabe
das Verzeichnis angegeben, in welches die jar-Datei mit der Buchhaltung heruntergeladen
wurde. Auf der rechten Seite zeigt Eclipse dann die Dateien in diesem Verzeichnis an. Vor die
gewünschte Datei vs_iw_fibu_mein_3.0.jar wird ein Häckchen gesetzt.
Als Zielordner ("Into folder:") sollte der neue Ordner für Bibliotheken "VS_IW_FiBu/lib"
eingetragen werden. Durch die Schaltfläche "Finish" wird der Import angeschlossen.
- 55 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Abbildung 48: Eclipse-Dialog "Import" - File system
Im Package Explorer wird die soeben importierte Bibliothek angezeigt, und sie wurde von
Eclipse auch dateimäßig ins Projektverzeichnis kopiert.
Abbildung 49: Eclipse Package Explorer
In den Projekteigenschaften muß nun eingestellt werden, daß die Bibliothek von Eclipse bei
der Compilierung von Java-Code einbezogen werden soll. Das entsprechende Dialogfenster
wird im Menü unter
Project -> Properties
aufgerufen. Unter der Rubrik "Java Build Path" und dem Reiter "Libraries" sind die bei der
Compilierung verwendeten Bibliotheken aufgelistet.
- 56 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Abbildung 50: Eclipse-Dialog "Properties for VS_IW_FiBu"
Von der Erstellung des Projektes her ist bereits der Eintrag für die Java-StandardBibliotheken (z.B. "JRE System Library [jre6]") vorhanden. Mit der Schaltfläche
"Add JARs…" werden Bibliotheken aus dem aktuellen Workspace hinzugefügt. In einem
separaten Fenster kann die jar-Datei der Buchhaltung ausgewählt werden.
Abbildung 51: Eclipse-Dialog "JAR Selection"
Nach dem Schließen der Dialogfenster wird im Package Explorer die Anzeige aktualisiert.
Die jar-Datei wird nicht mehr unter "lib", sondern als Java-Bibliothek unter "References
Libraries" angezeigt. Man kann sich nun auch die enthaltenen Packages und Klassen anzeigen
lassen.
- 57 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Abbildung 52: Eclipse Package Explorer
3.9.3. Start der Anwendung
Gestartet wird die Anwendung über die Klasse de.fhr.vs_iw.Client (siehe auch 3.2), die
jetzt nach dem Importieren im CLASSPATH von Eclipse verfügbar ist. Über den Menüpunkt
Run -> Open Run Dialog...
wird das Dialogfenster zum Erstellen einer Startkonfiguration aufgerufen.
Abbildung 53: Eclipse-Dialog "Run" - Empty
- 58 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Als Typ wird "Java Application" ausgewählt und durch die Schaltfläche "New" wird eine
neue Startkonfiguration erzeugt.
Für die Konfiguration kann ein Name (z.B. Client) vergeben werden. Unter dem Punkt
"Main class" wird die o.g. Klasse de.fhr.vs_iw.Client eingetragen.
Abbildung 54: Eclipse-Dialog "Run" - Java Application
Auf dem Reiter "Aguments" könnten noch weitere Argumente zum Start der Anwendung
angegeben werden, wie auf der Kommandozeile auch. Dies könnte eben der Name einer vorhandenen Proxy-Klasse sein, die sofort beim Aufruf der Benutzeroberfläche geladen werden
soll.
Durch die Schaltfläche "Run" wird das Dialogfenster geschlossen und die Buchhaltungsanwendung gestartet.
3.9.4. Erstellung eines eigenen Moduls für die Buchhaltung
Sobald die Benutzeroberfläche der Buchhaltungsanwendung abblauffähig ist, kann mit der
Erstellung eigener Buchhaltungsklassen begonnen werden. Dazu wird günstigerweise zuerst
über den Menüpunkt
File -> New -> Package
eine neues Package (z.B. de.fhr.vs_iw.local) angelegt, um die Übersicht zu behalten.
- 59 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Abbildung 55: Eclipse-Dialog "New Java Package"
Das neue Package wird im Package Explorer standardmäßig im Ordner "src" angelegt.
Abbildung 56: Eclipse Package Explorer
Anschließend werden die eigenen Klassen über den entprechenden Dialog im Menü unter
File -> New -> Class
erstellt. Für jede neue Klasse muß eine Name vergeben werden. Als Package wird über die
zweite Schaltfläche "Browse…" die zuvor angelegte Package de.fhr.vs_iw.local ausgewählt. Im Bereich "Interfaces:" wird jeder neuen Klasse mit der Schaltfläche "Add…" eine
der Buchhaltungsschnitststellen aus dem FiBu-Paket hinzugefügt (z.B. IAccountingProxy).
- 60 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Abbildung 57: Eclipse-Dialog "Implemented Interfaces Selection"
Abbildung 58: Eclipse-Dialog "New Java Class"
- 61 -
Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse
Dieser Vorgang wird für alle vier Schnittstellen der Buchhaltung (siehe 2.2) wiederholt, bis
für jede Schnittstelle jeweils eine zugehörige implementierende Klasse angelegt wurde.
IAccount
IAccountingEntry
IAccountingService
IAccountingProxy
->
->
->
->
de.fhr.vs_iw.local.Account
de.fhr.vs_iw.local.AccountingEntry
de.fhr.vs_iw.local.AccountingService
de.fhr.vs_iw.local.LocalProxy
Abbildung 59: Eclipse Package Explorer
Eclipse erstellt für jede Methode jeder der Schnittstellen eine leere Methode in der zugehörigen Klasse, z.B. in der Klasse de.fhr.vs_iw.IAccountingService:
public Double calculateBalance(Integer number) throws IOException,
AccountingException {
// TODO Auto-generated method stub
return null;
}
Die Methoden müssen nun mit der entsprechenden Funktionalität ausgestattet werden, um die
Geschäftslogik bzw. die Datenübertragung des Buchhaltungssystems zu realisieren.
Abbildung 60: Eclipse-Gesamtansicht des neu erstellten Projekts
- 62 -
Datenhaltung - Übersicht
4. Datenhaltung
4.1. Übersicht
Der erste Abschnitt über die verschiedenen möglichen Module der Buchhaltung beschäftigt
sich mit der Datenhaltungsschicht als Endpunkt des Systems (siehe 2.4).
4.1.1. Datenspeicherung
Der Hauptthematik dabei ist die Speicherung der Buchhaltungsdaten. Eine Service-Klasse
muß eine Anzahl von Konten und Buchungen verwalten, die ja wiederum ihre einzelnen Daten besitzen. Neu angelegte Konten oder Buchungen müssen später in der Auflistung erscheinen und auch wieder abrufbar sein.
4.1.2. Geschäftslogik
Ein weiterer Punkt, der die Datenhaltungsschicht betrifft, ist die korrekte Implementierung der
Geschäftslogik, damit bei ungültigen Aufrufen die entsprechenden Ausnahmen geworfen
werden. Dies erfordert eine bidirektionale Beziehung zwischen der Service-Klasse, den Konten und den Buchungen.
Das bedeutet, daß die Service-Klasse nicht einfach nur alle vorhandenen Konten und Buchungen verwalten muß, sondern jedes Konten- bzw. Buchungsobjekt muß seine zugehörige Service-Klasse kennen. Nur so kann z.B. ein Buchungsobjekt, dessen Haben-Konto neu festgelegt wird, feststellen, ob dieses Konto auch vorhanden und gültig ist, indem es auf die Kontenliste der Service-Klasse zugreift.
Beim Laden und Speichern der Objekte muß dieser Bezug hergestellt und vor allem auch
wieder aufgelöst werden, damit der Vorgang nicht in eine endlose Rekursion läuft. Dies ist
bei allen im Folgenden Technologien ein Punkt der besondere Aufmerksamkeit erfordert.
- 63 -
Datenhaltung - Einfache lokale Datenhaltung
4.2. Einfache lokale Datenhaltung
4.2.1. Allgemeines
Als Einstieg in die Datenhaltung werden die Daten des Buchhaltungssystems in Objekten der
laufenden Virtuellen Maschine gespeichert, wobei die Daten aber flüchtig sind, und verloren
gehen, wenn die VM terminiert. Die Geschäftslogik wird vollständig implementiert.
4.2.2. Daten in HashMaps
Zur Verwaltung werden die Objekte mit den Daten in HashMaps abgelegt, damit effizient darauf zugegriffen werden kann.
4.2.2.1. Konten und Buchungen
Die Service-Klasse benötigt zwei HashMaps jeweils für die Buchungen und Konten
private Map<Integer, AccountingEntry> accountingEntries
= new HashMap<Integer, AccountingEntry>();
private Map<Integer, Account> accounts
= new HashMap<Integer, Account>();
Code 37: HashMaps für Konten- und Buchungs-Objekte
Ein Objekt wird dabei direkt der ihm vergebenen Nummer bzw. ID zugeordnet, damit es später direkt gefunden zurückgegeben werden kann.
4.2.2.2. Soll- und Haben-Buchungen
Die Konto-Klasse verwaltet ihre zugeordneten Soll- und Haben-Buchungen ebenfalls in zwei
HashMaps.
private Map<Integer, AccountingEntry> creditEntries
= new HashMap<Integer, AccountingEntry>();
private Map<Integer, AccountingEntry> debitEntries
= new HashMap<Integer, AccountingEntry>();
Code 38: HashMaps für Soll- und Haben-Buchungen
Dabei werden natürlich dieselben Objekte referenziert, wie in der entsprechenden HashMap in
der Service-Klasse.
4.2.2.3. Bidirektionaler Bezug
Die Service-Klasse stellt den bereits angesprochenen bidirektionalen Bezug her, indem sie
einen Verweis auf sich selbst (this) an alle neuen Konten- und Buchungs-Objekte übergibt.
- 64 -
Datenhaltung - Einfache lokale Datenhaltung
4.2.3. Implementierung der Schnittstellen
Es werden alle Schnittstellen des Buchhaltungsmodels in Klassen implementiert und mit der
nötigen Funktionalität im Sinne der Geschäftlogik versehen.
4.2.3.1. Service-Klasse
Die Service-Klasse implementiert die Service-Schnittstelle (siehe 2.2.4) und die HashMaps zur
Aufnahme der Konten- und Buchungs-Objekte. Die ID für neue Buchungen wird mit dem
Feld nextID einfach hochgezählt.
package de.fhr.vs_iw.local;
public final class AccountingService implements IAccountingService {
private
= new
private
= new
Map<Integer, AccountingEntry> accountingEntries
HashMap<Integer, AccountingEntry>();
Map<Integer, Account> accounts
HashMap<Integer, Account>();
private int nextId = 0;
private AccountingService() {}
public Double calculateBalance(Integer number)
throws AccountingException, IOException {
return ((Account)getAccountByNumber(number)).balance();
}
public void createAccount(Integer number, AccountType type,
String description) throws AccountingException {
if (number <= 0) {
throw new AccountingException("Die Nummer darf nicht negativ sein");
}
synchronized (accounts) {
checkAccountNumber(number, false);
Account acc = new Account(this, number, description, type);
accounts.put(number, acc);
}
}
public Integer createAccountingEntry(Integer debitAccount,
Integer creditAccount, Double amount, String text)
throws AccountingException {
synchronized (accountingEntries) {
Integer id = nextId++;
AccountingEntry = new AccountingEntry(this, id, debitAccount,
creditAccount, amount, text);
accountingEntries.put(id, entry);
((Account)getAccountByNumber(debitAccount)).addDebitEntry(entry);
((Account)getAccountByNumber(creditAccount)).addCreditEntry(entry);
}
return id;
}
- 65 -
Datenhaltung - Einfache lokale Datenhaltung
public IAccount getAccountByNumber(Integer number)
throws AccountingException {
synchronized (accounts) {
checkAccountNumber(number, true);
return accounts.get(number);
}
}
public Set<Integer> getAccountingEntries() {
synchronized (accountingEntries) {
return accountingEntries.keySet();
}
}
public IAccountingEntry getAccountingEntryById(Integer id)
throws AccountingException {
synchronized (accountingEntries) {
IAccountingEntry entry = accountingEntries.get(id);
if (entry == null) {
throw new AccountingException("Buchung nicht vorhanden");
}
return entry;
}
}
public Set<Integer> getAccounts() {
synchronized (accounts) {
return accounts.keySet();
}
}
public void removeAccount(Integer accountNumber)
throws IOException, AccountingException {
synchronized (accountingEntries) {
checkAccountNumber(accountNumber, true);
for (IAccountingEntry entry : accountingEntries.values()) {
if (entry.getCreditAccount().equals(accountNumber)
|| entry.getDebitAccount().equals(accountNumber)) {
throw new AccountingException("Konto wird noch verwendet");
}
}
synchronized (accounts) {
accounts.remove(accountNumber);
}
}
}
public void removeAccountingEntry(Integer id)
throws IOException, AccountingException {
synchronized (accountingEntries) {
IAccountingEntry entry = getAccountingEntryById(id);
Account credit
= (Account)getAccountByNumber(entry.getCreditAccount());
Account debit
= (Account)getAccountByNumber(entry.getDebitAccount());
credit.removeCreditEntry(id);
debit.removeDebitEntry(id);
accountingEntries.remove(id);
}
}
- 66 -
Datenhaltung - Einfache lokale Datenhaltung
}
protected void checkAccountNumber(Integer number, boolean should)
throws AccountingException {
synchronized (accounts) {
if (accounts.containsKey(number)) {
if (!should) {
throw new AccountingException("Konto bereits vorhanden");
}
} else {
if (should) {
throw new AccountingException("Konto nicht vorhanden");
}
}
}
}
Code 39: Class AccountingService (Lokal)
Die Klasse implementiert alle spezifizierten Funktionalitäten im Sinne der Geschäftslogik und
besitzt dafür die Methode checkAccountNumber(), um den Status eines Kontos zu überprüfen und gegebenenfalls eine Ausnahme zu werfen. Das Erzeugen und Entfernen von Buchungen wird an die dabei involvierten Konten weitergegeben.
4.2.3.2. Konto-Klasse
Hier wird die Konto-Schnittstelle (siehe 2.2.1) implementiert und die Buchungen eines Kontos in den angesprochenen HashMaps verwaltet.
package de.fhr.vs_iw.local;
public final class Account implements IAccount {
private String accountDesc = null;
private Integer accountNumber = null;
private AccountType accountType = null;
private
= new
private
= new
Map<Integer, AccountingEntry> creditEntries
HashMap<Integer, AccountingEntry>();
Map<Integer, AccountingEntry> debitEntries
HashMap<Integer, AccountingEntry>();
protected Account(Integer number, String description, AccountType type) {
accountNumber = number;
accountType = type;
accountDesc = description;
}
public Set<Integer> getCreditEntries() {
return new HashSet<Integer>(creditEntries.keySet());
}
public Set<Integer> getDebitEntries() {
return new HashSet<Integer>(debitEntries.keySet());
}
public String getDescription() {
return accountDesc;
}
- 67 -
Datenhaltung - Einfache lokale Datenhaltung
public Integer getNumber() {
return accountNumber;
}
public AccountType getType() {
return accountType;
}
public void setDescription(String description) {
accountDesc = description;
}
public void setType(AccountType type) {
accountType = type;
}
protected Double balance() throws IOException {
double balance = 0.0;
for (IAccountingEntry entry : debitEntries.values()) {
balance += entry.getAmount().doubleValue();
}
for (IAccountingEntry entry : creditEntries.values()) {
balance -= entry.getAmount().doubleValue();
}
return balance;
}
protected void addCreditEntry(AccountingEntry entry) {
creditEntries.put(entry.getId(), entry);
}
protected void addDebitEntry(AccountingEntry entry) {
debitEntries.put(entry.getId(), entry);
}
protected void removeCreditEntry(Integer entryId) {
creditEntries.remove(entryId);
}
}
protected void removeDebitEntry(Integer entryId) {
debitEntries.remove(entryId);
}
Code 40: Class Account (Lokal)
Die Klasse enthält von der Konto-Schnittstelle unabhängige Methoden zur Verwaltung der
Objekte in den HashMaps:




addCreditEntry()
addDebitEntry()
removeCreditEntry()
removeDebitEntry()
- Haben-Buchung hinzufügen
- Soll-Buchung hinzufügen
- Haben-Buchung entfernen
- Soll-Buchung entfernen
Dadurch kann die Service-Klasse Informationen über die vorhandenen Buchungen weitergeben. Da die Konto-Klasse dadurch vollständige Informationen über ihre Buchungen besitzt
wird auch die Berechnung des Saldos (balance()) hier durchgeführt und von der ServiceKlasse aufgerufen.
- 68 -
Datenhaltung - Einfache lokale Datenhaltung
4.2.3.3. Buchungs-Klasse
Die Buchungs-Klasse nimmt die Daten einer Buchung auf und bietet über die BuchungsSchnittstelle Zugriff darauf.
package de.fhr.vs_iw.local;
public final class AccountingEntry implements IAccountingEntry {
private
private
private
private
private
Integer creditAccount = null;
Integer debitAccount = null;
Double entryAmount = null;
Integer entryId = null;
String entryText = null;
private AccountingService serviceClass = null;
protected AccountingEntry(AccountingService service, Integer id,
Integer debit, Integer credit, Double amount, String text)
throws AccountingException {
serviceClass = service;
setDebitAccount(debit);
setCreditAccount(credit);
entryAmount = amount;
entryId = id;
entryText = text;
}
public Double getAmount() {
return entryAmount;
}
public Integer getCreditAccount() {
return creditAccount;
}
public Integer getDebitAccount() {
return debitAccount;
}
public Integer getId() {
return entryId;
}
public String getText() {
return entryText;
}
public void setAmount(Double amount) {
entryAmount = amount;
}
public void setText(String text) {
entryText = text;
}
- 69 -
Datenhaltung - Einfache lokale Datenhaltung
public void setCreditAccount(Integer number) throws AccountingException {
serviceClass.checkAccountNumber(number, true);
if (number.equals(debitAccount)) {
throw new AccountingException("Haben-Konto und Soll-Konto identisch")
}
creditAccount = number;
}
}
public void setDebitAccount(Integer number) throws AccountingException {
serviceClass.checkAccountNumber(number, true);
if (number.equals(creditAccount)) {
throw new AccountingException("Haben-Konto und Soll-Konto identisch")
}
debitAccount = number;
}
Code 41: Class AccountingEntry (Lokal)
Über den Rückbezug zur Service-Klasse (Feld serviceClass), die im Konstruktor übergeben
wird, kann die Funktion checkAccountNumber() aufgerufen werden, um die Gültigkeit der
zugeordneten Konten zu überprüfen.
4.2.3.4. Proxy-Klasse
Die Proxy-Klasse hat hier noch kaum Funktionalität, sie muß im Prinzip nur vorhanden sein
um instanziert werden zu können.
package de.fhr.vs_iw.local;
public final class LocalProxy implements IAccountingProxy {
public LocalProxy() {}
public void close() {}
public IAccountingService connect(String[] args) throws IOException {
return new AccountingService();
}
}
Code 42: Class LocalProxy
Bei Aufruf der connect()-Methode wird lediglich eine neue Instanz der Service-Klasse erzeugt und zurückgegeben.
4.2.4. Fazit
Die erfolgte erste Implementierung der Geschäftslogik ist effektiv und einfach, jedoch die
Tatsache, daß die Daten des Systems nach dem Ende der Anwendung verloren gehen, paßt
nicht unbedingt zu den Erfordernissen der Datenhaltungsschicht. Deshalb geht es im folgenden um die dauerhafte Speicherung der Buchhaltungsdaten.
- 70 -
Datenhaltung - Java-Serialisierung
4.3. Java-Serialisierung
4.3.1. Allgemeines
Eine grundlegende Möglichkeit um Daten mit Java zu speichern, ist das Serialisieren der Objekte in eine Datei. Dazu müssen die vorhandenen Klassen des "lokalen" Moduls lediglich
etwas erweitert werden.
4.3.2. Serialisierbare Klassen
Im Prinzip muß bei der Konto-Klasse und der Buchungs-Klasse nur die Schnittstelle
java.io.Serializable implementiert werden, damit die Konten- und Buchungs-Objekte
durch einen ObjectOutputStream gespeichert und durch einen ObjectInputStream eingelesen werden können.
4.3.2.1. Konto-Klasse
Die vorhandene Konto-Klasse muß lediglich durch die Implementierung der Serializable-Schnittstelle als serialisierbar deklariert werden.
package de.fhr.vs_iw.serialize;
public final class Account implements IAccount, Serializable {
private static final long serialVersionUID = 1L;
...
}
<Fields and Methods>
Code 43: Class Account (Serialize)
Mit der Implementierung der Schnittstelle geht einher, daß die Klasse ein statisches Feld
serialVersionUID definieren sollte, das Versionsänderungen bei serialisierten Objekten angibt. So kann die Klasse mit ihren Daten von Java bereits vollständig serialisiert werden.
4.3.2.2. Buchungs-Klasse
Auch die vorhandene Buchungs-Klasse wird um die Serializable-Schnittstelle und die zugehörige Serialisierungs-ID erweitert.
package de.fhr.vs_iw.serialize;
public final class AccountingEntry
implements IAccountingEntry, Serializable {
private static final long serialVersionUID = 1L;
...
<Fields and Methods>
...
- 71 -
Datenhaltung - Java-Serialisierung
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
entryId=stream.readInt();
entryAmount=stream.readDouble();
entryText=(String)stream.readObject();
debitAccount=stream.readInt();
creditAccount=stream.readInt();
}
}
private void writeObject(java.io.ObjectOutputStream stream)
throws IOException {
stream.writeInt(entryId);
stream.writeDouble(entryAmount);
stream.writeObject(entryText);
stream.writeInt(debitAccount);
stream.writeInt(creditAccount);
}
Code 44: Class AccountingEntry (Serialize)
Hier besteht allerdings ein Problem mit dem vorhandenen Bezug zur Service-Klasse, die nicht
serialisierbar ist, und es auch nicht sein soll, und deshalb einen Fehler hervorrufen würde.
Deshalb muß die Serialisierung "manuell" durchgeführt werden, durch die Methoden


private void writeObject(java.io.ObjectOutputStream stream)
private void readObject(java.io.ObjectInputStream stream)
die vom Serialisierungs-Vorgang aufgerufen werden, wenn sie mit genau dieser Signatur vorliegen. Damit werden alle Daten eines Buchungsobjekts, außer dem Bezug zur ServiceKlasse, verarbeitet.
4.3.3. De-/Serialisierungs-Vorgang
Nachdem die Konto- und Buchungs-Klasse entsprechend vorbereitet wurden, müssen nun die
Objekte dieser Klasse in eine Datei serialisiert und wieder geladen werden.
4.3.3.1. Service-Klasse
Dazu wird die Service-Klasse um zwei Methoden zum Laden und Speichern erweitert.
protected void loadData(File file)
throws FileNotFoundException, IOException, ClassNotFoundException {
if (file.exists()) {
ObjectInputStream stream = new ObjectInputStream(
new FileInputStream(file));
nextId = stream.readInt();
accounts = (Map<Integer, Account>)stream.readObject();
accountingEntries
= (Map<Integer, AccountingEntry>)stream.readObject();
stream.close();
}
}
Code 45: Method loadData() (Serialize)
- 72 -
Datenhaltung - Java-Serialisierung
protected void storeData(File file)
throws FileNotFoundException, IOException {
ObjectOutputStream stream
= new ObjectOutputStream(new FileOutputStream(file));
stream.writeInt(nextId);
stream.writeObject(accounts);
stream.writeObject(accountingEntries);
stream.close();
}
Code 46: Method storeData() (Serialize)
Die Methode loadData() lädt die serialisierten Objekte aus der angegebenen Datei, die vorher durch storeData() gespeichert wurden. Dabei werden die HashMaps der Konten und
Buchungen als Einheit behandelt und auf einmal verarbeitet. Java übernimmt automatisch die
Verarbeitung der enthaltenen Objekte. Außerdem wird die ID der nächsten Buchung (nextId)
mitgespeichert, damit der Zustand des Systems nach dem Laden wieder konsistent ist.
4.3.3.2. Proxy-Klasse
Die Proxy-Klasse muß nun feststellen in welcher Datei die Objekte abgelegt werden sollen
und damit die Methoden zum Laden und Speichern der Service-Klasse aufrufen.
package de.fhr.vs_iw.serialize;
public final class SerializeProxy implements IAccountingProxy {
private AccountingService service = null;
private File file = null;
public void close() throws IOException {
service.storeData(file);
service = null;
file = null;
}
}
public IAccountingService connect(String[] args) throws IOException {
if (args.length <= 0) {
throw new IllegalArgumentException("Datei muß angegeben werden");
}
file = new File(args[0]);
service = new AccountingService();
try {
service.loadData(file);
} catch (ClassNotFoundException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
return service;
}
Code 47: Class SerializeProxy
- 73 -
Datenhaltung - Java-Serialisierung
4.3.4. Fazit
Die Serialisierung von Java-Objekten stellt zwar eine einfache Möglichkeit dar, Daten zu
speichern, zumal auch ohne großartiges Zutun Objekthierarchien verarbeitet werden können.
Auch der Zugriff auf die Daten erfolgt sehr schnell, da alle Daten während der Verwendung
des Systems im Speicher gehalten werden, und die De-/Serialisierung nur am Anfang bzw. am
Ende erfolgt.
Für eine dauerhafte Verarbeitung von Daten ist dieses Verfahren jedoch nicht geeignet, da die
serialisierten Objekte an die Implementierung der Klassen zum Zeitpunkt der Serialisierung
gebunden sind, und schnell eine Situation auftreten kann, in der vorhandene Daten nicht wieder eingelesen werden können, wenn an Klassen funktionale Änderungen durchgeführt worden sind. Das System ist daher zu unflexibel, weil die Implementierung der Geschäftslogik
und die letztendliche Datenspeicherung zu sehr ineinander verwoben sind.
- 74 -
Datenhaltung - XML-Serialisierung über JAXB
4.4. XML-Serialisierung über JAXB
4.4.1. Allgemeines
Die Java Architecture für XML Binding (JAXB) [69] ist ein Framework, um die Daten aus
einem XML-Schema an Java-Klassen zu binden und sogar aus einer XML-Schema-Instanz
heraus zu erzeugen. Dadurch kann mit XML-Daten direkt über Java-Objekte gearbeitet werden, und ein Entwickler muß nicht manuell auf XML-Parsing-Technologien wie DOM (Document Object Model) oder SAX (Simple API for XML) zurückgreifen [70].
Das Framework ist als Teil des Java-Web-Service-Developer-Pack von Sun unter
http://java.sun.com/webservices/downloads/webservicespack.html
oder separat als Referenz-Implementierung unter
https://jaxb.dev.java.net/
erhältlich. Ein Satz von Bibliotheken aus dem jeweiligen Paket muß zum CLASSPATH der Anwendung hinzugefügt werden.




jaxb1-impl.jar
jaxb-api.jar
jaxb-impl.jar
jaxb-xjc.jar
Die Bibliotheken enthalten die notwendigen Schnittstellen, Annotations und Werkzeuge zur
Verwendung von JAXB und der automatischen Erzeugung der notwendigen Artefakte und
Dateien.
4.4.2. Annotations
Die Verbindung des XML-Schemas zu den Java-Klassen wird durch JSR-250-Annotations
[65] aus dem Paket javax.xml.bind.annotations [71] im Quellcode beschrieben. Standardmäßig werden die öffentlichen Eigenschaften einer Klasse, für die entsprechende GetterMethoden vorhanden sind (JavaBean-Style) und einen bekannten Datentyp zurückgeben in
das XML-Schema abgebildet, auch wenn diese Eigenschaften nicht explizit durch Annotations ausgezeichnet sind.
Ausgehend von einer bestimmten Klasse als Wurzel-Element wird dadurch eine beliebige
komplexe Objekthierarchie auf eine XML-Datei abgebildet. Durch die Annotations wird dieser Vorgang gesteuert.
4.4.2.1. Package-Annotations
Die erste Gruppe von XML-Annotations hat für eine gesamte Java-Package Gültigkeit und
beschreibt allgemeine Einstellung für die Zuordnung zum XML-Schema und die Verarbeitung der Java-Klassen.
- 75 -
Datenhaltung - XML-Serialisierung über JAXB
Annotation
@XmlSchema
Beschreibung und Standardwerte
Ordnet ein Java-Paket einem XML-Namensraum zu.
@XmlSchema (
xmlns = {},
namespace = "",
elementFormDefault = XmlNsForm.UNSET;
attributeFormDefault = XmlNsForm.UNSET,
)
Gibt die standardmäßige Verarbeitungsvariante für Felder und
Eigenschaften an.
@XmlAccessorType
@XmlAccessorType (
value = AccessType.PUBLIC_MEMBER
)
Stellt die Anordnung der XML-Elemente für Felder und Eigenschaften einer Klasse ein.
@XmlAccessorOrder
@XmlAccessorOrder (
value = AccessorOrder.UNDEFINED
)
Eine eigene Zuordnung zu einem integrierten XML-Schema-Typ.
@XmlSchemaType
@XmlSchemaTypes
@XmlSchemaType (
namespace = "http://www.w3.org/2001/XMLSchema",
type = DEFAULT.class
)
Faßt mehrere @XmlSchemaType Annotations zusammen.
Tabelle 4: JAXB Paket-Annotations
4.4.2.2. Klassen-Annotations
Eine Java-Klasse wird auf einen komplexen Typ des XML-Schemas abgebildet, wobei eine
Klasse dabei als Wurzel des XML-Baumes bzw. als "Document-Element" beschreiben wird.
Annotation
@XmlType
Beschreibung und Standardwerte
Bildet eine Java-Klasse auf einen Schema
@XmlType (
name = "##default",
propOrder = {""},
namespace = "##default" ,
factoryClass = DEFAULT.class,
factoryMethod = ""
)
Definiert eine Klasse als Wurzel-Element des Schemas.
@XmlRootElement
@XmlRootElement (
name = "##default",
namespace = "##default"
)
Tabelle 5: JAXB Klassen-Annotations
- 76 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.2.3. Enum-Annotations
Die folgenden Annotations können bei einer Java-Enumeration verwendet werden.
Annotation
Beschreibung und Standardwerte
Ordnet eine Enumeratioen einem einfachen XML-Typ zu.
@XmlEnum
@XmlEnum (
value = String.class
)
Bildet eine einzelne Konstante einer Enumeration in XML ab.
@XmlEnumValue
@XmlEnumValue (
value = "##default"
)
Bildet eine Enumeration durch einen komplexen Typ im
XML-Schema ab.
@XmlType
@XmlType (
name = "##default",
propOrder = {""},
namespace = "##default" ,
factoryClass = DEFAULT.class,
factoryMethod = ""
)
Gibt die Enumeration als Wurzel-Element an.
@XmlRootElement
@XmlRootElement (
name = "##default",
namespace = "##default"
)
Tabelle 6: JAXB Enum-Annotations
4.4.2.4. Field-Annotations
Die Eigenschaften einer Klasse (Getter-Methoden) bzw. die Felder werden durch die folgenden Annotations ausgezeichnet.
Annotation
@XmlElement
@XmlElements
Beschreibung und Standardwerte
Bildet ein Feld als fest benanntes XML-Element ab.
@XmlElement (
name = "##default",
nillable = false,
namespace = "##default",
type = DEFAULT.class,
defaultValue = "\u0000"
)
Faßt mehrere @XmlElement Annotations zusammen.
- 77 -
Datenhaltung - XML-Serialisierung über JAXB
Bildet ein Feld als dynamisch zugeordnetes XML-Element ab.
@XmlElementRef
@XmlElementRefs
@XmlElementRef (
name = "##default",
namespace = "##default",
type = DEFAULT.class,
)
Faßt mehrere @XmlElementRef Annotations zusammen.
Wird für eine Liste verwendet, die XML-Elemente aufnimmt,
die keinem Feld direkt zugeordnet werden können.
@XmlAnyElement
@XmlAnyElement (
lax = false,
value = W3CDomHandler.class
)
Ordnet ein Feld einem XML-Attribut zu.
@XmlAttribute
@XmlAnyAttribute
@XmlTransient
@XmlValue
@XmlID
@XmlIDREF
@XmlList
@XmlMixed
@XmlMimeType
@XmlAttachmentRef
@XmlInlineBinaryData
@XmlElementWrapper
@XmlAttribute (
name = ##default,
required = false,
namespace = "##default"
)
Wird für eine Liste verwendet, die XML-Attribute aufnimmt,
die keinem Feld direkt zugeordnet werden können.
Verhindert daß ein Feld im XML-Schema abgebildet wird.
Ordnet ein Feld einem einfachen XML-Typ zu.
Verwendet ein Feld als XML-ID.
Verwendet ein Feld als Referenz auf eine XML-ID.
Bildet die Objekte einer Kollektion nicht als XML-Elemente,
sondern als zusammengesetzte Liste ihrer Werte ab.
Gibt ein Feld zur Aufnahme von XML-Mixed-Content an.
Assoziiert einen MIME-Typ mit einem Feld.
Spezifiziert ein Feld als URI zu einem MIME-Anhang.
Gibt an, daß binäre Daten in base64-codierter Form direkt im
XML-Element stehen sollen, und nicht XOP-codiert werden
sollen.
Bewirkt die Erstellung eines zusätzlichen Elements um die
XML-Darstellung einer Java-Klasse herum. Wird typischerweise bei Kollektionen verwendet.
@XmlElementWrapper (
name = "##default",
namespace = "##default",
nillable = false
)
Tabelle 7: JAXB Feld-Annotations
- 78 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.2.5. Objekt-Fabrik-Annotations
Die folgenden Annotations zeichnen eine eigene Objekt-Fabrik aus, die Objekte für bestimmte XML-Elemente mit Daten instanzieren soll.
Annotation
@XmlRegistry
@XmlElementDecl
Beschreibung und Standardwerte
Markiert eine Klasse als Objekt-Fabrik zur Instanzierung von
Objekten für XML-Elemente.
Ordnet eine Fabrik-Methode einem XML-Element zu.
@XmlElementDecl (
scope = GLOBAL.class,
namespace = "##default",
substitutionHeadNamespace = "##default",
substitutionHeadName = ""
)
Tabelle 8: JAXB Objekt-Fabrik-Annotations
4.4.2.6. Adapter-Annotations
Mit diesen Annotations werden eigene Adapter-Klassen für die XML-Verarbeitung definiert.
Annotation
@XmlJavaTypeAdapter
@XmlJavaTypeAdapters
Beschreibung und Standardwerte
Verwendet eine Adapter-Klasse, die die Schnittstelle
XmlAdapter implementiert, um eine Java-Klasse manuell auf
das XML-Schema abzubilden.
@XmlJavaTypeAdapter (
type = DEFAULT.class
)
Faßt mehrere @XmlJavaTypeAdapter Annotations zusammen.
Tabelle 9: JAXB Adapter-Annotations
4.4.3. Anwendung der Annotations bei den Buchhaltungs-Klassen
Die beschrieben Annotations werden nun auf die bereits vorhandenen Klassen aus dem lokalen Modul angewandt. Die Implementierung der Geschäftslogik wird direkt übernommen, und
es werden eine nötige Erweiterungen der Klassen durchgeführt.
4.4.3.1. Service-Klasse
Die Service-Klasse wird als @XmlRootElement gekennzeichnet. Wichtig ist die zusätzliche
Angabe der Ordnung der Elemente. Die Konten müssen vor den Buchungen verarbeitet werden, damit es beim Einlesen nicht zu Ausnahmen kommt. Für die HashMaps werden Getter
und Setter-Methoden erzeugt, wodurch sie von JAXB bereits verarbeitet werden können. Außerdem ist noch ein Standard-Konstruktor ohne Argumente nötig. Die ID der nächsten Buchung (nextId) wird als XML-Attribut (@XmlAttribute) gespeichert.
- 79 -
Datenhaltung - XML-Serialisierung über JAXB
package de.fhr.vs_iw.jaxb.v1;
@XmlRootElement
@XmlType(propOrder = { "accountMap", "accountingEntryMap" })
public final class AccountingService implements IAccountingService {
private
= new
private
= new
Map<Integer, AccountingEntry> accountingEntryMap
HashMap<Integer, AccountingEntry>();
Map<Integer, Account> accountMap
HashMap<Integer, Account>();
@XmlAttribute
private int nextId = 0;
@XmlID
@XmlAttribute
@SuppressWarnings("unused")
private final String xmlid = "service";
public AccountingService() {}
public Map<Integer, AccountingEntry> getAccountingEntryMap();
public Map<Integer, Account> getAccountMap();
public void setAccountingEntryMap(
Map<Integer, AccountingEntry> accountingEntryMap);
public void setAccountMap(Map<Integer, Account> accountMap);
}
...
Code 48: Class AccountingService (JAXB - Variante 1)
Ein zusätzliches Datenfeld muß noch eingeführt werden, und zwar eine XML-ID, die später
als Referenz für den notwendigen Rückbezug der Buchungen zu einer Service-Instanz dient.
Diese ID muß ein String sein, wird als @XmlID gekennzeichnet und als Attribut gespeichert.
4.4.3.2. Buchungs-Klasse
Auch die bereits vorhandene Buchungsklasse wird im Prinzip direkt übernommen und lediglich um einige Punkte erweitert.
package de.fhr.vs_iw.jaxb.v1;
public final class AccountingEntry implements IAccountingEntry {
@XmlIDREF
@XmlAttribute
private AccountingService service = null;
public AccountingEntry() {}
public void setId(Integer id);
public void afterUnmarshal(Unmarshaller u, Object parent);
}
...
Code 49: Class AccountingEntry (JAXB - Variante 1)
- 80 -
Datenhaltung - XML-Serialisierung über JAXB
Durch @XmlIDREF wird der Bezug zur Service-Klasse im XML-Schema abgebildet. Ansonsten muß nur noch ein Standard-Konstruktor und eine Setter-Methode für die ID hinzugefügt
werden. Da von der Buchungs-Schnittstelle her bereits für alle Felder öffentliche Getter- und
Setter-Methoden vorhanden sind, können alle Eigenschaften der Klasse bereits von JAXB
verarbeitet werden.
Da die Buchungs-Klasse als Wert-Typ in der Buchungs-HashMap der Service-Klasse verwendet wird, wird sie im XML-Schema implizit als XML-Typ (@XmlType) behandelt, auch ohne
daß die Annotation explizit vorhanden ist.
4.4.3.3. Callback-Methoden
Neben den semantischen Erweiterungen der Buchungs-Klasse ist auch noch eine rein funktionale Erweitung notwendig. Da im lokalen Buchhaltungsmodul eine mehrfache Datenspeicherung, der Buchungen, sowohl in der Service-Klasse, als auch in Soll- und Haben-Buchungen
getrennt beim jedem Konto erfolgt, muß auch bei den Konten diese Zuordnung, beim Einlesen der Buchhaltungsdaten aus einer XML-Datei, wiederhergestellt werden. Dazu wird eine
Callback-Methode verwendet.
public void afterUnmarshal(Unmarshaller u, Object parent)
throws AccountingException {
((Account)service.getAccountByNumber(debitAccount)).addDebitEntry(this);
((Account)service.getAccountByNumber(creditAccount)).addCreditEntry(this);
}
Code 50: Methode afterUnmarshal()
Wenn diese Methode in einer Klasse vorhanden ist, so wird sie von JAXB immer nach dem
Erzeugen eines neuen Objekts dieser Klasse und dem Setzen aller Eigenschaften aufgerufen.
Diese Funktionalität wird in der Buchungs-Klasse genutzt, um die Buchung jeweils beim zugehörigen Soll- und Haben-Konto einzutragen, genauso wie beim Erfassen einer neuen Buchung.
Es gibt entsprechend noch weitere Callback-Methoden, die in einer Klasse für die Verarbeitung mit JAXB verwendet werden können, in diesem Fall aber nicht benötigt werden.
// Wird direkt nach dem Instanzieren eines "leeren" Objekts aufgerufen
public void beforeUnmarshal(Unmarshaller u, Object parent);
// Wird vor dem Auslesen der Eigenschaften eines Objekts aufgerufen
public boolean beforeMarshal(Marshaller m, Object parent);
// Wird nach der Verarbeitung zu einem XML-Element aufgerufen
public void afterMmarshal(Marshaller m, Object parent);
Das Object parent ist dabei immer eine zugeordnete Objektinstanz, die dem gerade verarbeiteten Element im XML-Baum übergeordnet ist, und deren Verarbeitung je nach Aufbau
des Schemas und des aktuellen Vorgangs unter Umständen noch nicht abgeschlossen wurde.
- 81 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.3.4. Konto-Klasse
Die Konto-Klasse wird wie die Buchungs-Klasse nur übernommen und angepaßt. Da die
komplexen Funktionalitäten, die bei den Buchhaltungsdaten notwendig sind, bereits vollständig von der Buchungs-Klasse abgedeckt werden, ist hier kaum Aufwand nötig.
package de.fhr.vs_iw.jaxb.v1;
public final class Account implements IAccount {
public Account() {}
public void setNumber(Integer number);
}
...
Code 51: Class Account (JAXB - Variante 1)
Die Klasse wird um den fehlenden Standard-Konstruktor und die Setter-Methode für die Kontonummer erweitert, was bereits ausreicht, damit alle Daten eines Kontos in das Schema aufgenommen werden können.
4.4.4. Erzeugung des XSD-Schemas aus den annotierten Klassen
Eigentlich könnten Daten mit den so annotierten Klassen bereits in einer XML-Datei abgebildet werden. Günstiger ist es jedoch, zuerst ein XSD-Schema aus den Klassen erzeugen zu
lassen. Dadurch wird die Korrektheit der Annotations überprüft, und das erzeugte Schema
kann später Verwendet werden, um eine XML-Datei mit Buchhaltungsdaten vor dem Einlesen zu validieren.
4.4.4.1. Generierung durch einen Ant-Vorgang
durch
einen
entsprechenden
Ant-Vorgang
Erzeugt
wird
das
Schema
(com.sun.tools.jxc.SchemaGenTask), der in den JAXB-Bibliotheken enthalten ist.
<?xml version="1.0" standalone="yes"?>
<project basedir=".." default="generate-schema">
<property file="${basedir}/ant/build.properties" />
<target name="generate-schema">
<path id="classpath">...</path>
<taskdef name="schemagen" classname="com.sun.tools.jxc.SchemaGenTask"
classpathref="classpath" />
<delete file="${basedir}/generated/de/fhr/vs_iw/jaxb/schema-v1.xsd" />
<schemagen srcdir="${basedir}/src/de/fhr/vs_iw/jaxb/v1/"
destdir="${basedir}/generated/de/fhr/vs_iw/jaxb/">
<schema namespace="" file="schema-v1.xsd" />
<classpath refid="classpath" />
<include name="AccountingService.java" />
</schemagen>
</target>
</project>
Resource 1: Ant-Build-Datei jaxb-generate-schema-v1.xml
- 82 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.4.2. Das Schema zu den HashMaps
Das erzeugte Schema bildet die Java-HashMaps jeweils als paarweise XML-Elemente <key>
und <value> mit dem entsprechenden XML-Typ ab.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="accountingService" type="accountingService"/>
<xs:complexType name="accountingService" final="extension restriction">
<xs:sequence>
<xs:element name="accountMap">
<xs:complexType>
<xs:sequence>
<xs:element name="entry" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="key" minOccurs="0" type="xs:int"/>
<xs:element name="value" minOccurs="0" type="account"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="accountingEntryMap">
<xs:complexType>
<xs:sequence>
<xs:element name="entry" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="key" minOccurs="0" type="xs:int"/>
<xs:element name="value" minOccurs="0"
type="accountingEntry"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="xmlid" type="xs:ID"/>
</xs:complexType>
<xs:complexType name="account" final="extension restriction">
<xs:sequence>
<xs:element name="description" type="xs:string" minOccurs="0"/>
<xs:element name="number" type="xs:int" minOccurs="0"/>
<xs:element name="type" type="accountType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="accountingEntry" final="extension restriction">
<xs:sequence>
<xs:element name="amount" type="xs:double" minOccurs="0"/>
<xs:element name="creditAccount" type="xs:int" minOccurs="0"/>
<xs:element name="debitAccount" type="xs:int" minOccurs="0"/>
<xs:element name="id" type="xs:int" minOccurs="0"/>
<xs:element name="text" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="service" type="xs:IDREF"/>
</xs:complexType>
- 83 -
Datenhaltung - XML-Serialisierung über JAXB
<xs:simpleType name="accountType">
<xs:restriction base="xs:string">
<xs:enumeration value="PROCEEDS"/>
<xs:enumeration value="PASSIVE"/>
<xs:enumeration value="EXPENSE"/>
<xs:enumeration value="AMOUNT"/>
<xs:enumeration value="ACTIVE"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Resource 2: schema-v1.xsd
4.4.4.3. Resultierende XML-Datei
Aus diesem Schema ergibt sich vom Aufbau her folgende XML-Datei.
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<accountingService xmlid="service">
<accountMap>
<entry>
<key>2800</key>
<value>
<description>Bank</description>
<number>2800</number>
<type>ACTIVE</type>
</value>
</entry>
...
</accountMap>
<accountingEntryMap>
<entry>
<key>2</key>
<value service="service">
<amount>100.0</amount>
<creditAccount>4400</creditAccount>
<debitAccount>6000</debitAccount>
<id>2</id>
<text>Kauf von Rohstoffen 1</text>
</value>
</entry>
...
</accountingEntryMap>
</accountingService>
Resource 3: fibu-v1.xml
4.4.4.4. Probleme
Die HashMaps sind eine etwas problematische Angelegenheit, da die XML-Struktur unnötig
komplex wird. Außerdem hat die aktuelle Version 2 von JAXB einen Fehler, der erst in Version 3 behoben werden soll, durch den HashMaps nur dann richtig verarbeitet werden, wenn
sie public (Feld oder Getter/Setter) und nicht mit Annotations versehen sind [72].
- 84 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.5. Implementierung der Klassen nach einem vorgegebenen Schema
Wegen den Problemen mit den HashMaps werden im zweiten Ansatz die Klassen auf eine
andere Weise neu implementiert, die für die Verarbeitung mit JAXB besser geeignet ist und
eine einfachere XML-Struktur ergibt.
4.4.5.1. Service-Klasse
Der wesentliche Aufbau der Service-Klasse entspricht der ersten Variante. Die Annotations
für JAXB (@XmlRootElement, @XmlType(propOrder...), @XmlID, @XmlAttribute) sind
gleichermaßen vorhanden, aber die HashMaps werden durch Set bzw. HashSet (möglich wäre
auch List) ersetzt und als @XmlElement ausgezeichnet.
package de.fhr.vs_iw.jaxb;
@XmlRootElement
@XmlType(propOrder = { "accountSet", "accountingEntrySet" })
public final class AccountingService implements IAccountingService {
@XmlAttribute
private int nextId = 0;
@XmlID
@XmlAttribute
private String xmlid = "service";
@XmlElement(name = "accountingEntry")
protected Set<AccountingEntry> accountingEntrySet
= new HashSet<AccountingEntry>();
@XmlElement(name = "account")
protected Set<Account> accountSet = new HashSet<Account>();
public AccountingService() {}
...
public IAccount getAccountByNumber(Integer number)
throws AccountingException {
checkAccountNumber(number, true);
for (Account account : accountSet) {
if (account.getNumber().equals(number)) {
return account;
}
}
return null;
}
}
...
Code 52: Class AccountingService (JAXB - Variante 2)
Der Nachteil ist natürlich, daß fast alle Methoden neu implementiert werden müssen, und auf
Buchungs- und Kontenobjekte nicht mehr direkt über einen Identifikator zugegriffen werden
kann. Die beiden HashSets müssen jeweils manuell nach einem bestimmten Objekt in einer
Schleife durchsucht werden.
- 85 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.5.2. Konto-Klasse
Die Konto-Klasse wird im Vergleich zur vorigen Variante etwas stärker abgeändert. Sie soll
keine eigenen HashMaps für die Zuordnung der Soll- und Haben-Buchungen mehr enthalten,
um die redundante Verwaltung dieser Informationen zu vermeiden. Dafür erhält nun auch
diese Klasse einen Rückbezug (@XmlIDREF) zu einer Instanz der Service-Klasse.
package de.fhr.vs_iw.jaxb.v2;
public final class Account implements IAccount {
@XmlIDREF
@XmlAttribute
private AccountingService service = null;
public Account() {}
public Set<Integer> getCreditEntries() {
Set<Integer> credit = new HashSet<Integer>();
for (AccountingEntry entry : service.accountingEntrySet) {
if (entry.getCreditAccount().equals(number)) {
credit.add(entry.getId());
}
}
return credit;
}
public Set<Integer> getDebitEntries() {
Set<Integer> debit = new HashSet<Integer>();
for (AccountingEntry entry : service.accountingEntrySet) {
if (entry.getDebitAccount().equals(number)) {
debit.add(entry.getId());
}
}
return debit;
}
}
...
Code 53: Class Account (JAXB - Variante 2)
Zum Abruf der Soll- und Haben-Buchungen wird auf den Set der Buchungen in der ServiceKlasse zurückgegriffen und jeweils die entsprechenden Buchungen herausgesucht. Dies bedeutet zwar zur Laufzeit einen etwas höheren Aufwand, vor allem wenn die Anzahl der Buchungen zunimmt, aber wie bereits erwähnt soll das Ziel dieser Implementierung eine einfachere XML-Struktur sein.
4.4.5.3. Buchungs-Klasse
Bei der neuen Buchungs-Klasse ändert sich am wenigsten. Hier wird lediglich die CallbackMethode afterUnmarshall() wieder entfernt, da Aufgrund der nun nicht mehr vorhandenen
HashMaps in der Konto-Klasse die Buchungen nach dem Laden in den Set der Service-Klasse
nicht nochmals zugeordnet werden müssen.
- 86 -
Datenhaltung - XML-Serialisierung über JAXB
package de.fhr.vs_iw.jaxb.v2;
public final class AccountingEntry implements IAccountingEntry {
@XmlIDREF
@XmlAttribute
private AccountingService service = null;
public AccountingEntry();
public void setId(Integer id);
...
}
Code 54: Class AccountingEntry (JAXB - Variante 2)
Da beim Laden einer Buchung aus der XML-Datei die angegebenen Konten aber immer noch
in den Setter-Methoden überprüft werden, ist es weiterhin wichtig daß die Konten vor den
Buchungen in der XML-Datei stehen, was bei der Service-Klasse auch angegeben wurde.
4.4.5.4. Schema und Daten
Zur Erzeugung des neuen Schemas wird im Prinzip derselbe Ant-Vorgang wie vorher verwendet (siehe 4.4.4.1) nur mit dem anderen Paket als Quelle (de.fhr.vs_iw.jaxb.v2) und
einem anderen Namen für die Schema-Datei (schema-v2.xsd). Das erzeugte Schema ist dann
durchaus weniger komplex als im vorigen Fall.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="accountingService" type="accountingService"/>
<xs:complexType name="accountingService" final="extension restriction">
<xs:sequence>
<xs:element name="account" type="account" maxOccurs="unbounded"
minOccurs="0"/>
<xs:element name="accountingEntry" type="accountingEntry"
maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="xmlid" type="xs:ID"/>
<xs:attribute name="nextId" type="xs:int" use="required"/>
</xs:complexType>
<xs:complexType name="account" final="extension restriction">
<xs:sequence>
<xs:element name="description" type="xs:string" minOccurs="0"/>
<xs:element name="number" type="xs:int" minOccurs="0"/>
<xs:element name="type" type="accountType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="service" type="xs:IDREF"/>
</xs:complexType>
<xs:complexType name="accountingEntry" final="extension restriction">
<xs:sequence>
<xs:element name="amount" type="xs:double" minOccurs="0"/>
<xs:element name="creditAccount" type="xs:int" minOccurs="0"/>
<xs:element name="debitAccount" type="xs:int" minOccurs="0"/>
<xs:element name="id" type="xs:int" minOccurs="0"/>
<xs:element name="text" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="service" type="xs:IDREF"/>
</xs:complexType>
- 87 -
Datenhaltung - XML-Serialisierung über JAXB
<xs:simpleType name="accountType">
<xs:restriction base="xs:string">
<xs:enumeration value="PROCEEDS"/>
<xs:enumeration value="PASSIVE"/>
<xs:enumeration value="EXPENSE"/>
<xs:enumeration value="AMOUNT"/>
<xs:enumeration value="ACTIVE"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Resource 4: schema-v2.xsd
Es basiert nun nur noch auf den zugehörigen XML-Elementen und XML-Typen zu den drei
Klassen der Buchhaltung und kommt ohne zusätzliche oder zum Teil gar überflüssige Elemente aus. Daraus ergibt sich nach dem Speichern von Buchhaltungsdaten eine XML-Datei
mit dem folgenden Aufbau.
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<accountingService nextId="12" xmlid="service">
<account service="service">
<description>Bank</description>
<number>2800</number>
<type>ACTIVE</type>
</account>
<account service="service">
<description>Schlussbilanzkonto</description>
<number>8010</number>
<type>AMOUNT</type>
</account>
...
<accountingEntry service="service">
<amount>10000.0</amount>
<creditAccount>2400</creditAccount>
<debitAccount>2800</debitAccount>
<id>9</id>
<text>Bezahlung aus Verkauf 3</text>
</accountingEntry>
<accountingEntry service="service">
<amount>100.0</amount>
<creditAccount>4400</creditAccount>
<debitAccount>6000</debitAccount>
<id>10</id>
<text>Kauf von Rohstoffen 3</text>
</accountingEntry>
...
</accountingService>
Resource 5: fibu-v2.xml
Diese XML-Datei enthält nun nur noch die wirklich relevanten Elemente für die Buchhaltungsdaten, was auch einen Kernpunkt bei der Arbeit mit JAXB darstellt. Die Philosophie von
JAXB geht eher in die Richtung, eine bestimmte XML-Struktur mit einem günstigen Schema
vorzugeben, und die Java-Klassen zur Verarbeitung der XML-Daten an das Schema anzupassen und nicht umgekehrt. JAXB würde auch die Möglichkeit bieten die Klassen aus einem
XSD-Schema automatisiert zu generieren.
- 88 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.6. Proxy- und Utility-Klasse
Nachdem nun das XML-Schema und die Zuordnung der XML-Daten zu Java-Klassen und
Objekten definiert wurde, müssen die Daten noch eine XML-Datei gespeichert bzw. daraus
gelesen werden.
4.4.6.1. Proxy-Klasse
Eine angegebene XML-Datei mit Daten wird in der connect()-Methode der JAXB-ProxyKlasse ausgelesen, sofern sie vorhanden ist. Die Proxy-Klasse liefert eine Instanz der ServiceKlasse zurück, mit der dann das Buchhaltungssystem wie gewohnt arbeiten kann. Die Daten
werden nur im Speicher der VM gehalten. Erst in der close()-Methode werden die Instanz
der Service-Klasse und damit alle Daten in die XML-Datei zurückgeschrieben.
package de.fhr.vs_iw.jaxb.v1;
public final class JAXBProxy1 implements IAccountingProxy {
private String filename = null;
private AccountingService service = null;
public JAXBProxy1() {}
public void close() throws IOException {
if (service != null && filename != null) {
try {
JAXBUtil.writeXML(AccountingService.class, service, filename);
} catch (Exception e) {
IOException ioe = new IOException();
ioe.initCause(e);
throw ioe;
}
}
}
}
public IAccountingService connect(String[] args) throws IOException {
if (args.length <= 0) {
throw new IllegalArgumentException("Ein Dateiname muß "
+ "als erstes Arguemnt angegeben werden");
}
filename = args[0];
if (!(new File(filename).exists())) {
service = new AccountingService();
} else {
try {
service = JAXBUtil.readXML(AccountingService.class,
filename, "schema-v1.xsd");
} catch (Exception e) {
IOException ioe = new IOException();
ioe.initCause(e);
throw ioe;
}
}
return service;
}
Code 55: Class JAXBProxy1
- 89 -
Datenhaltung - XML-Serialisierung über JAXB
Die Klasse JAXBProxy2 der zweiten Implementierungs-Variante ist grundsätzlich völlig identisch, nur daß sie zu einem anderen Java-Paket gehört und daher die andere Service-Klasse,
eben der zweiten Variante verwendet.
Jede der beiden Proxy-Klassen verwendet die Methoden der Utility-Klassen zum eigentlichen
Zugriff auf die Daten.
4.4.6.2. Utility-Klasse
Die notwendigen Aufrufe der JAXB-Klassen zur Laufzeit werden in den Methoden der Klasse JAXBUtil ausgeführt.
package de.fhr.vs_iw.jaxb;
public final class JAXBUtil {
public static <T> T readXML(Class<T> clas, String filename,
String schemaname) throws SAXException, JAXBException {
public static void writeXML(Class clas, Object object, String filename)
throws FileNotFoundException, JAXBException {
private static Schema getSchema(String schemaResourceName)
throws SAXException {
}
Code 56: Class JAXBUtil
Die Methoden dieser Klasse werden von den Proxy-Klassen zur Verarbeitung der Buchhaltungsdaten in den XML-Dateien aufgerufen.
4.4.6.3. Speichern der Objekte
In der Methode writeXML() der Utility-Klasse werden die Daten Objekte der Buchhaltungsklassen in eine XML-Datei gespeichert.
public static void writeXML(Class clas, Object object,
String filename) throws FileNotFoundException, JAXBException {
JAXBContext cont = JAXBContext.newInstance(clas);
Marshaller marshaller = cont.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1");
PrintStream ps = new PrintStream(new File(filename));
marshaller.marshal(object, ps);
ps.close();
}
Code 57: Method writeXML() (JAXBUtil)
Zuerst wird der JAXB-Kontext mit der angegebenen Klasse (die Service-Klasse) instanziert.
Die angegebene Klasse wird verarbeitet, und Aufgrund ihres Aufbaus und mit den vorhandenen Annotations wird der Kontext konfiguriert. Aus dem Kontext kann ein Marshaller für
die Objekte der Klasse erzeugt werden, der die Umwandlung der Daten in das passende
XML-Format durchführt. Über einen OutputStream erfolgt die Ausgabe des Objekts in die
angegebene Datei.
- 90 -
Datenhaltung - XML-Serialisierung über JAXB
4.4.6.4. Laden der Objekte
Zum Laden von Objekten mit den Daten aus einer XML-Datei enthält die Utility-Klasse die
Methode readXML().
public static <T> T readXML(Class<T> clas, String filename,
String schemaname) throws SAXException, JAXBException {
JAXBContext ctx = JAXBContext.newInstance(clas);
Unmarshaller um = ctx.createUnmarshaller();
if (schemaname != null) {
um.setSchema(getSchema(schemaname));
}
return (T)um.unmarshal(new File(filename));
}
Code 58: Method readXML() (JAXBUtil)
Auch hier wird zuerst ein JAXB-Kontext für die angegebene Klasse konfiguriert. Über einen
Unmarshaller wird ein Objekt der angegebenen Klasse mit den Daten aus der angegebenen
XML-Datei erzeugt. Wenn der Name einer Schema-Datei angegeben wird, wird diese mit der
Methode getSchema() als Ressource geladen, und verwendet um die XML-Datei beim Einlesen zu validieren.
private static Schema getSchema(String schemaResourceName)
throws SAXException {
SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
return sf.newSchema(JAXBUtil.class.getResource(schemaResourceName));
}
Code 59: Method getSchema() (JAXBUtil)
Die Schema-Datei wird als Ressource in der Umgebung der Virtuellen Maschine gesucht und
über eine Instanz der Schema-Fabrik als Objekt zur weiteren Verwendung geladen.
4.4.7. Fazit
Die direkte Verwendung von Java-Objekten, die durch Annotations ausgezeichnet werden,
stellt für den Entwickler eine einfach zu handhabende Möglichkeit zur Datenspeicherung dar.
Allerdings kann in diesem Fall die Tatsache, daß die Datenverarbeitung eigentlich ausgehend
von einer XML-Datei und den zugehörigen Schema erfolgt, die Flexibilität beim Aufbau der
eigenen Java-Klassen einschränken.
Außerdem ist die Datenspeicherung im XML-Format zwar sehr flexibel, und die Daten können fast beliebig weiterverarbeitet werden, aber bei großen Datenmengen auch sehr ineffizient, und man muß sich um den Lade- und Speichervorgang, und die Datenintegrität, selbst
kümmern.
- 91 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
4.5. Direkter SQL-Datenbankzugriff mit JDBC
4.5.1. Allgemeines
Das Ziel jeder Datenhaltung ist heute aber in der Regel ein SQL-Datenbanksystem. Auch die
Buchhaltungsdaten sollen in einer Datenbank gespeichert werden. Zum Zugriff auf eine SQLDatenbank unter Java ist der entsprechende JDBC-Treiber, in unserem Beispiel für das
MySQL-Datenbanksystem [11], nötig, der unter
http://dev.mysql.com/downloads/connector/j/5.0.html
heruntergeladen werden kann (siehe auch 6.2.3.2), und in den aktuellen classpath aufgenommen werden muß. Auf den Betrieb des MySQL-Datenbanksystems an sich soll hier aber
nicht näher eingegangen werden, da es den Umfang dieses Tutorials übersteigen würde.
4.5.2. JDBC-Datenbankanbindung
Der Zugriff auf Datenbank erfolgt bei Java über einheitlich über die JDBC-Schnittstelle (Java Database Connectivity) [37]. Für die jeweils gewünschte Datenbank muß nur der passende
JDBC-Treiber vorhanden sein.
4.5.2.1. Verbindung aufbauen
Die Proxy-Klasse baut über die Klasse java.sql.Connection eine Verbindung zum
MySQL-Datenbanksystem auf. Dazu wird eine URL der Form
java:mysql://host[:port]/datenbankname
ein Benutzername und ein Paßwort angegeben. Der DriverManager liefert mit diesen Angaben durch Aufruf von getConnection() eine Instanz von Connection.
package de.fhr.vs_iw.mysql;
public final class MySQLProxy implements IAccountingProxy {
private Connection con = null;
public MySQLProxy() {}
public IAccountingService connect(String[] args) throws IOException {
if (args.length < 4) {
throw new IllegalArgumentException(
"use MySQLProxy host[:port] database user password");
}
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(
"jdbc:mysql://" + args[0] + "/" + args[1], args[2], args[3]);
} catch (Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
return new AccountingService(con);
}
- 92 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
}
public void close() throws IOException {
try {
con.close();
con = null;
} catch (SQLException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
Code 60: Class MySQLProxy
Durch den Aufruf von Class.forName("com.mysql.jdbc.Driver") wird der MySQLTreiber zuvor geladen, damit er zum Aufbau der Verbindung verfügbar ist. Bei JDBCZugriffen wird bei Fehlern eine SQLException geworfen, die hier in eine IOException verpackt wird, um der Schnittstellen-Spezifikation zu entsprechen (siehe 2.3.2). Die Verbindung
(Connection) wird einer neuen Instanz der Service-Klasse übergeben, damit darüber die
SQL-Anfragen und Anweisungen ausgeführt werden können.
4.5.2.2. Einzelne Werte
Für Anfragen nach einzelnen Werten wird die Service-Klasse um eine parametrische QueryMethode erweitert, die für Anfragen an die Datenbank gedacht ist, die einen einzelnen Wert
als Ergebnis zurückliefern (SELECT ... WHERE ...).
protected synchronized <T> T query(Class<T> clas, String query,
Object... args) throws IOException {
try {
PreparedStatement stmt = prepare(query, args);
ResultSet rs = stmt.executeQuery();
T result = null;
if (rs.next()) {
result = (T)rs.getObject(1);
}
rs.close();
stmt.close();
return result;
} catch (SQLException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
Code 61: Method query() (MySQL)
Die Funktion liefert den ersten Wert der ersten Ergebniszeile einer Abfrage mit dem Typ der
angegebenen Klasse (Class<T>) zurück. Beim Aufruf von getObject() eines ResultSet
wird ein Java-Objekt, das zum Datentyp der angefragten Spalte einer Tabelle in der Datenbank paßt, zurückgegeben. Wenn die SQL-Anfrage passend formuliert wird, erlaubt dies einen effizienten Zugriff auf einzelne spezifische Werte in der Datenbank.
- 93 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
4.5.2.3. Auflistungen
Die Abfrage von einzelnen Werten ist jedoch nicht ganz ausreichend, um die spezifizierten
Funktionalitäten der Buchhaltung abzudecken. Auch eine Auflistung mit mehreren Ergebnissen (SELECT ...) muß abgefragt werden können, wozu eine eigene Query-Methode dient, die
alle Zeilen eines ResultSet verarbeitet.
protected synchronized <T> HashSet<T> querySet(Class<T> clas, String query,
Object... args) throws IOException {
try {
PreparedStatement stmt = prepare(query, args);
ResultSet rs = stmt.executeQuery();
HashSet<T> set = new HashSet<T>();
while (rs.next()) {
set.add((T)rs.getObject(1));
}
rs.close();
stmt.close();
return set;
} catch (SQLException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
Code 62: Method querySet() (MySQL)
Die Methode liefert einen HashSet<T> mit Elementen des angegebenen Typs zurück. Dazu
werden die Werte aus der ersten Spalte aller Ergebniszeilen einer entsprechend formulierten
Anfrage verwendet.
4.5.2.4. Anweisungen
Schließlich gibt ein noch SQL-Befehle, die kein Ergebnis liefern (INSERT, UPDATE, DELETE).
Für diese Art von Anweisungen wird eine andere Methode verwendet.
protected synchronized void execute(String query, Object... args)
throws IOException {
try {
PreparedStatement stmt = prepare(query, args);
stmt.execute();
stmt.close();
} catch (SQLException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
Code 63: Method execute() (MySQL)
- 94 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
4.5.2.5. PreparedStatements
Alle SQL-Befehle werden über die JDBC-Klasse PreparedStatement ausgeführt, wodurch
beliebige Java-Objekte als SQL-Parameter verwendet werden.
private PreparedStatement prepare(String query, Object... args)
throws SQLException {
PreparedStatement stmt = con.prepareStatement(query);
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
return stmt;
}
Code 64: Method prepare() (MySQL)
Der Query-String sollte formuliert werden, daß er Platzhalter ? für Parameter enthält, z.B.
UPDATE accountingentry SET text=? WHERE id=?
die dann der Reihe nach mit den Werten der angegebenen Objekte gefüllt werden. Die Methode setObject() konvertiert die Java-Objekte nach internen Vorgaben in SQLDatentypen. Eine SQL-Anweisung muß natürlich nicht zwingend Parameter enthalten.
4.5.3. SQL-Anweisungen
Da nun die notwendigen Zugriffsfunktionen erstellt wurden, können die Daten des Buchhaltungssystems abgefragt und verändert werden. Die Daten stammen dann immer direkt aus der
Datenbank, die Geschäftslogik bleibt in den Klassen implementiert.
4.5.3.1. Datenbank-Schema
Zuerst muß ein Datenbank-Schema für die Buchhaltung erstellt werden. Es ist jeweils eine
Tabelle für Konten und eine Tabelle für Buchungen nötig, die mit den entsprechenden SQLBefehlen angelegt werden.
CREATE TABLE `account` (
`number` int(10) NOT NULL,
`accounttype` int(10) default NULL,
`description` varchar(255) default NULL,
PRIMARY KEY (`number`)
) Type=MyISAM;
CREATE TABLE `accountingentry` (
`id` int(10) NOT NULL auto_increment,
`text` varchar(255) default NULL,
`amount` double default NULL,
`creditaccount` int(10) default NULL,
`debitaccount` int(10) default NULL,
PRIMARY KEY (`id`)
) Type=MyISAM;
Resource 6: SQL-Befehle zum Anlegen der Konten- und Buchungs-Tabelle
- 95 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
4.5.3.2. Service-Klasse
Jede Methode der Service-Klasse führt SQL-Abfragen aus, und arbeitet immer direkt mit den
Daten der Datenbank.

calculateBalance()
SELECT SUM(amount) FROM accountingentry
WHERE debitaccount=? GROUP BY debitaccount
SELECT SUM(amount) FROM accountingentry
WHERE creditaccount=? GROUP BY creditaccount

createAccount()
INSERT INTO account SET number=?, accounttype=?, description=?

createAccountingEntry()
LOCK TABLES accountingentry WRITE
INSERT INTO accountingentry
SET debitaccount=?, creditaccount=?, amount=?, text=?
SELECT LAST_INSERT_ID() FROM accountingentry
UNLOCK TABLES

getAccountingEntries()
SELECT id FROM accountingentry

getAccounts()
SELECT number FROM account

removeAccount()
DELETE FROM account WHERE number=?

removeAccountingEntry()
DELETE FROM accountingentry WHERE id=?
Resource 7: SQL-Befehle der Service-Klasse (MySQL)
Bei der Methode createAccountingEntry() sind besondere Maßnahmen nötig, da die ID
einer neuen Buchung von der Datenbank vergeben wird. Zuerst wird die Tabelle der Buchungen gesperrt, damit keine anderen Befehle dazwischen kommen können. Nach dem Einfügen
der neuen Buchung kann mit der MySQL-Funktion LAST_INSERT_ID() die ID der neuen Buchung abgerufen werden. Danach wird die Tabelle wieder entsperrt.
Die Konten- und Buchungsobjekte werden wie vorher schon in HashMaps verwaltet und jeweils für eine bestimmte Nummer bzw. ID erzeugt und zurückgegeben.
AccountingEntry entry = accountingEntries.get(id);
if (entry == null) {
entry = new AccountingEntry(this, id);
accountingEntries.put(id, entry);
}
return entry;
Account account = accounts.get(number);
if (account == null) {
account = new Account(this, number);
accounts.put(number, account);
}
return account;
Code 65: Verwaltung der Konten- und Buchungsobjekte (MySQL)
- 96 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC
4.5.3.3. Konto-Klasse
Allerdings speichert ein Objekt der Konto-Klasse nicht wie in den vorherigen Abschnitten
seine Daten selbst, sondern lediglich die Kontonummer. Alle Daten werden direkt aus der
Kontentabelle der Datenbank geholt und auch wieder dort abgelegt, wobei die Kontonummer
als Schlüssel (... WHERE number=?) dient.

getCreditEntries()
SELECT id FROM accountingentry WHERE creditaccount=?

getDebitEntries()
SELECT id FROM accountingentry WHERE debitaccount=?

getDescription()
SELECT description FROM account WHERE number=?

getType()
SELECT accounttype FROM account WHERE number=?

setDescription()
UPDATE account SET description=? WHERE number=?

setType()
UPDATE account SET accounttype=? WHERE number=?
Resource 8: SQL-Befehle der Konto-Klasse (MySQL)
Neben der Kontonummer enthält jedes Kontenobjekt auch einen Rückbezug zur ServiceKlasse, um die drei Abfrage-Methoden aufrufen zu können.
4.5.3.4. Buchungs-Klasse
Auch ein Buchungsobjekt speichert selbst nur die ID und greift damit auf die Datenbank zu.

getAmount()
SELECT amount FROM accountingentry WHERE id=?

getCreditAccount()
SELECT creditaccount FROM accountingentry WHERE id=?

getDebitAccount()
SELECT debitaccount FROM accountingentry WHERE id=?

getText()
SELECT text FROM accountingentry WHERE id=?

setAmount()
UPDATE accountingentry SET amount=? WHERE id=?

setCreditAccount()
UPDATE accountingentry SET creditaccount=? WHERE id=?

setDebitAccount()
UPDATE accountingentry SET debitaccount=? WHERE id=?
- 97 -
Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC

setText()
UPDATE accountingentry SET text=? WHERE id=?
Resource 9: SQL-Befehle der Buchungs-Klasse (MySQL)
Die Daten stammen immer direkt aus der Buchungstabelle der Datenbank und werden bei
Änderungen auch wieder direkt dort gespeichert. Darüber hinaus ist auch nur wieder der
Rückbezug zur Service-Klasse, zum Aufruf der Abfrage-Methoden, als Feld vorhanden.
4.5.4. Fazit
Auf dem MySQL-Datenbanksystem und der JDBC-Schnittstelle basierend lassen sich die
Daten ordentlich und sicher speichern und abrufen. Auch der gleichzeitige Zugriff durch mehrere Clients auf die Daten ist möglich wodurch sich bereits ein erster Ansatz eines verteilten
Systems ergibt, das nicht explizit aufgebaut werden muß.
Nachteilig ist allerdings, daß die SQL-Abfragen manuell angegeben und fest im Programmcode enthalten sind. Wenn sich Änderungen am Aufbau der Daten ergeben, müssen die SQLAnweisungen und das Datenbank-Schema manuell angepaßt werden.
Und auch die Portabilität ist eingeschränkt, da durch die JDBC-Schnittstelle zwar eine einheitliche Schnittstelle zum Zugriff auf verschiedene Datenbanksysteme bereitgestellt wird,
aber die unterstützten SQL-Anweisungen doch unterschiedlich sein können, obwohl SQL
eigentlich standardisiert ist..
Deshalb muß an dieser Stelle noch etwas mehr Aufwand betrieben werden, um eine umfassende und einfach verwendbare Datenhaltung zu realisieren, die vom verwendeten Datenbanksystem unabhängig ist oder aber auf die speziellen Eigenheiten der verschiedenen Datenbanksysteme eingehen kann.
- 98 -
Datenhaltung - Hibernate
4.6. Hibernate
Da das Hibernate-Persistenz-Framework [27] auch im JBoss-Application-Server (siehe 6.2)
integriert ist, und später bei den Enterprise Java Beans (siehe 6.3) auch wieder verwendet
wird, soll hier auf die Funktionalität ausführlicher eingegangen werden, als bei den vorherigen Themen.
4.6.1. Allgemeines
Hibernate ist ein Persistenz-Framework, das zwischen einer Anwendung und der darunterliegenden
JDBC-Datenbank [37] als zusätzliche AbstraktionsSchicht fungiert und einem Entwickler einen Großteil der Aufgaben bezüglich der Datenhaltung abnimmt.
Anwendung
Persistente
Objekte
Hibernate
XML-Mapping
Annotations
hibernate.
properties
Datenbank
Dabei werden auch andere etablierte Java-Standards
wie JTA (Java Transaction API) [51] für Transaktionen und JNDI (Java Naming and Directory Interface) [17] zur Verbindung mit einer Datenbank unterstützt.
Tabelle 10: Einbindung von Hibernate als zusätzliche Schicht
Das Hibernate-Framework ist sehr flexibel, und man kann mehr oder weniger Funktionalitäten der API verwenden, und entsprechend den fehlenden Teil in der eigenen Anwendung implementieren. Bei vollständiger Verwendung von Hibernate wird die eigene Anwendung vollständig von der Datenbank abstrahiert, wobei man jedoch immer noch jederzeit direkt auf die
Datenbank zugreifen kann.
Transiente Objekte
Anwendung
Persistente
Objekte
SessionFactory
TransactionFactory
JNDI
Session
ConnectionProvider
JDBC
Transaction
JTA
Datenbank
Tabelle 11: Schematisher Aufbau der Hibernate-Komponenten
Die Funktion und Verwendung der einzelnen Komponenten wird im Folgenden schrittweise
näher erläutert.
- 99 -
Datenhaltung - Hibernate
4.6.1.1. Persistenz von Objekten
Die Funktionalität von Hibernate ist auf den objektorientierten Aufbau von Java ausgelegt, so
daß man die meiste Zeit nur mit Java-Objekten arbeitet und sich um die genauen Vorgänge
zum Zugriff auf die Datenbank, wie Transaktionen und SQL-Anweisungen, nach der einmaligen Konfiguration von Hibernate nicht mehr kümmern muß. Hibernate übernimmt das Speichern der Objekte und stellt diese später wieder zur Verfügung. Auch Sammlungen von Objekten und komplexe Objekthierarchien können von Hibernate verwaltet und gespeichert werden.
4.6.1.2. Arbeitsweise mit POJOs
Hibernate arbeitet mit beliebigen Java-Klassen, die wenig spezielle Anforderungen (z.B. Implementierung einer bestimmten Schnittstelle) erfüllen müssen (POJO - Plain Old Java Object),
sich aber an folgende Vorgaben halten können bzw. sollten, um ein optimales Verhalten bei
der Verwendung mit Hibernate zu erzielen [67]:

Einen Standard-Konstruktor ohne Parameter sollte eine persistente Klasse definieren,
damit Hibernate ein Objekt über Constructor.newInstance() instanzieren kann.
Der Konstruktor muß dafür nicht einmal als public deklariert sein.

Außerdem ist es sinnvoll jeder Klasse einen Identifikator mitzugeben, der (unter Umständen unabhängig von der Geschäftslogik) später als Primär-Schlüssel in der Datenbank dient. Ein solcher Identifikator ist aber auf jeden Fall optional, d.h. man kann es
auch Hibernate überlassen, die Objekte intern zu identifizieren. Bestimmte Funktionalitäten sind allerdings nur für Klassen verfügbar, die einen Identifikator definieren
(Transitive Persistenz, siehe 4.6.2.6, Session.saveOrUpdate(), Session.merge(),
siehe 4.6.5.2).

In diesem Sinne ist auch das Überschreiben der euqals() und hashCode() Methoden
sinnvoll (in speziellen Fällen sogar erforderlich), damit sich die Objekte richtig Verhalten (z.B. in einem Set). Dabei sollte man sich jedoch nicht unbedingt auf die Identifikatoren beziehen, die auch als Datenbank-Schlüssel dienen bzw. von Hibernate
vergeben werden, sondern eigene Schlüssel und Vergleichsmöglichkeiten im Sinne
der gewünschten Geschäftslogik verwenden.

Es bietet sich an, Zugriffmethoden auf die Felder der Klasse (Getter/Setter) im JavaBean-Style (z.B. getBla(), isBla(), setBla()) zu definieren, die dann von Hibernate automatisch verwendet werden. Man kann Hibernate aber auch anweisen, direkt auf
die Felder zuzugreifen.

Eine Klasse sollte nicht als final deklariert sein, oder aber alle Zugriffsmethoden über eine Schnittstelle implementieren, damit Hibernate zur Laufzeit dynamisch ProxyKlassen dafür (bzw. für die Schnittstelle) erzeugen kann, zur Verwendung von "lazy
association fetching" (wird hier nicht näher beschrieben, siehe 6.3.3.3).
- 100 -
Datenhaltung - Hibernate
4.6.1.3. OR-Mapping
Der Kernpunkt von Hibernate ist allerdings das OR-Mapping, die Objekt/relationale Zuordnung der Java-Klassen zu den Tabellen in einer relationalen Datenbank. Diese Zuordnung ist
auf Java bezogen und baut auf den persistenten Klassen auf, nicht auf den Tabellen der Datenbank.
Dabei werden die Felder von persistenten Klassen, ihre Beziehungen zueinander, und die gewünschte Abbildung in die Datenbank abstrakt beschrieben. Es wird festgelegt wie Beziehungen oder Vererbungshierarchien gespeichert werden, ob bei Sammlungen von Objekten, alle
Objekte zusammen automatisch gespeichert werden sollen, oder ob jedes Objekt für sich manuell persistiert werden muß.
Die Beschreibung des OR-Mapping kann entweder in einer separaten XML-Datei stehen,
oder aber durch Annotations [65] direkt im Quellcode erfolgen.
4.6.2. Annotations
Die speziellen Annotations zur Beschreibung von Persistenz werden durch die Enterprise JavaBeans 3.0 Spezifikation (JSR-220 - EJB3) [66] und die Java Persistence API (JPA) im Paket javax.persistence definiert. Hibernate unterstützt dabei alle Aspekte dieser Spezifikation vollständig, und darüber hinaus noch eigene Erweiterungen [68] auf die hier aber nicht
weiter eingegangen wird.
4.6.2.1. Entitäten
Durch die Annotation @Entity wird eine POJO-Klasse als persistente Klasse (Entitiy-Bean)
deklariert. Normal würden die Klassen dann in einer Tabelle der Datenbank mit demselben
Namen gespeichert. Dies kann durch die zusätzliche Angabe von @Table angepaßt werden.
@Entity
@Table(name = "Konten")
public class Account {
Long number;
@Id
public Long getNumber() {
return number;
}
}
public void setNumber(Long number) {
this.number = number;
}
Code 66: Class Account (Hibernate-Beispiel)
4.6.2.2. Identifikatoren - Schlüssel
Die Kontonummer wird im obigen Beispiel durch @Id als Primär-Schlüssel definiert und muß
von der eigenen Anwendung vergeben werden. Man kann den Identifikator aber auch von
Hibernate erzeugen lassen, durch die Annotation @GeneratedValue.
- 101 -
Datenhaltung - Hibernate
Dafür gibt es mehrere mögliche Strategien zur Erzeugung des Wertes:




benutzt einen @TableGenerator für den Identifikator
IDENTITY die Datenbank erzeugt eine Identitäts-Spalte
SQEUENCE fortlaufende Numerierung
AUTO
automatische Auswahl von TABLE, IDENTITY oder SEQUENCE ja nach
zugrundeliegender Datenbank, z.B:
o MySQL IDENTITY mit Spalten-Eigenschaft auto_increment
o Resin
IDENTITY mit Spalten-Eigenschaft auto_increment
o Postgres SEQUENCE
o Oracle
SEQUENCE
TABLE
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
Allerdings ist es am günstigsten den Typ AUTO zu verwenden, damit die Anwendung für verschiedene Datenbanksysteme portabel bleibt.
4.6.2.3. Einfache Daten
Von allen als @Entity gekennzeichneten Klassen ließt Hibernate die zu speichernden Felder
aus. Standardmäßig werden alle Eigenschaften mit Getter-Methode verarbeitet, wobei man
bei jeder dieser Methoden durch Annotations explizit angeben kann, wie das zugehörige Datum behandelt werden soll.

@Basic


@Transient



@Temporal
@Lob
@Enumerated
Keine Annotation
das Feld wird einer Spalte in der Datenbank mit dem passenden
Datentyp zugeordnet
das Feld wird von Hibernate ignoriert
Large Object - das Feld wird als CLOB oder serialisiert als BLOB in
der Datenbank gespeichert
für Datums- und Zeitangaben
für Java-Enumerationen
wird behandelt wie @Basic
@Basic
public Integer getNumber();
@Transient
public Set<Integer> getDebitAccounts();
Zusätzlich kann bei den obigen Annotations (außer bei @Transient, dafür aber bei @Id und
@Version) durch @Column die Spalte manuell genauer angegeben werden. Die Parameter für
diese Annotation orientieren sich an gängigen Eigenschaften von Spalten in einer Datenbank.


der Name der Spalte
ob die Werte Spalte einzigartig sein
müssen (Unique-Constraint)
name = "columnName";
boolean unique default false;
- 102 -
Datenhaltung - Hibernate

boolean nullable default true;

boolean insertable default true;

boolean updatable default true;

String columnDefinition default "";




String table default "";
int length default 255;
int precision default 0;
int scale default 0;
ob die Spalte keinen Wert (d.h. NULL)
enthalten darf
ob die Spalte bei INSERTAnweisungen verwendet wird
ob die Spalte in UPDATEAnweisungen angegeben wird
DDL-Anweisung zum Erzeugen der
Spalte
Tabelle zu der die Spalte gehört
maximale Länge des Inhalts
dezimale Genauigkeit
dezimaler Maßstab
@Id
@Column(name = "Buchungs-ID")
public Integer getId();
@Basic
@Column(unique = true, nullable = false, length = 42)
public String getDescription();
Hibernate kann ohne weitere Angaben die meisten Standard-Java-Datentypen (primitive Typen, viele der JDK-Klassen, Kollektionen) verarbeiten.
4.6.2.4. Beziehungen
Falls eine persistente Klasse ein Feld enthält, das den Typ einer anderen persistenten Klasse
hat, so haben diese beiden Entitäten eine Beziehung zueinander. Die einfachste Form ist eine
1:1-Beziehung von zwei Objekten der beiden Klassen zueinander, die mit der Annotation
@OneToOne angegeben wird.
@Entity
public class Customer {
@Id
public Long getId() {
return id;
}
@OneToOne
@PrimaryKeyJoinColumn
public Account getAccount() {
return account;
}
...
}
@Entity
public class Account {
@Id
public Long getNumber() {
return number;
}
...
}
Code 67: Beispiel für 1:1-Beziehung mit Primär-Schlüssel
- 103 -
Datenhaltung - Hibernate
Die zusätzliche Angabe von @PrimaryKeyJoinColumn bedeutet, daß der Primär-Schlüssel
(@Id) von Account für die Zuordnung zu Customer verwendet wird, d.h. daß zusammengehörige Objekte beider Klassen denselben Wert für den Primär-Schlüssel erhalten.
Im Gegensatz dazu kann auch ein zusätzlicher Fremd-Schlüssel für die Zuordnung eingeführt
werden, der auf einer Seite der bidirektionalen Beziehung durch @JoinColumn deklariert wird,
damit die Primär-Schlüssel der Klassen unabhängig voneinander vergeben werden können.
@Entity
public class Customer {
@OneToOne
@JoinColumn(name = "account_fk")
public Account getAccount() {
return account;
}
...
}
@Entity
public class Account {
@OneToOne(mappedBy = "account")
public Account getAccount() {
return account;
}
...
}
Code 68: Beispiel für 1:1-Beziehung mit Fremd-Schlüssel
Auf der anderen Seite der bidirektionalen Beziehung wird die Annotation @OneToOne mit dem
Parameter mappedBy verwendet, der angibt, welches Feld der ersten Klasse die Beziehung
aufbaut. Diese Beziehung könnte jederzeit auch umgekehrt definiert werden.
4.6.2.5. Kollektionen
Im nächsten Schritt werden 1:n und n:n-Beziehungen betrachtet, die durch die Java-Klassen
Collection, List, Set und Map realisiert werden, wenn deren enthaltene Elemente wie bei
1:1-Beziehungen vom Typ einer persistenten Klasse sind. Kollektionen mit primitiven Typen
oder Basis-Klassen werden dabei aber nicht unterstützt.
Semantik
Java Darstellung
Annotation
Bag
java.util.List
java.util.Collection
@OneToMany oder @ManyToMany
(u.U. mit @CollectionId)
List
java.util.List
@OneToMany oder @ManyToMany
Set
java.util.Set
@OneToMany oder @ManyToMany
Map
java.util.Map
@MapKey
Tabelle 12: Hibernate-Beziehungs-Semantik
- 104 -
Datenhaltung - Hibernate
Auch hier ist die Einführung eines Fremd-Schlüssels, wie oben bereits durchgeführt, notwendig. Ohne die Angabe von @JoinColumn wird der Name des Schlüssels von Hibernate automatisch aus den Namen der beteiligten Entitäten erzeugt.
@Entity
public class Account {
@OneToMany(mappedBy = "account")
public Set<AccountingEntry> getAccountingEntries() {
return accountingEntries;
}
...
}
@Entity
public class AccountingEntry {
@ManyToOne
public Account getAccount() {
return account;
}
...
}
Code 69: Beispiel für 1:n-Beziehung
Die Klasse Account enthält eine Kollektionen von mehreren Objekten der Klasse
AccountingEntry, so daß sich die beiden Entitäten in einer 1:n-Beziehung zueinander befinden, die durch die Annotations @OneToMany bzw. @ManyToOne auf der jeweiligen Seite der
Beziehung definiert wird. Durch mappedBy wird wieder die zuordnende Eigenschaft auf der
referenzierten Seite angegeben.
Bei einer n:n-Beziehung zweier persistenter Klassen verhält es sich ähnlich, nur daß in der
Datenbank dafür eine separate Tabelle und ein zugehöriger Fremdschlüssel bei jeder der Entitäten notwendig sind.
@Entity
public class Store {
@ManyToMany
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy = "customers")
public Set<Store> getStores() {
...
}
}
Code 70: Beispiel für n:n-Beziehung
Durch die Annotation @ManyToMany, die auch durch Parameter wie mappedBy oder cascade
ergänzt werden kann, wird die Beziehung etabliert. Durch eine weitere Annotation
@JoinTable kann die zusätzlich erzeugte Tabelle manuell genau definiert werden [68].
- 105 -
Datenhaltung - Hibernate
4.6.2.6. Kaskadierung
Durch Kaskadierung, auch transitive Persistenz genannt, kann Hibernate angewiesen werden
alle Objekte der Objekthierachie, von durch Beziehungen verbundenen persistenten Klassen,
automatisch zu speichern. Dabei kann durch den Parameter cascade bei @OneToOne,
@OneToMany und @ManyToMany genau angegeben werden, in welchen Fällen die referenzierten
Objekte automatisch verarbeitet werden sollen.





CascadeType.PERSIST
CascadeType.MERGE
CascadeType.REMOVE
CascadeType.REFRESH
CascadeType.ALL
ruft die persist()-Methode für die Objekte auf
ruft die merge()-Methode für die Objekte auf
ruft die delete()-Methode für die Objekte auf
ruft die refresh()-Methode für die Objekte auf
ruft all diese Methoden auf
Es können auch mehrere der Werte als Array gemeinsam angegeben werden.
@OneToMany(cascade = CascadeType.ALL)
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
Wenn kein Kaskadierungstyp angegeben wird, müssen alle Objekte durch die Anwendung,
durch expliziten Aufruf von persist() oder save() gespeichert werden, wobei bei der Reihenfolge auf die Abhängigkeiten von Fremd- und Primär-Schlüsseln geachtet werden muß.
4.6.3. Installation von Hibernate
Zur Installation von Hibernate müssen die Hibernate-Pakete im Download-Bereich von
http://www.hibernate.org
heruntergeladen werden. Für die Verwendung der beschriebenen Funktionalitäten sind die
Pakete "Hibernate Core" und "Hibernate Annotations" in der aktuellen Version 3.2.1.GA
notwendig, die jeweils eine Vielzahl von Bibliotheken enthalten, die in den CLASSPATH der
Anwendung aufgenommen werden müssen (siehe auch Anhang C).
4.6.3.1. Hibernate-Einstellungen
Da Hibernate für die mögliche Verwendung in vielen verschiedenen Umgebungen konzipiert
wurde, gibt es eine Vielzahl von Einstellungen. Dabei gilt jedoch der Grundsatz
"Configuration by Exception", d.h. daß für fast alle Einstellungen die Standardwerte verwendet werden können, und nur sowenig wie nötig zusätzlich konfiguriert werden muß. Der Entwickler kann aber jederzeit in alle Aspekte der Konfiguration eingreifen.
Die Konfiguration kann in der Anwendung programmiert oder in separaten XML- und Property-Dateien erfolgen. Die Datei hibernate.properties enthält meistens die Grundeinstellungen und wird von Hibernate automatisch als Ressource gesucht und verarbeitet.
- 106 -
Datenhaltung - Hibernate
4.6.3.2. Datenbankanbindung
Die wichtigste Einstellung für Hibernate, die auf jeden Fall konfiguriert werden muß, ist die
Anbindung an ein Datenbanksystem. Dafür müssen in der Konfiguration etliche Parameter
angegeben werden.
hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost:3306/hibernate
hibernate.connection.username = hibernate
hibernate.connection.password = hibernate
Resource 10: Hibernate Datenbankeinstellungen
Für die gewünschte Datenbank müssen eine JDBC-Treiber-Klasse und der zugehörige Hibernate-Dialekt, der für die Erzeugung der passenden SQL-Anweisungen zuständig ist, verfügbar
sein. Unter
http://www.hibernate.org/hib_docs/v3/api/org/hibernate/dialect/package-summary.html
kann die Liste der verfügbaren Dialekte und unterstützten Datenbanken abgerufen werden.
4.6.3.3. Weitere Einstellungen
Zwei weitere Einstellungen können für die grundlegende Verwendung von Hibernate wichtig
sein. Zum einen kann man sich mit
hibernate.show_sql = true
alle SQL-Anweisungen, die Hibernate erzeugt und an die Datenbank schickt, ausgeben lassen,
was während der Entwicklungsphase einer Anwendung von großer Bedeutung sein kann.
Zum anderen kann man festlegen wie Hibernate mit dem Datenbank-Schema für die persistenten Klassen umgeht. Der Parameter
hibernate.hbm2ddl.auto = ...
hat dafür mögliche vier mögliche Werte, die angeben auf welche Weise Hibernate das Datenbank-Schema automatisch verwaltet.




das Schema wird nicht automatisch erzeugt, sondern nur auf seine Gültigkeit hin überprüft
update
das Schema wird erzeugt oder aktualisiert, wenn bereits vorhanden
create
das Schema wird automatisch erzeugt, wenn es nicht vorhanden ist
create-drop das Schema wird erzeugt und beim expliziten Schließen von Hibernate
wieder gelöscht
validate
Bei allen Einstellungen ist besonders auf die richtige Schreibweise zu achten, da sie sonst von
Hibernate nicht erkannt und die Standardwerte verwendet werden.
- 107 -
Datenhaltung - Hibernate
4.6.4. Zugriff auf Hibernate
Nachdem nun alle Vorbereitungen getroffen wurden, wird Hibernate angewiesen nach diesen
Vorgaben zu arbeiten, wodurch man seine eigene Anwendung auf Hibernate aufsetzen kann.
4.6.4.1. Erzeugen der Konfiguration
Zentraler Punkt zur Verwendung von Hibernate in einer Java-Anwendung ist ein Konfigurationsobjekt. In diesem Fall wird Eines verwendet, das mit Annotations arbeitet. Dem Konfigurationsobjekt werden alle durch Annotations gekennzeichneten persistenten Klassen hinzugefügt. Die Datei hibernate.properties mit den Grundeinstellungen wird automatisch im
aktuellen CLASSPATH gesucht und, wenn vorhanden, eingelesen. Außerdem ist es möglich
weitere XML-Konfigurationsdateien oder in der Anwendung erstellte Property-Objekte mit
weiteren Einstellungen anzugeben.
AnnotationConfiguration cfg = new AnnotationConfiguration();
cfg.addAnnotatedClass(AccountingService.class);
cfg.addAnnotatedClass(Account.class);
cfg.addAnnotatedClass(AccountingEntry.class);
sessionFactory = cfg.buildSessionFactory();
Das fertige Konfigurationsobjekt wird schließlich dazu verwendet, eine Sitzungs-Fabrik zu
erstellen, die nach den angegebenen Parametern arbeitet. Die Sitzungs-Fabrik ist threadsicher
und dient von nun an als Ausgangspunkt für alle weiteren Aktionen mit Hibernate.
4.6.4.2. Sitzungen
Die Arbeit mit den persistenten Objekten erfolgt innerhalb einer Sitzung, die man von der
Sitzungs-Fabrik erhält.
Session session = sessionFactory.openSession();
Ein Sitzungs-Objekt ist nicht threadsicher und darf deshalb nur von einem einzelnen Thread
begrenzt verwendet werden. Für die Verwendung des Objekts gibt es verschiedene Ansätze
bezüglich der Lebensdauer einer Sitzung.

session-per-operation: Eine bequeme Möglichkeit wäre es, eine eigene Sitzung für jede Operation zu öffnen, die Operation auszuführen, und die Sitzung wieder zu schließen. Dies ist jedoch eine schlechte Vorgehensweise, da in einer Anwendung normalerweise mehrere Operationen logisch zusammengehören, und auch die Performance
stark darunter leiden könnte.

session-per-request: Besser wäre es, eine Sitzung zu öffnen, die solange geöffnet
bleibt, bis ein Benutzeraufruf des Systems vollständig verarbeitet ist. Alle DatenbankOperationen die für den Aufruf notwendig sind, können innerhalb einer Sitzung von
einem Thread ausgeführt werden.
Jede Datenbank-Operation wird von Hibernate auf jeden Fall als Transaktion ausgeführt, so
daß einer Sitzung 1:1 immer eine Transaktion zugeordnet wird.
- 108 -
Datenhaltung - Hibernate
4.6.4.3. Transaktionen
Häufig reicht diese eine implizite Transaktion jedoch nicht aus. Viele Anwendungen führen
während eines Benutzeraufrufs eine längere Konversation (conversation) mit dem Benutzer
(auch genannt Anwendungstransaktion - application transaction) durch, wobei meist längere
Wartezeiten auf Benutzereingaben (think time) eingeschlossen sind. Wird nun nach dem Muster session-per-request vorgegangen bleibt eine Datenbanktransaktion während der think time
offen, und zugeordnete Datenbank-Ressourcen bleiben gesperrt, was z.B. für die Skalierung
bezüglich der Benutzerzahl sehr schlecht ist.
Deshalb umfaßt eine Anwendungstransaktion meistens mehrere Datenbanktransaktionen, zuerst zum Laden von Daten und später zum Speichern von geänderten Daten, die entsprechend
programmiert werden müssen. In verwalteten Umgebungen, wie z.B. einem Applikationsserver (siehe 6), wird die Transaktions-Demarkation über JTA [51] normalerweise automatisch
vorgenommen. In Einzelanwendungen, oder um die Anwendung portabel zu halten, kann die
Transaktions-Demarkation auch einprogrammiert werden.
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
...
// Datenbank-Operationen
tx.commit();
} catch (RuntimeException e) {
if (tx != null)
tx.rollback();
}
throw e;
} finally {
session.close();
}
Code 71: Manuelle Transaktions-Demarkation (1)
Um dabei jedoch flexibler vorgehen zu können, kann man die Verwaltung der Sitzung auch
Hibernate überlassen, und sich ganz auf die benötigten Transaktionen konzentrieren.
try {
sessionFactory.getCurrentSession().beginTransaction();
...
// datenbankbezogene Operationen
sessionFactory.getCurrentSession().getTransaction().commit();
} catch (RuntimeException e) {
sessionFactory.getCurrentSession().getTransaction().rollback();
throw e;
}
Code 72: Manuelle Transaktions-Demarkation (2)
Ein Problem dabei ist jedoch eben der gleichzeitige Zugriff von mehreren Benutzern auf das
System, wodurch mehrere Entity-Objekte, die sich aber auf den selben Datensatz in der Datenbank beziehen, im Umlauf sein können, wobei der Datensatz eben nicht für einen bestimmten Benutzer exklusiv gesperrt ist.
- 109 -
Datenhaltung - Hibernate
4.6.4.4. Fehlerbehandlung
Wenn beim Aufruf von Hibernate-Methoden eine Ausnahme geworfen wird, sollte die aktuelle Transaktion unbedingt zurückgerollt werden (rollback()). Außerdem muß die aktuelle
Sitzung geschlossen werden, da bei bestimmten Hibernate-Methoden die Sitzung nicht in einem konsistenten Zustand bleibt. Generell sind Ausnahmen bei Hibernate nicht behebbar und
es muß in einer neuen Sitzung von vorne begonnen werden [68].
Hibernate
wirft
normalerweise
eine
HibernateException
(abgeleitet
von
RuntimeException) für einen allgemeinen Fehler in der Persistenz-Schicht.
Der häufigere Fall sind aber Datenbank-Ausnahmen (SQLException), die als JBDCException
oder einer Unterklasse davon (siehe auch SQLExceptionConverterFactory), mit einer bestimmten Bedeutung, geworfen werden.

JDBCConnectionException

SQLGrammarException

ConstraintViolationException

LockAcquisitionException

GenericJDBCException
ein allgemeines Problem mit der Datenbankverbindung
ein Grammatik- oder Syntax-Problem bei der
ausgeführten SQL-Anweisung
ein Datenbank-Einschränkung wäre verletzt
worden
eine notwendige Sperre für die aktuelle Operation kann nicht erhalten werden
eine andere Ausnahme, die nicht in eine der genannten Kategorien fällt
4.6.4.5. Transaktions-Zeitlimit
Neben der Kontrolle der auftretenden Fehler ist es außerdem wichtig, daß DatenbankRessourcen von ungünstig verlaufenden Transaktionen nicht beliebig lange belegt werden
können. Dazu kann für jede Transaktion ein Zeitlimit (Timeout) angegeben werden.
//Zeitlimit auf 3 Sekunden setzen
session.getTransaction().setTimeout(3);
Hibernate versucht über die Kontrolle von Datensatz-Sperren und der Größe von AbfrageErgebnissen sicherzustellen, daß die eingestellte Zeit eingehalten wird. In einer verwalteten
Umgebung übergibt Hibernate die Verwaltung des Zeitlimits an JTA, sofern das Zeitlimit
nicht sowieso von der Ablauf-Umgebung vorgegeben wird.
- 110 -
Datenhaltung - Hibernate
4.6.4.6. Versionskontrolle
Durch Versionskontrolle (optimistic concurrency control) der persistenten Objekte kann die
Datenintegrität bei gleichzeitigen Zugriffen sichergestellt werden. Im einfachsten Fall kann
die Versionskontrolle selbst implementiert werden, und bei unterschiedlichen Versionen eine
Fehlermeldung ausgegeben werden.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
int oldVersion = foo.getVersion();
session.load(foo, foo.getKey());
if (oldVersion != foo.getVersion) {
throw new StaleObjectStateException();
}
foo.setProperty("bar");
tx.commit();
session.close();
Code 73: Selbst implementierte Versionskontrolle
Dieses Vorgehen ist für Verarbeitung komplexer Objekthierarchien, besonders bei der Anwendung der Kaskadierung, sehr umständlich, weshalb die Versionskontrolle von Hibernate
automatisch durchgeführt werden sollte.
Dazu sollte eine persistente Klasse ein numerisches Feld (oder Timestamp) definieren, das mit
der Annotation @Version für die automatische Versionskontrolle gekennzeichnet wird.
@Entity
@org.hibernate.annotations.Entity(
optimisticLock = OptimisticLockType.VERSION)
public class Account {
@Version
public Integer getVersion() {
...
}
}
Code 74: Klasse mit automatischer Versionskontrolle durch Hibernate
Bei der @Entity-Annotation von Hibernate kann durch den zusätzlichen Parameter
optimisticLock angegeben werden, wie Hibernate die Versionskontrolle durchführen soll.



OptimisticLockType.NONE

OptimisticLockType.ALL
OptimisticLockType.VERSION
OptimisticLockType.DIRTY
keine automatische Versionskontrolle
Verwendung eines vorhandenen Versionsfeldes
Versionskontrolle durch Vergleich aller Felder, die
von Hibernate als geändert registriert wurden
Versionskontrolle auch ohne spezielles Versionsfeld, durch Vergleich aller persistenten Felder der
Klasse
Wenn Hibernate durch die Versionskontrolle beim Speichern die mehrfache gleichzeitige
Änderung eines Objekts festestellt wird eine Ausnahme geworfen.
- 111 -
Datenhaltung - Hibernate
4.6.4.7. Sperren
Die andere Möglichkeit die Konsistenz der Daten zu gewährleisten sind Datenbank-Sperren
(pessimistic locking), die von Hibernate automatisch bei bestimmten Zugriffen auf die Datenbank verwendet oder durch den Entwickler explizit gesetzt werden.
Die Klasse LockMode definiert die möglichen Sperren, die Hibernate beim Zugriff auf den
Datensatz für ein persistentes Objekt erhalten kann.

LockMode.WRITE



LockMode.UPGRADE
LockMode.READ
LockMode.NONE
wird verwendet beim Aktualisieren oder Einfügen einer Zeile
in die Datenbank
wird nur beim expliziter Angabe verwendet
wird beim wiederholten Laden von Objekten verwendet
keine Sperren werden verwendet
Bei den folgenden Hibernate-Methoden kann explizit angegeben werden, daß eine bestimmte
Sperre verwendet werden soll.




session.load()
session.get()
session.lock()
Query.setLockMode()
Hibernate verwendet immer die Sperr-Mechanismen der Datenbank und wird niemals Objekte
im Speicher sperren. Wenn die Datenbank die angeforderte Sperre nicht unterstützt, verwendet Hibernate eine passende Alternative und gibt keinen Fehler zurück, damit die Anwendung
portabel bleibt [68].
4.6.5. Verwendung der Objekte
Hibernate hält nicht nur die konkreten Details der zugrundeliegenden Datenbank vom Entwickler fern, sondern kümmert sich auch um die Verwaltung des Objekt-Zustands. Das bedeutet, daß sich ein Entwickler keine Gedanken darüber machen muß, wann welche SQLAnweisungen ausgeführt werden, er muß lediglich den aktuellen Zustands eines Objekts gegenüber Hibernate im Auge behalten.
4.6.5.1. Zustände
Jedes Objekt, das mit dem new-Operator gerade erst erzeugt worden ist, ist bei Hibernate noch
nicht bekannt und daher im Objektzustand transient. Es hat keinen entsprechenden Datensatz
in der relationalen Datenbank und keinen von Hibernate zugewiesenen Identifikator.
Account account = new Account();
account.setNumber(2400);
account.setType(AccountType.ACTIVE);
account.setDescription("Hibernate-Konto");
- 112 -
Datenhaltung - Hibernate
Nachdem ein Objekt durch den Aufruf von perist() oder save() an Hibernate übergeben
wurde ist es persistent. Es ist eine geöffnete Hibernate-Sitzung gebunden und hat zugehörige
Daten in der Datenbank, sowie einen erzeugten oder angegebenen Identifikator.
Long id = (Long)session.save(account);
session.save(id, account);
Hibernate überwacht dann das Objekt, so daß am Ende einer Transaktion oder der Sitzung bei
geänderten Daten eine Synchronisation mit der Datenbank stattfindet. Durch den Aufruf von
session.flush();
kann der Abgleich mit der Datenbank auch manuell angestoßen werden.
Wenn nun die Hibernate-Sitzung zu der ein Objekt gehört geschlossen wird, bleibt das JavaObjekt in der Virtuellen Maschine natürlich bestehen, es hat nur keine Verbindung zu Hibernate mehr, weshalb es in den Zustand detached übergeht. Man kann das Objekt normal weiterverwenden und auch seine Daten ändern. Später kann das Objekt dann wieder ein Sitzung
zugeordnet, und so die geänderten Daten in der Datenbank gespeichert werden.
session2.update(account);
Dazu muß die Methode update() verwendet werden, da der Aufruf von persist() oder
save() nur bei transienten Objekten möglich ist [67].
4.6.5.2. Automatische Erkennung des Objektzustandes
Durch die möglichen Vorgänge, bei denen ein Objekt an Hibernate gebunden und wieder von
der Sitzung getrennt werden kann, kann es kompliziert werden, jeweils die richtige HibernateFunktion zu verwenden, z.B. wenn ein Objekt aus der Datenhaltungsschicht an die Benutzeroberfläche gegeben wird, dort verändert wird, und dann zum Speichern wieder an die Datenhaltung zurückgegeben wird.
Dafür bietet Hibernate die weiteren Funktionen saveOrUpdate() und merge(), die den aktuellen Zustand eines Objekts in Betracht ziehen, und dann entsprechend verfahren.
session2.saveOrUpdate(account);
session2.merge(account);
Die Funktion saveOrUpdate() verhält sich dabei folgendermaßen:






wenn das Objekt bereits persistent ist, wird nichts unternommen
wenn bereits ein Objekt mit dem selben Identifikator vorhanden ist, wird eine Ausnahme geworfen
wenn das Objekt noch keinen Identifikator hat, wird es persistiert
wenn das Objekt einen Identifikator hat, der noch nicht vergeben ist, wird es persistiert
wenn das Objekt eine Versionsangabe (@Version) enthält, die auf ein neues Objekt
hindeutet, wird es durch save() persistiert
in allen anderen Fällen wird update() aufgerufen
- 113 -
Datenhaltung - Hibernate
Die Funktion merge() verhält sich im Gegensatz dazu ganz unterschiedlich:




wenn es bereits ein persistentes Objekt mit dem selben Identifikator gibt, werden nur
die Daten des neuen Objekts übernommen
wenn es kein passendes persistentes Objekt gibt, wird Eines aus der Datenbank geladen oder neu erzeugt
die persistente Instanz wird zurückgegeben
die übergebene Instanz wird nicht der aktuellen Sitzung zugeordnet und bleibt somit
detached
4.6.5.3. Laden der Objekte
Durch die load()-Methode wird eine persistente Instanz geladen, die sicher in der Datenbank
vorhanden und deren Identifikator bekannt ist.
Account account = (Account)session.load(Account.class, id);
Die Methode instanziert ein Objekt der angegebenen Klasse mit den zugehörigen Daten aus
der Datenbank und gibt es im Zustand persistent zurück. Wenn kein Datensatz zu dem angegebenen Identifikator existiert, wird eine Ausnahme geworfen.
Falls von vorne herein nicht sichergestellt ist, daß ein zugehöriger Datensatz vorhanden ist,
kann die get()-Methode verwendet werden.
Account account = (Account)session.get(Account.class, id);
if (account == null) {
account = new Account();
session.save(account, id);
}
return account;
Diese Methode wirft keine Ausnahme, sondern gibt null zurück, wenn der Identifikator ungültig ist. Wie bereits erwähnt, gibt es bei dieser Methode eine Variante, mit der explizit eine
Sperre angefordert werden kann, z.B.
account = (Account)session.get(Account.class, id, LockMode.UPGRADE);
Außerdem können die Daten eines bereits instanzierten persistenten Objekts durch die
refresh()-Methode erneut geladen werden.
session.save(account);
// Objekt speichern
session.flush();
// SQL-Insert-Anweisung ausführen lassen
session.refresh(account); // Daten erneut laden
Dies ist für den Fall nützlich, daß Datenbank-Trigger beim Speichern verwendet werden, um
Felder des persistenten Objekts zu initialisieren, wie z.B. einen Zeitstempel.
- 114 -
Datenhaltung - Hibernate
4.6.5.4. Abfragen
Wenn die Identifikatoren von gewünschten Objekten nicht bekannt sind muß eine Abfrage
ausgeführt werden. Hibernate bietet dafür eine eigene objektorientierte Abfragesprache
(HQL) die ähnlich zu SQL aufgebaut ist, aber die Portabilität der Anwendung gegenüber verschiedenen Datenbanksystemen erhält. Es können aber auch native SQL-Abfragen direkt für
die Datenbank formuliert und ausgeführt werden.
Eine neue Abfrage wird über ein bestehendes Sitzungsobjekt erzeugt.
Query debits = session.createQuery(
"FROM accountingentries WHERE debitaccount = ?");
debits.setNumber(0, 2400);
Collection result = debits.list();
Die Abfrage unterstützt ? Platzhalter im Stil von JDBC, nur mit dem Unterschied, daß die
Numerierung bei 0 und nicht bei 1 beginnt. Nachdem alle Platzhalter mit einem Wert belegt
worden sind, wird die Abfrage durch den Aufruf von list() ausgeführt, und eine Kollektionen mit den zutreffenden Objekten als Ergebnis zurückgeliefert.
Wenn vorher bereits bekannt ist, daß eine Abfrage nur ein einzelnes Objekt als Ergebnis zurückliefert, kann auch die Methode uniqueResult() an Stelle von list() zur Ausführung
der Abfrage benutzt werden.
4.6.5.5. Modifikationen an Objekten
Persistente Objekte, die mit einer Sitzung verbunden sind, können über ihre Methoden direkt
manipuliert werden. Die Änderungen werden in der Datenbank gespeichert, wenn flush()
explizit aufgerufen oder die gerade laufende Transaktion abgeschlossen wird. Es ist nicht nötig SQL-Anweisungen wie UPDATE oder DELETE extra aufzurufen, und die Hibernate-API bietet deshalb auch keine Methoden dafür.
Falls nötig kann man jedoch durch Session.connection() direkt auf die JDBC-Verbindung
zur Datenbank zugreifen, z.B. um eine größere Anzahl Datensätze direkt zu ändern.
Bei detached Objekten, deren zugehörige Sitzung geschlossen wurde, ist es nötig eine neue
Sitzung zu öffnen, und ihr die Objekte zu übergeben, damit Änderungen gespeichert werden.
Dies erfolgt durch eine der beschriebenen Methoden update(), saveOrUpdate() oder
merge() (siehe 4.6.5.2).
Außerdem ist möglich mit der lock()-Methode ein nicht modifiziertes Objekt einfach nur an
eine neue Session zu binden und dabei ggf. eine Sperre in der Datenbank zu erwirken.
4.6.5.6. Löschen persistenter Objekte
Persistente Objekte werden durch delete() aus der Hibernate-Sitzung und der Datenbank
gelöscht, wobei das Java-Objekt für die Anwendung natürlich bestehen bleibt, solange eine
Referenz darauf vorhanden ist, d.h. das Objekt wird wieder transient.
session.delete(account);
- 115 -
Datenhaltung - Hibernate
4.6.6. Buchhaltung mit einer Singleton-Service-Klasse
Ein Ansatz für ein persistentes Buchhaltungssystem mit Hibernate wäre die Betrachtung der
Objekthierarchie von den Konten aus, denen dann jeweils die Buchungen zugeordnet sind.
Abbildung 61: ERM-Modell 1 für die Buchhaltung mit Hibernate
Die Service-Klasse ist dann keine Entität und es muß lediglich für die Implementierung der
Methoden der Kontenobjekte und Buchungsobjekte eine Instanz der Service-Klasse als
"Dienstleister" verfügbar sein.
4.6.6.1. Service-Klasse
Dazu bietet sich die Verwendung des Singleton-Musters an. Die Service-Klasse wird daher
mit einigen statischen Methoden ausgestattet, die immer nur die eine statische Instanz der
Service-Klasse zurückgeben.
package de.fhr.vs_iw.hibernate.v1;
public final class AccountingService implements IAccountingService {
private static AccountingService instance = null;
private static Session session = null;
private static SessionFactory sessionFactory = null;
private static void initInstance() {
AnnotationConfiguration cfg = new AnnotationConfiguration();
cfg.addAnnotatedClass(Account.class);
cfg.addAnnotatedClass(AccountingEntry.class);
sessionFactory = cfg.buildSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
instance = new AccountingService();
instance.accountSet = new HashSet<Account>(
session.createQuery("FROM Account").list());
instance.accountingEntrySet = new HashSet<AccountingEntry>(
session.createQuery("FROM AccountingEntry").list());
}
protected static void closeInstance() {
if (instance != null) {
session.getTransaction().commit();
session.close();
session = null;
sessionFactory = null;
instance = null;
}
}
- 116 -
Datenhaltung - Hibernate
protected static AccountingService getInstance() {
if (instance == null) {
initInstance();
}
return instance;
}
...
public void createAccount(Integer number, AccountType type,
String description) {
Account account = new Account(number, type, description);
accountSet.add(account);
session.save(account);
}
public Integer createAccountingEntry(Integer debitAccount,
Integer creditAccount, Double amount, String text){
AccountingEntry entry = new AccountingEntry(
debitAccount, creditAccount, amount, text);
accountingEntrySet.add(entry);
Integer id = (Integer)session.save(entry);
return id;
}
}
...
Code 75: Class AccountingService (Hibernate - Variante 1)
Dabei werden alle Konten und Buchungen innerhalb der Service-Klasse verwaltet. Beim Erzeugen der Instanz der Service-Klasse durch initInstance() wird Hibernate konfiguriert,
sowie eine Session und eine Transaktion gestartet. Alle in der Datenbank vorhandenen Konten und Buchungen werden durch Abfragen geladen. Die Konten- bzw. Buchungsobjekte
verwenden dann zum Zugriff darauf die Singleton-Instanz der Service-Klasse.
AccountingService.getInstance();
Alle von Hibernate aus der Datenbank geladenen Objekte sind persistent und Änderungen
daran werden gespeichert. Nur beim Erzeugen neuer Objekte durch createAccount() bzw.
createAccountingEntry() müssen diese durch den Aufruf von persist() oder save(),
wie oben im Code gezeigt, bei der Hibernate-Sitzung registriert werden. Beim Speichern eines neuen Buchungsobjekts wird die zugewiesene ID des neuen Objekts zurückgegeben.
- 117 -
Datenhaltung - Hibernate
4.6.6.2. Konto-Klasse
Ein Konto speichert seine zugeordneten Buchungen nicht selbst, sondern sucht diese über die
Service-Klasse. Konten und Buchungen speichern nur ihre jeweiligen Basisdaten. Da dieser
Ansatz bereits vollständig dem Buchhaltungs-Modell entspricht, und die Entitäten bereits über
die Kontonummer einander zugeordnet werden, muß für Hibernate in dieser Hinsicht keine
Beziehung zwischen den Entitäten konfiguriert werden, da dies von der implementierten Geschäftslogik des Buchhaltungssystems her bereits erfolgen muß.
package de.fhr.vs_iw.hibernate.v1;
@Entity
@Table(name = "Account")
public final class Account implements IAccount {
private String description = null;
private Integer number = null;
private AccountType type = null;
public Account();
public Account(Integer number, AccountType type, String description);
@Transient
public Set<Integer> getCreditEntries() {
final Set<Integer> credit = new HashSet<Integer>();
for (AccountingEntry entry
: AccountingService.getInstance().accountingEntrySet) {
if (entry.getCreditAccount().equals(number)) {
credit.add(entry.getId());
}
}
return credit;
}
@Transient
public Set<Integer> getDebitEntries();
@Basic
public String getDescription();
@Id
@Column(name = "accountnumber")
public Integer getNumber();
@Basic
public AccountType getType();
public void setDescription(String description);
public void setNumber(Integer number);
public void setType(AccountType type);
}
Code 76: Class Account (Hibernate - Variante 1)
Die Konto-Klasse wird als Entität und die Eigenschaften der Klasse als Basis-Elemente gekennzeichnet, wobei die Methoden zum Ermitteln der zugeordneten Buchungen transient sein
müssen, da Hibernate hier nicht darauf zugreifen soll.
- 118 -
Datenhaltung - Hibernate
4.6.6.3. Buchungs-Klasse
Auch die Buchungs-Klasse wird einfach als Entität gekennzeichnet.
package de.fhr.vs_iw.hibernate.v1;
@Entity
@Table(name = "AccountingEntry")
public final class AccountingEntry implements IAccountingEntry {
private Double amount = null;
private Integer creditAccount = null;
private Integer debitAccount = null;
private Integer id = null;
private String text = null;
public AccountingEntry();
public AccountingEntry(Integer debit, Integer credit,
Double amount, String text);
@Basic
public Double getAmount();
@Basic
public Integer getCreditAccount();
@Basic
public Integer getDebitAccount();
@Id
@GeneratedValue()
public Integer getId();
@Basic
public String getText();
public
public
public
public
public
void
void
void
void
void
setAmount(Double amount);
setCreditAccount(Integer number);
setDebitAccount(Integer number);
setId(Integer id);
setText(String text);
}
Code 77: Class AccountingEntry (Hibernate - Variante 1)
Bei der Buchungs-Klasse treten von den Daten her sowieso nur Basis-Elemente auf. Da bei
der Buchungs-ID die Vergabe durch das System vorgesehen ist, kann Hibernate dies durch
die Annotation @GeneratedValue übernehmen.
4.6.6.4. Proxy-Klasse
In der connect()-Methode der Proxy-Klasse wird lediglich die Singleton-Instanz der Service-Klasse zurückgegeben (getInstance()). Beim Schließen des Systems durch close()
wird dann noch die Methode closeInstance() der Service-Klasse aufgerufen, um die Objekte zu speichern und die Hibernate-Sitzung abzuschließen.
- 119 -
Datenhaltung - Hibernate
4.6.7. Buchhaltung mit einer Entity-Service-Klasse
Ein anderer Ansatz wäre es, die Objekthierarchie von der Buchhaltungsanwendung von der
Service-Klasse aus zu betrachten, die ja sowieso schon alle vorhandenen Konten und Buchungen verwaltet.
Abbildung 62: ERM-Modell 2 für die Buchhaltung mit Hibernate
Die Service-Klasse ist dann selbst eine zu speichernde Entität des Systems, und die Konten
und Buchungen werden mit ihr in eine feste Beziehung gesetzt.
4.6.7.1. Service-Klasse
Dazu wird die Service-Klasse als Entität gekennzeichnet und um direkte Zugriffsmethoden
für die Listen der Konten bzw. der Buchungen erweitert. Außerdem muß ein Primär-Schlüssel
definiert werden, der allerdings immer 0 ist.
package de.fhr.vs_iw.hibernate.v2;
@Entity
@Table(name = "AccountingService2")
public final class AccountingService implements IAccountingService {
private int nextId = 0;
private Set<AccountingEntry> accountingEntrySet
= new HashSet<AccountingEntry>();
private Set<Account> accountSet
= new HashSet<Account>();
public AccountingService();
@OneToMany(mappedBy = "service", cascade = CascadeType.ALL)
public Set<AccountingEntry> getAccountingEntrySet();
@OneToMany(mappedBy = "service", cascade = CascadeType.ALL)
public Set<Account> getAccountSet();
@Basic
public int getNextId();
@Id
public Integer getServiceId() {
public void setAccountingEntrySet(
Set<AccountingEntry> accountingEntriesSet);
public void setAccountSet(Set<Account> accountsSet);
public void setNextId(int nextId);
public void setServiceId(Integer serviceId);
}
...
Code 78: Class AccountingService (Hibernate - Variante 2)
- 120 -
Datenhaltung - Hibernate
Der Primärschlüssel wird nicht ein einem Feld der Instanz der Klasse gespeichert, sondern ist
immer 0, weil es nur eine Instanz der Service-Klasse geben soll. Außerdem ist es sinnvoll das
Feld nextId, mit der nächsten zu vergebenden Buchungs-ID, auch mitzuspeichern, damit das
System nach dem Laden aus der Datenbank keine Fehler beim Erfassen neuer Buchungen
produziert. Die Buchungs-ID kann man hier nur schlecht von Hibernate vergeben lassen, da
nicht vorgesehen ist, daß ein Buchungsobjekt nach dem Anlegen sofort von Hibernate persistiert wird, sondern später die vollständige Objekthierarchie auf einmal (siehe 4.6.7.4). Deshalb ist bei den @OneToMany-Beziehungen auch der Kaskadierungstyp angegeben
4.6.7.2. Konto-Klasse
Die Konto-Klasse stellt ebenfalls wieder eine eigene Entität dar, die mit der Service-Klasse in
einer bidirektionalen Beziehung steht. Dafür erhält die Konto-Klasse ein entsprechendes Feld
mit Getter- und Setter-Methode für die zugehörige Instanz der Service-Klasse.
package de.fhr.vs_iw.hibernate.v2;
@Entity
@Table(name = "Account2")
public final class Account implements IAccount {
private AccountingService service = null;
@ManyToOne
public AccountingService getService();
public void setService(AccountingService service);
}
...
Code 79: Class Account (Hibernate - Variante 2)
Durch die Annotation @ManyToOne wird der Rückbezug zur Service-Klasse hergestellt. Ansonsten bleiben die Methoden und Annotations im Vergleich zur vorherigen Konto-Klasse
(siehe 4.6.6.2) unverändert.
4.6.7.3. Buchungs-Klasse
Bei der Buchungs-Klasse wird analog zur Konto-Klasse vorgegangen und die Beziehung zur
Service-Klasse hergestellt. Ansonsten entspricht auch diese Klasse der vorigen (siehe 4.6.6.3).
package de.fhr.vs_iw.hibernate.v2;
@Entity
@Table(name = "AccountingEntry2")
public final class AccountingEntry implements IAccountingEntry {
private AccountingService service = null;
@ManyToOne
public AccountingService getService();
public void setService(AccountingService service);
}
...
Code 80: Class AccountingEntry (Hibernate - Variante 2)
- 121 -
Datenhaltung - Hibernate
4.6.7.4. Proxy-Klasse
Da die Service-Klasse jetzt selbst eine Entität darstellt wird der Aufruf von Hibernate in die
Proxy-Klasse verlegt. Die Konfiguration erfolgt im Konstruktor wie vorher (siehe 4.6.6.1).
package de.fhr.vs_iw.hibernate.v2;
public final class HibernateProxy2 implements IAccountingProxy {
private AccountingService service = null;
private Session session = null;
private SessionFactory sessionFactory = null;
public HibernateProxy() {
AnnotationConfiguration cfg = new AnnotationConfiguration();
cfg.addAnnotatedClass(AccountingService.class);
cfg.addAnnotatedClass(Account.class);
cfg.addAnnotatedClass(AccountingEntry.class);
sessionFactory = cfg.buildSessionFactory();
}
public void close() {
if (service != null) {
session.getTransaction().commit();
session.close();
session = null;
sessionFactory = null;
service = null;
}
}
}
public IAccountingService connect(String[] args) {
if (service == null) {
session = sessionFactory.openSession();
session.beginTransaction();
service = (AccountingService)session.get(AccountingService.class, 0);
if (service == null) {
service = new AccountingService();
session.save(service);
}
}
return service;
}
Code 81: Class HibernateProxy2 (Hibernate - Variante 2)
In der connect()-Methode wird eine Hibernate-Sitzung geöffnet und eine Transaktion gestartet. Dann wird das Service-Objekt, und durch Kaskadierung auch alle zugehörigen Konten- und Buchungsobjekte, aus der Datenbank geladen. So sind all diese Objekte, und auch
neue Objekte die später den Sets der Service-Klasse hinzugefügt werden, als persistent anzusehen. Wenn in der Datenbank noch kein Service-Objekt vorhanden ist, wird ein neues Objekt
erzeugt und an die Hibernate-Sitzung übergeben.
Die Hibernate-Sitzung und die Transaktion werden bis zum Aufruf der close()-Methode
offen gehalten. Dort wird dann commit() aufgerufen und so die aktuellen Daten der gesamten
Hierarchie persistenter Objekte in die Datenbank gespeichert.
- 122 -
Datenhaltung - Hibernate
4.6.7.5. Fazit
Hibernate ist ein mächtiges und ausgereiftes Framework, um die Persistenz-Schicht einer
Anwendung zur Speicherung der Daten zu realisieren. Die Entwicklung von Hibernate war
mit ausschlaggebend für die JPA-Spezifikation (Java Persistence API) im Rahmen der Enterprise JavaBeans 3.0 Spezifikation und stellt damit quasi selbst den Standard für die Datenspeicherung bei großen Java-Anwendungen dar.
Die Einarbeitung ist im ersten Schritt etwas aufwendiger, da Hibernate sehr flexibel und für
jede mögliche Situation anwendbar ist, dafür zahlt sich die Persistenz durch einfache JavaObjekte für jeden Anwendungsfall aus.
Der hier beschriebene Umfang der Funktionalität von Hibernate stellt nur einen Bruchteil der
Möglichkeiten zum Einstieg in die Verwendung des Frameworks dar und ist speziell auf das
Buchhaltungsbeispiel bezogen. Die gesamten Möglichkeiten von Hibernate sind noch wesentlich umfangreicher.
- 123 -
Transport - Übersicht
5. Transport
5.1. Übersicht
Nachdem nun mehrere Möglichkeiten besprochen wurden, die Daten der Buchhaltungsanwendung zu speichern und dabei die Geschäftslogik zu implementieren, soll die Anwendung
jetzt auf mehrere Systeme verteilt werden.
5.1.1. Prinzipieller Ablauf
Dazu wäre es einfach ein Protokoll zu definieren, und damit Daten über ein Netzwerk zu transportieren, da die Anbindung an
Netzwerk-Sockets unter Java sehr
einfach möglich ist, sei es durch
asynchronches Versenden von
Datenpakten oder durch ein einfaches
synchrones
AnfrageAntwort-Protokoll.
Abbildung 64:
Anfrage-Antwort
Abbildung 63: Datenpakte
Ziel ist aber die Verwendung von
einem objektorientierten System,
bei dem Methoden von entfernten
Objekten aufgerufen werden, so
als ob das Objekt lokal vorhanden
wäre. Das System sollte im Idealfall vollständige Transparenz bieten, so daß sich ein Entwickler
nicht darum kümmern muß, wo
und wie genau der Aufruf einer
Methode eines Objekts ausgeführt
wird. Eine Middleware-Schicht
übernimmt dabei den Transport
des Aufrufs über das Netzwerk.
Abbildung 65: Entfernter Methodenaufruf
- 124 -
Transport - Übersicht
5.1.2. Erstellung mit Eclipse
Zur Realisierung der Middlewareschicht wird ein neues Modul für die Buchhaltungsanwendung erstellt (siehe 3.9.4). Wie beschrieben wird zuerst in Eclipse ein neues Package dafür
angelegt, z.B. mit dem Namen de.fhr.vs_iw.socket, im Menü unter:
File -> New -> Package
Dann wird wieder je eine eigene Klasse für jede der Schnittstellen der Buchhaltung erzeugt.
Durch die Implementierung der Schnittstellen kann das Modul nun wieder von der Benutzeroberfläche aufgerufen werden. Weiterhin benötigt das Modul aber noch eine Server-Seite, die
in einer eigenen Klasse erstellt wird, über:
File -> New -> Class
Zusammen mit einem Modul aus der Datenhaltung ergibt sich im Package Explorer von Eclipse z.B. die nebenstehende
Ansicht.
Ein Modul aus der Datenhaltung kann von
der Server-Klasse über die Buchhaltungsschnittstellen direkt verwendet werden,
damit bereits vorhandene Funktionalitäten
nicht nochmals implementiert werden
müssen. Auf diese Weise entsteht der gewünschte modulare Aufbau des gesamten
Systems.
Abbildung 66: Eclipse Package Explorer
Dazu wird die Proxy-Klasse des gewünschten Moduls (z.b. de.fhr.vs_iw.local) in der
Server-Klasse entweder direkt instanziert
IAccountingProxy proxy = new LocalProxy();
oder über die Methode loadProxy() der Klasse Utilities, durch ihren vollständigen Namen (über Reflection) geladen.
proxy = Utilities.loadProxy("de.fhr.vs_iw.local.LocalProxy");
Durch die Verwendung des Names wird das Datenhaltungsmodul leicht austauschbar. Dies ist
deshalb die Methode, die bevorzugt verwendet werden sollte (siehe 2.4.1). Anschließend kann
nun die Datenübertragung zwischen der Proxy-Klasse (hier SocketProxy) und der ServerKlasse (hier SocketServer) implementiert werden.
- 125 -
Transport - Eigener Transport über Sockets
5.2. Eigener Transport über Sockets
5.2.1. Allgemeines
Der Einstieg in die Transportschicht soll mit einer völlig eigenen Implementierung der Datenund Objektübertragung erfolgen. Dies soll einen Eindruck vermitteln welche Mechanismen
und Maßnahmen für die Anwendungs-Kommunikation notwendig sind. Deshalb erfolgt der
Ansatz auf einer grundlegenden Ebene, unter direkter Verwendung von Netzwerk-Sockets,
und wird wie eine Middleware-Schicht aufgebaut.
5.2.2. Anbindung an die Datenhaltung
Das Socket-Kommunikations-Modul soll auf ein beliebiges Modul der Datenhaltungsschicht
aufsetzen können, die bereits implementierte Geschäftslogik nutzen und lediglich die Kommunikationsfähigkeit hinzufügen.
5.2.2.1. SocketServer
Dazu verhält sich der SocketServer gegenüber einem Datenhaltungs-Modul wie ein Client.
Er instanziert die auf der Kommandozeile angegebene Proxy-Klasse eines vorhandenen Moduls, und lädt damit die zugehörige Service-Klasse.
package de.fhr.vs_iw.socket;
public final class SocketServer extends Thread {
private SocketServer(IAccountingService service, Socket socket);
public void run();
private String genOid(Object obj);
private String getOid(Object obj);
private ServerSideResponse process(ServerSideRequest req);
private ServerSideResponse remoteObjectResponse(Object o, Class c);
public static void main(String[] args) {
try {
if (args.length <= 1) {
System.err.println("Port und Proxy-Klasse angegeben");
return;
}
ServerSocket ss = new ServerSocket(Integer.parseInt(args[0]));
IAccountingProxy localProxy = Utilities.loadProxy(args[1]);
IAccountingService nextService
= localProxy.connect(Utilities.shiftArgs(args, 2));
while (true) {
try {
new SocketServer(nextService, ss.accept()).start();
} catch (IOException e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Code 82: Class SocketServer
- 126 -
Transport - Eigener Transport über Sockets
Als weiteres Kommandozeilenargument benötigt der SocketServer noch den Port auf dem er
lauschen soll. Auf diesem Port wird ein ServerSocket eröffnet, der mit accept() Verbindungen von Clients entgegen nimmt.
java de.fhr.vs_iw.socket.SocketServer <port> <proxy> [proxy-parameters]
Die restlichen Kommandozeilenargumente werden der angegebenen Proxy-Klasse als Parameter für die connect()-Methode übergeben. Dazu ist die Utilities-Methode
shiftArgs(String[] args, int number);
hilfreich, die die angegebene Anzahl Argumente vom Anfang des String-Arrays entfernt.
5.2.2.2. Parallele Server-Instanzen
Der SocketServer selbst ist von Thread abgeleitet. Damit wird für jeden Client, der sich mit
dem Server verbindet, eine eigener SocketServer instanziert und gestartet, der den verbundenen Socket und die Instanz der geladenen Service-Klasse erhält. Dies stellt die einfachste
Möglichkeit dar, die Anfragen von Clients parallel und unabhängig zu verarbeiten. Hier wird
dann auch deutlich, warum die Implementierung eines Moduls der Datenhaltungsschicht
threadsicher sein muß. Eben weil alle Threads auf die selbe Instanz eines Modules zugreifen.
5.2.3. Aufbau und Nutzung der Verbindung
Aufbau und Verwendung der Verbindung gehen vom Client aus. Der Server verarbeitet die
lediglich die eingehenden Anfragen.
5.2.3.1. Socket-Proxy-Klasse
Um dem Konzept der Schnittstellen und Module der Buchhaltung zu entsprechen, wird eine
Socket-Client-Klasse erstellt, welche die IAccountingProxy-Schnittstelle implementiert. Die
connect()-Methode wird im Sinne ihres Namens tätig und stellt die Socket-Verbindung zum
SocketServer her, die dann in der close()-Methode wieder geschlossen wird.
package de.fhr.vs_iw.socket;
public final class SocketProxy implements IAccountingProxy {
private BufferedReader reader = null;
private Socket socket = null;
private PrintWriter writer = null;
public void close() throws IOException {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
}
- 127 -
Transport - Eigener Transport über Sockets
public IAccountingService connect(String[] args) throws IOException {
if (args.length < 2) {
throw new IllegalArgumentException(
"Hostname und Port müssen angegeben werden");
}
socket = new Socket(args[0], Integer.parseInt(args[1]));
writer = new PrintWriter(socket.getOutputStream());
reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
try {
return (IAccountingService)requestObject(
IAccountingService.class, "SocketService", null);
} catch (AccountingException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
private ClientSideResponse request(String oid, String method,
Object... args);
protected Object requestObject(Class clas, String oid, String method,
Object... args);
protected Object request1(String oid, String method, Object... args)
throws IOException;
protected Object request2(String oid, String method, Object... args)
throws IOException, AccountingException;
}
Code 83: Class SocketProxy
Nach dem Herstellen der Verbindung wird der Ein- bzw. Ausgabestrom des Socket geöffnet.
Die Request-Methoden der Klasse übernehmen dann die eigentliche Kommunikation nach
dem definierten Protokoll (siehe 5.2.4) mit den dafür vorhandenen Klassen.
5.2.3.2. Verarbeitung auf dem Server
Auf dem Server wird nach dem Instanzieren (siehe 5.2.2.2) ebenso verfahren. Sobald die Server-Instanz eigenständig läuft wird in der run()-Methode der Ein- bzw. Ausgabestrom des
verbundenen Sockets geöffnet.
public void run() {
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
while (true) {
try {
ServerSideRequest req = new ServerSideRequest(br);
ServerSideResponse res = process(req);
res.send(pw);
} catch (SocketException e) {
break;
} catch (IOException e) {
break;
}
}
- 128 -
Transport - Eigener Transport über Sockets
}
try {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
socket.close();
} catch (IOException e) {
}
Code 84: run()-Methode von SocketServer
Dann werden kontinuierlich die Anfragen eines Clients gelesen, jeweils in der process()Methode verarbeitet und eine Antwort an den Client zurückgeschickt.
5.2.4. Definition eines eigenes Protokolls
Zur Kommunikation wird ein selbst definiertes Request-Response-Protokoll verwendet, das
auf einer Socket-Verbindung aufsetzt und alle Daten in String-Form zeilenweise überträgt.
writer = new PrintWriter(socket.getOutputStream())
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))
Dies erlaubt die unproblematische Verwendung der bei Java mitgelieferten Reader- und Writer-Klassen mit den Streams des Socket.
Das Protokoll orientiert sich im Aufbau an der Objektorientierung von Java und bestehenden
objektorientierten Kommunikationsverfahren (RMI, CORBA) und versucht den Aufruf einer
Methode eines entfernten Objekts nachzubilden.
Es wird in mehreren Klassen implementiert, die auf Client- und Serverseite die Verarbeitung
der übertragenen Daten übernehmen.
Abbildung 67: Socket-Anfrage-Antwort-Klassen
- 129 -
Transport - Eigener Transport über Sockets
5.2.4.1. Anfrage
Eine Anfrage besteht aus bei diesem Protokoll aus mindestens drei Textzeilen:
Objekt-Identifkator
Methodenname
[Parameter]
[Parameter]
[...]
Anfrage-Trenner
Resource 11: Socket-Protokoll - Anfrage
Zuerst wird ein Objekt-Identifikator gesendet, der Angibt welches Objekt auf dem Server aufgerufen werden soll, sowie der Name der aufzurufenden Methode. Dann folgt eine beliebige
Anzahl Parameter für den Methodenaufruf, die aber nicht notwendigerweise vorhanden sein
müssen. Die Anfrage wird mit einer speziellen Trennzeile abgeschlossen.
5.2.4.2. Anfrage-Klassen
Als Basis-Klasse für Anfragen an den Server dient die abstrakte Klasse Request, die die o.g.
Elemente des Anfrage-Protokolls enthält.
package de.fhr.vs_iw.socket;
abstract class Request {
String method = null;
String oid = null;
final List<Object> params;
static final String DELIM;
}
Code 85: Class Request
Davon werden die konkreten Klassen zur Verarbeitung einer Anfrage abgeleitet. Durch die
Klasse ClientSideRequest wird das Erstellen und Versenden der Anfrage über den zuvor
geöffneten PrintWriter durchgeführt.
package de.fhr.vs_iw.socket;
final class ClientSideRequest extends Request {
ClientSideRequest(String oid, String method, Object... args) {
super();
this.oid = oid;
this.method = method;
for (Object o : args) {
params.add(o);
}
}
- 130 -
Transport - Eigener Transport über Sockets
void send(PrintWriter pw) throws Exception {
ArrayList<String> strparams = new ArrayList<String>();
for (Object o : params) {
strparams.add(StringUnMarshaller.marshall(o));
}
pw.println(oid);
pw.println(method);
for (String str : strparams) {
pw.println(str);
}
pw.println(DELIM);
pw.flush();
}
}
Code 86: Class ClientSideRequest
Der Aufruf von flush() nach jeder Anfrage ist explizit notwendig, da sonst die Daten vom
PrintWriter bzw. vom Socket vor dem eigentlichen Senden bis zu einer bestimmten Datenmenge gepuffert würden, die bei dieser Anwendung praktisch nicht erreicht wird.
Auf der Server-Seite wird die Anfrage von der zugehörigen Klasse ServerSideRequest entgegengenommen. Mit dem vorhandenen BufferedReader wird solange vom Eingabestrom
des Sockets gelesen bis ein Anfrage-Trenner gefunden wird.
Nach dem Einlesen einer Anfrage stellt die Klasse Methoden zur Verfügung, mit denen die
empfangenen Daten und Werte gezielt abgefragt werden können.
package de.fhr.vs_iw.socket;
final class ServerSideRequest extends Request {
ServerSideRequest(BufferedReader br) throws Exception {
super();
oid = readLine(br);
method = readLine(br);
ArrayList<String> strparams = new ArrayList<String>();
while (true) {
String line = readLine(br);
if (line.equals(DELIM)) {
break;
}
strparams.add(line);
}
for (String str : strparams) {
params.add(StringUnMarshaller.unmarshall(str));
}
}
private String readLine(BufferedReader br) throws IOException {
String line = br.readLine();
if (line == null) {
throw new IOException("end of input stream");
}
return line;
}
- 131 -
Transport - Eigener Transport über Sockets
String method() {
return method;
}
String oid() {
return oid;
}
Object[] params() {
return params.toArray();
}
Class[] types() {
Class[] types = new Class[params.size()];
for (int i = 0; i < params.size(); i++) {
types[i] = params.get(i).getClass();
}
return types;
}
}
Code 87: Class ServerSideResponse
Zusätzlich zur Rückgabe-Methode für die empfangenen Parameter params() gibt es die Methode types(), die die Typen der Parameter zurückgibt, was später für den Methodenaufruf
über Reflection (siehe 5.2.6.1) benötigt wird.
5.2.4.3. Antwort
Die Antwort ist ebenfalls zeilenweise Aufgebaut und hat immer drei Zeilen:
Objekt-Identifikator
Rückgabewert
Antwort-Trennzeile
Resource 12: Socket-Protokoll - Antwort
Bei einer empfangenen Antwort müssen drei verschiedene Möglichkeiten unterschieden und
entsprechend verarbeitet werden:

Entferntes Objekt:
Wenn die Antwort mit einem Objekt-Identifikator beginnt, hat der Server eine Referenz zu einem entfernten Objekt zurückgegeben, für das mit diesem Identifikator ein
lokaler Stellvertreter erzeugt werden muß. Der Rückgabewert stellt den Typ (Class)
des entfernten Objekts dar. (siehe 5.2.6)

Ergebnis eines Methodenaufrufs:
Wenn kein Objekt-Identifikator sondern eine Leerzeile am Beginn der Antwort steht
handelt es sich um den normalen Rückgabewert eines Methodenaufrufs.

Ausnahme:
Eine Ausnahme wird wie der Rückgabewert eines Methodenaufrufs (ohne ObjektIdentifikator) übertragen, und dann vom Client erkannt und in einer ServerException
(siehe 5.2.7) verpackt und geworfen.
- 132 -
Transport - Eigener Transport über Sockets
Die Trennzeile würde hier im Prinzip nicht unbedingt benötigt werden, da die Länge der Antwort fest vorgegeben ist, wird aber trotzdem für einen "sauberen" Abschluß verwendet.
5.2.4.4. Antwort-Klassen
Wie auch bei der Anfrage teilt sich die Verarbeitung der Antworten auf Server und Client auf,
ausgehend von der abstrakten Klasse Response.
package de.fhr.vs_iw.socket;
abstract
String
Object
String
static
}
class Response {
oid;
value;
method;
final String DELIM = "-#-responseend-#-";
Code 88: Class Response
Auf der Serverseite wird dann die Antwort als ServerSideResponse analog zu einer Anfrage
auf der Clientseite erzeugt und verschickt.
package de.fhr.vs_iw.socket;
final class ServerSideResponse extends Response {
ServerSideResponse(Object value) {
super();
this.oid = null;
this.value = value;
}
ServerSideResponse(String oid, Class clas) {
super();
this.oid = oid;
this.value = clas;
}
}
void send(PrintWriter pw) throws Exception {
String strvalue = StringUnMarshaller.marshall(value);
pw.println(oid);
pw.println(strvalue);
pw.println(DELIM);
pw.flush();
}
Code 89: Class ServerSideResponse
Der Client liest die Antwort über die Klasse ClientSideResponse ein. Hier wird dann geprüft ob die Antwort eine Ausnahme ist, und falls ja, diese entsprechend als
ServerException geworfen (siehe 5.2.4.3).
- 133 -
Transport - Eigener Transport über Sockets
package de.fhr.vs_iw.socket;
final class ClientSideResponse extends Response {
ClientSideResponse(BufferedReader br) throws Exception {
super();
oid = readLine(br);
String strvalue = readLine(br);
if (!readLine(br).equals(DELIM)) {
throw new IOException("delimiter expected");
}
value = StringUnMarshaller.unmarshall(strvalue);
}
if (value instanceof Throwable) {
throw new ServerException((Throwable)value);
}
private String readLine(BufferedReader br) throws IOException {
String line = br.readLine();
if (line == null) {
throw new IOException("end of input stream");
}
return line;
}
}
String getOid() {
return oid;
}
String getMethod() {
return method;
}
Object getValue() {
return value;
}
Code 90: Class ClientSideResponse
- 134 -
Transport - Eigener Transport über Sockets
5.2.5. Un-/Marshalling der Objekte
Ein wesentlicher Punkt ist nun die Umformung von Java-Objekten bzw. deren datenmäßigem
Inhalt in ein Format, das zu dem String-basierten Protokoll paßt, und auch wieder zurück, das
so genannte Marshalling und Unmarshalling. Dazu wird ein Objekt in folgender grundlegender Form dargestellt
Java-Type (Klasse) : spezifische Werte
Durch einen Doppelpunkt getrennt wird die Klasse eines Objekt und die für ein Objekt dieser
Klasse spezifischen Werte als eine Zeile im Protokoll übertragen.
5.2.5.1. Die Klasse StringUnMarshaller
Diese Umwandlungen übernimmt die statische Hilfs-Klasse StringUnMarshaller in beiden
Richtungen über die entsprechenden Methoden marshall() und unmarshall().
package de.fhr.vs_iw.socket;
final class StringUnMarshaller {
static synchronized String marshall(Object o) throws Exception {
static synchronized Object unmarshall(String line)
...
}
Code 91: Class StringUnMarshaller (Teil 1)
Diese beiden Methoden werden beim Verarbeiten von Anfragen und Antworten aufgerufen.
5.2.5.2. Verarbeitung definierter Superklassen
Da nicht wie z.B. bei der Java-Serialisierung annähernd alle Objekte verarbeitet werden müssen, wird der Vorgang über einen vorgegebenen Satz von Klassen gesteuert, deren Objekte
bzw. Objekte von Unterklassen spezifisch umgewandelt werden.
package de.fhr.vs_iw.socket;
final class StringUnMarshaller {
private static final HashSet<Class> CLASSES = new HashSet<Class>();
static {
CLASSES.add(Class.class);
CLASSES.add(Set.class);
CLASSES.add(Integer.class);
CLASSES.add(Throwable.class);
CLASSES.add(String.class);
CLASSES.add(AccountType.class);
CLASSES.add(Double.class);
}
...
}
Code 92: Class StringUnMarshaller (Teil 2)
- 135 -
Transport - Eigener Transport über Sockets
5.2.5.3. Aufruf der Un-/Marshal-Methoden
Durch Prüfung eines Objekts mit isAssignableFrom() gegenüber allen in der obigen Auflistung festgelegten Klassen wird die seine Zulässigkeit festgestellt. Die entsprechende Methode
zur Verarbeitung des Objekts wird aus dem Namen der festgelegten Klasse mit vorangestelltem "marshal" bzw. "unmarshal" ermittelt, und über Reflection aufgerufen.
package de.fhr.vs_iw.socket;
final class StringUnMarshaller {
...
private
private
private
private
private
private
private
private
private
private
private
private
private
private
static
static
static
static
static
static
static
static
static
static
static
static
static
static
String
String
String
String
String
String
String
Object
Object
Object
Object
Object
Object
Object
marshalAccountType(Object o)
marshalClass(Object o)
marshalDouble(Object o)
marshalInteger(Object o)
marshalSet(Object o)
marshalString(Object o)
marshalThrowable(Object o)
unmarshalAccountType(String s)
unmarshalClass(String s)
unmarshalDouble(String s)
unmarshalInteger(String s)
unmarshalSet(String s)
unmarshalString(String s)
unmarshalThrowable(String s)
}
Code 93: Class StringUnMarshaller (Teil 3)
5.2.5.4. Einfache Objekte
Bei einfachen Objekten wird beim Marshalling von den Methoden der Datenwert als String
dargestellt und zurückgegeben.





Integer.toString(((AccountType)object).getId());
((Class)object).getName();
((Double)object).toString();
((Integer)object).toString();
(String)object;
Dem zurückgelieferten Wert wird wie oben angegeben (siehe 5.2.5) die Information über die
Klasse des Objekts vorangestellt.
Im umgekehrten Fall, beim unmarshalling, wird genauso verfahren und mit der Information
aus einem String ein neues Objekt erzeugt. Die vorangestellte Information über die Klasse des
Objekts wird zuvor benutzt um die richtige Methode aufzurufen.





AccountType.getAccountTypeById(Integer.parseInt(string));
Class.forName(string);
Double.valueOf(string);
Integer.valueOf(string);
string
- 136 -
Transport - Eigener Transport über Sockets
5.2.5.5. Kollektionen
Die Verarbeitung einer Kollektion (Set) erfordert noch einen weiteren Schritt, da jedes enthaltene Objekt selbst auch verarbeitet werden muß.
private static String marshallSet(Object object) throws Exception {
StringBuffer sb = new StringBuffer();
for (Object obj : (Set)object) {
sb.append(marshall(obj));
sb.append(",");
}
return sb.toString();
}
Code 94: Method mashalSet()
Für jedes enthaltene Objekt wird nochmals die marshall()-Methode aufgerufen. Die erhaltenen Teil-Strings werden durch Komma getrennt als Gesamt-String-Darstellung für die Kollektion zurückgegeben.
private static Object unmarshallSet(String string) throws Exception {
HashSet set = new HashSet();
for (String str : string.split(",")) {
if (str != null && !str.equals("")) {
set.add(unmarshall(str));
}
}
return set;
}
Code 95: Method unmarshalSet()
Beim unmarshalling wird der Gesamt-String der Kollektion bei den Kommas in Teil-Strings
getrennt und für jeden Teil-String die unmarshall()-Methode aufgerufen. Die daraus resultierenden Objekte werden in einem neuen Set zurückgegeben.
5.2.5.6. Ausnahmen
Bei Ausnahmen werden neben dem allgemeinen Typen Throwable, auch der spezifische Typ
der Ausnahme und die Nachricht, durch Komma getrennt, übertragen.
private static String marshallThrowable(Object object) {
Throwable throwable = (Throwable)object;
return throwable.getClass().getName() + "," + t.getMessage();
}
private static Object unmarshallThrowable(String string) throws Exception {
int p = string.indexOf(",");
String type = string.substring(0, p);
String value = string.substring(p + 1);
Class c = Class.forName(type);
Constructor co = c.getDeclaredConstructor(new Class[] { String.class });
return co.newInstance(new Object[] { value });
}
Code 96: Methoden marshalThrowable() und unmarshalThrowable()
- 137 -
Transport - Eigener Transport über Sockets
5.2.6. Entfernte Objekte
Wie angesprochen (siehe 5.2.4) arbeitet das Protokoll mit dem Ansatz von eindeutig identifizierbaren entfernten Objekten deren Methoden aufgerufen werden.
5.2.6.1. Aufruf der Methoden über Reflection
Der SocketServer erhält vom Client in jeder Anfrage einen Objekt-Identifikator (OID) durch
den er das gewünschte Objekt in einer HashMap auffinden kann.
private final HashMap<String, Object> objects
= new HashMap<String, Object>();
Object object = objects.get(request.oid());
Sofern unter der angegebenen OID ein Objekt vorhanden ist, wird die in der Anfrage angegebene Methode davon über Reflection aufgerufen.
Class clas = object.getClass();
Method m = clas.getMethod(request.method(), request.types());
Object result = m.invoke(object, params);
Das Ergebnis (result) des Methodenaufrufs wird zurückgegeben und in der Antwort an den
Client geschickt (siehe 5.2.4.3).
response = new ServerSideResponse(result);
Durch diese Vorgehensweise wird eine Anfrage in der process()-Methode durch den
SocketServer verarbeitet. Durch das von den Anfrage- und Antwort-Klassen übernommene
Un-/Marshalling kann der Server die Aufrufe der Methoden der "entfernten" Objekte völlig
unproblematisch ausführen und das Ergebnis zurückschicken.
5.2.6.2. Übertragung der entfernten Objekte
Lediglich wenn der Rückgabewert eines Methodenaufrufs (getAccountByNumber(),
getAccountingEntryById()) ein Objekt liefert, das keinen Datenwert für sich, sondern
selbst wieder ein entferntes Objekt (IAccount, IAccountingEntry) darstellt wird etwas von
der normalen Verarbeitung abgewichen und das Objekt speziell behandelt.
if (result instanceof IAccount) {
response = remoteObjectResponse(result, IAccount.class);
} else if (result instanceof IAccountingEntry) {
response = remoteObjectResponse(result, IAccountingEntry.class);
} else {
response = new ServerSideResponse(result);
}
Durch die Methode remoteObjectResponse() wird eine passende Antwort erzeugt und der
Client erhält nicht das Objekt selbst, sondern nur die OID, um es später entfernt aufrufen zu
können.
- 138 -
Transport - Eigener Transport über Sockets
private ServerSideResponse remoteObjectResponse(Object o, Class c) {
String oid = getOid(o);
if (oid == null) {
oid = genOid(o);
objects.put(oid, o);
}
return new ServerSideResponse(oid, c);
}
Code 97: Methode remoteObjectResponse()
Sofern für das Objekt noch keine OID vergeben wurde (getOid()), wird eine neue erzeugt
(genOid()), und damit das Objekt in der HashMap zur Verwaltung der entfernten Objekte
abgelegt und in der Antwort zusammen mit dem Objekt-Typ (Class) an den Client geschickt.
Um festzustellen ob für ein Objekt bereits eine OID vergeben wurde, müssen die Schlüssel
der HashMap durchsucht werden.
private String getOid(Object object) {
for (Entry<String, Object> ent : objects.entrySet()) {
if (ent.getValue() == object) {
return ent.getKey();
}
}
return null;
}
private String genOid(Object object) {
return Integer.toHexString(object.hashCode());
}
Code 98: Methoden getOid() und genOid()
Als OID für ein Objekt wird die hexadezimale Darstellung seines hashCode() in der Virtuellen Maschine verwendet. Aber auch jede andere Methode, die innerhalb einer SocketServerInstanz eine eindeutige OID liefert (z.B. einfach eine Nummer hochzählen), ist anwendbar, da
ein Client bei diesem verteilten System immer nur mit genau einem Server verbunden ist und
dessen Objekte aufruft.
Da ein Client als Ausgangspunkt aller Aufrufe Zugriff auf eine Instanz der entfernten ServiceKlasse benötigt, wird eine spezielle OID für den Einstieg definiert.
if (req.oid().equals("SocketService")) {
return new ServerSideResponse(serviceOid, IAccountingService.class);
}
Weil die Erstellung eines eigenen Namensdienstes, der normalerweise zu einem verteilten
System gehört, doch ein gewissen Aufwand darstellt, wird einfach ein festgelegter String
"SocketService" wie eine OID verwendet und extra abgefragt.
requestObject(IAccountingService.class, "SocketService", null);
Bei einer Anfrage mit dieser speziellen OID wird als Antwort die eigentliche OID
(serviceOid) eines entfernten Service-Objekts zurückgegeben mit der ein lokaler Stellvertreter dafür erzeugt werden kann. Das Objekt und seine OID werden zusammen mit einer Server-Instanz erzeugt und in der HashMap der entfernten Objekte abgelegt.
- 139 -
Transport - Eigener Transport über Sockets
5.2.6.3. Lokale Stellvertreter
Da ein entferntes Objekt auch entfernt bleibt, auf der Clientseite jedoch mit den JavaObjekten der Buchhaltung gearbeitet werden soll, ist für jedes entfernte Objekt ein zugehöriges lokales Stellvertreter-Objekt (Stub) (AccountingService, Account, AccountingEntry)
notwendig, das entsprechend die jeweilige Buchhaltungsschnittstelle implementiert.
Für jede OID, die vom entfernten Server zurückgeschickt wird, wird ein Stellvertreter-Objekt
erzeugt. Jede Methode eines solchen lokalen Objekts ruft lediglich mit der OID die zugehörige Methode des entfernten Objekts auf, unter Verwendung der in der Klasse SocketProxy
definierten Methoden request1(), request2() und requestObject().
Die Buchhaltungsmethoden sind dann vom Prinzip her wie die folgenden Beispiele aus der
Klasse AccountingService implementiert.
public Set<Integer> getAccounts() throws IOException {
return (Set<Integer>)proxy.request1(oid, "getAccounts");
}
private HashMap<Integer, Account> accounts
= new HashMap<Integer, Account>();
public IAccount getAccountByNumber(Integer number)
throws IOException, AccountingException {
Account account = accounts.get(number);
if (account == null) {
account = (Account)proxy.requestObject(IAccount.class, oid,
"getAccountByNumber", number);
accounts.put(number, account);
}
return account;
}
public void removeAccount(Integer number)
throws IOException, AccountingException {
proxy.request2(oid, "removeAccount", number);
}
Code 99: Implementierung einiger Buchhaltungsmethoden (Socket - AccountingService)
Jede Methode einer Buchhaltungsschnittstelle ruft die passende Request-Methode mit allen
übergebenen Parametern auf und gibt das Ergebnis direkt zurück. Lediglich die Methoden
getAccountByNumber() und getAccountingEntryById() haben noch eine weitere Funktion. Hier werden die entfernten Objekte für Konten bzw. Buchungen angefragt und die erhaltenen lokalen Stellvertreter-Objekte werden in HashMaps gespeichert, damit nicht ständig
neue Stellvertreter-Objekte erzeugt werden.
5.2.6.4. Anfrage-Methoden
Die Anfrage-Methoden request1() und request2() geben jeweils einfache Werte zurück,
unterscheiden sich in ihrer Signatur aber in den geprüften Ausnahmen, die geworfen werden,
damit diese Anfrage-Methoden aus den jeweiligen Buchhaltungs-Methoden einfach aufgerufen werden können.
- 140 -
Transport - Eigener Transport über Sockets
Object request1(String oid, String method, Object... args)
throws IOException {
try {
return request2(oid, method, args);
} catch (AccountingException e) {
// Eine aufgetretene Buchhaltungsausnahme wird in eine
// E/A-Ausnahme umgewandelt
IOException ioe = new IOException();
ioe.initCause(e);
throw ioe;
}
}
Code 100: Method request1() (SocketProxy)
Die Methode request1() wirft eine lediglich eine IOException bei Kommunikationsfehlern,
und wird von den Methoden verwendet, die nach der Definition in der Schnittstelle auch nur
eine IOException werfen dürfen. Intern wird wiederum die Methode request2() aufgerufen
und lediglich die zusätzliche AccountingException als IOException verpackt.
Object request2(String oid, String method, Object... args)
throws IOException, AccountingException {
try {
return request(oid, method, args).getValue();
} catch (IOException e) {
// Eine E/A-Ausnahme wird direkt geworfen
throw e;
} catch (ServerException e) {
// Eine Buchhaltungsausnahme auf dem Server wird direkt geworfen
if (e.getCause() instanceof AccountingException) {
throw (AccountingException)e.getCause();
}
// Jede andere Server-Ausnahme wird als E/A-Ausnahme geworfen
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
} catch (final Exception e) {
// Jede andere Ausnahme wird als E/A-Ausnahme geworfen
IOException ioe = new IOException();
ioe.initCause(e);
throw ioe;
}
}
Code 101: Method request2() (SocketProxy)
Auch die Methode request2() verpackt nur mögliche Ausnahmen auf die passende Weise,
so daß letztlich nur IOExceptions und AccountingExceptions geworfen werden. Die eigentliche Funktionalität zur Datenübertragung durch die Request-Klasse wird in der privaten
Methode request() aufgerufen.
private ClientSideResponse request(String oid, String method,
Object... args) throws Exception {
ClientSideRequest req = new ClientSideRequest(oid, method, args);
req.send(writer);
return new ClientSideResponse(reader);
}
Code 102: Method request() (SocketProxy)
- 141 -
Transport - Eigener Transport über Sockets
Die Methode requestObject() schließlich gibt nicht nur einen einfachen Wert, sondern ein
Objekt der angegebenen Klasse als lokalen Stellvertreter für ein entferntes Objekt zurück.
Damit das entfernte Objekt durch ein Stellvertreter-Objekt austauschbar ist, muß mit Schnittstellen gearbeitet werden.
Die Verarbeitung von Ausnahmen erfolgt auf die gleiche Weise wie in der Methode
request2().
Object requestObject(Class clas, String oid, String method,
Object... args) throws IOException, AccountingException {
if (!clas.isInterface()) {
throw new IOException("requested type is not an interface");
}
ClientSideResponse res;
try {
res = request(oid, method, args);
} catch (...) {
...
}
if (res.getOid() == null) {
throw new IOException("no remote object received");
}
if (!res.getValue().equals(clas)) {
throw new IOException("unexpected remote object type");
}
if (clas.equals(IAccount.class)) {
return new Account(this, res.getOid());
} else if (clas.equals(IAccountingEntry.class)) {
return new AccountingEntry(this, res.getOid());
} else if (clas.equals(IAccountingService.class)) {
return new AccountingService(this, res.getOid());
}
throw new IOException("no proxy for remote object available");
}
Code 103: Method requestObject() (SocketProxy)
Je nachdem, ob eine OID von Server empfangen wurde, wird damit ein Objekt instanziert
oder eine Ausnahme geworfen. Im Beispiel erfolgt die Instanzierung direkt, durch Unterscheidung der gewünschten Schnittstelle über if-Anweisungen, wobei es sicher elegantere
Möglichkeiten gäbe ein Stellvertreter-Objekt für eine angegebene Schnittstelle zu erhalten
(Zuordnung in einer Map, dynamische Stub-Generierung).
5.2.7. Fazit
Dieses Beispiel zeigt, daß es einen nicht unerheblichen Aufwand darstellt, ein umfassendes
objektorientiertes verteiltes System zu entwickeln, wobei hier etliche Punkte noch gar nicht
betrachtet wurden (z.B. verteilte Garbage-Collection, Namensdienst, Trennzeichen im Protokoll). Glücklicherweise gibt es einige ausgereifte Technologien und Bibliotheken, die unter
Java verwendbar sind, und alle notwendigen Funktionalitäten für den Aufbau von verteilten
Systemen zur Verfügung stellen.
- 142 -
Transport - Remote Method Invocation (RMI)
5.3. Remote Method Invocation (RMI)
5.3.1. Allgemeines
Remote Method Invocation (RMI) [33] ist die Java-eigene Art, die Methoden von entfernten
Objekten aufzurufen. Das bedeutet, daß sich das Objekt auf jeden Fall in einer separaten virtuellen Maschine, die auch auf einem anderen Rechner laufen kann, befindet [34].
Abbildung 68: Schematischer Ablauf von RMI
Als Basis dient ein Namensdienst, bei dem sich Objekte, die einen entfernten Dienst anbieten,
unter einem festgelegten Namen registrieren (1), und dann über den Namen abgerufen werden
können (2). Man erhält dann einen lokalen Stellvertreter für dieses Objekt, von dem die Methoden aufgerufen (3) und auf dem entfernten System ausgeführt (4) werden. Wegen dem
Austausch durch lokale Stellvertreter werden die Methoden in einer Schnittstelle definiert.
Die Komplexität der Netzwerk-Kommunikation wird vor der Anwendung verborgen [36].
Abbildung 69: RMI-Kommunikations-Architektur
Auf die genaue Funktionsweise von RMI soll hier nicht eingegangen, sondern hauptsächlich
die Anbindung an das Buchhaltungssystem beschrieben werden (siehe auch [35]).
- 143 -
Transport - Remote Method Invocation (RMI)
5.3.2. Entfernte Objekte
Ein entferntes Objekt basiert auf einer Schnittstelle und einer implementierenden Klasse, die
nach den folgenden Vorgaben aufgebaut wird.
5.3.2.1. Remote-Interface
Wie bereits angesprochen wird eine Schnittstelle für die entfernten Methoden definiert, die
lediglich einige Kriterien erfüllen muß:



Vom Interface java.rmi.Remote abgeleitet, damit die Schnittstelle als Remote-Interface anerkannt wird
Alle definierten Methoden müssen eine RemoteException oder eine Superklasse davon werfen
Die verwendeten Parameter und Rückgabetypen müssen serialisierbar
(Interface java.io.Serializable) sein
Die im Buchhaltungsmodell definierten Schnittstellen (IAccountingServce, IAccount,
IAccountingEntry, siehe 2.2) erfüllen diese Anforderungen bereits, und können direkt als
Remote-Interface verwendet werden.
5.3.2.2. Remote-Object
Wie in den vorigen Kapiteln muß nun eine Service-Klasse, Konto-Klasse und BuchungsKlasse erstellt werden, die wieder jeweils eine der Schnittstellen implementieren. Durch die
Ableitung von java.rmi.Remote erkennt RMI Objekte als Remote-Object an.
Zugriff auf die Service-Klasse selbst erhält ein Client über den Namensdienst. Aber wenn nun
beim Aufruf von getAccountByNumber() oder getAccountEntryById() der Service-Klasse
ein Konten- oder Buchungsobjekt zurückgegeben wird, erhält ein Client automatisch nur den
lokalen Stellvertreter für das zurückgegebene Objekts.
Es reicht aber nicht, nur das Remote-Interface zu implementieren. Die Klassen von entfernten
Objekten müssen zusätzlich noch um einige Fähigkeiten bzgl. der Kommunikation über TCPVerbindungen erweitert werden. Dies geschieht durch das Ableiten von der Klasse
java.rmi.server.UnicastRemoteObject und den Aufruf des Konstruktors der Superklasse.
Dies gilt für alle drei Klassen unseres Systems (Service, Konto, Buchung).
5.3.3. Anbindung an die Datenhaltung
Auch die RMI-Implementierung zur Datenübertragung wird an eines der bestehenden Module
zur Datenhaltung angebunden.
5.3.3.1. Proxy-Instanzierung
Wie bei jedem Client eines Buchhaltungssystems wird eine Instanz der angegebenen Proxy-Klasse gebildet und davon die connect()-Methode aufgerufen. Damit kann die RMIImplementierung auf die Objekte des Datenhaltungmoduls zugreifen.
- 144 -
Transport - Remote Method Invocation (RMI)
5.3.3.2. Weitergabe der Aufrufe
Alle Aufrufe der entfernten Methoden werden direkt an die Datenhaltung weitergegeben. Das
RMI-System "stülpt" sich praktisch nur über die Datenhaltung und übernimmt die Datenübertragung, was allerdings manuell zu implementieren ist.
public Double calculateBalance(Integer number)
throws IOException, AccountingException {
return nextService.calculateBalance(number);
}
Code 104: Method calculateBalance() bei RMI
Dies hat aber nichts mit RMI an sich zu tun, sondern mit dem Aufbau des Buchhahltungssystems, und muß deshalb für jede einzelne Methode manuell ausprogrammiert werden.
5.3.4. Server - RMI-Registry
Im Prinzip kann man im ersten Ansatz nicht von einem RMI-Server sprechen, da zwei Dienste (siehe 5.3.1) benötigt werden, zum Einen der Namensdienst und zum anderen das entfernte
Objekt selbst.
5.3.4.1. Registry starten
Bei größeren Anwendungen läuft der Namensdienst, die RMI-Registry, meistens irgendwo als
eigenständiger Dienst, bei dem sich Objekte, die auf verschiedenen Systemen zur Verfügung
stehen zentral registrieren können. Beim JDK ist eine RMI-Registry enthalten, die durch
rmiregistry [Optionen] [Port]
auf dem angegebenen Port (standardmäßig 1099) gestartet werden kann.
5.3.4.2. Registry einbinden
Über die Schnittstelle java.rmi.Registry wird in einer Anwendung auf den Namensdienst
zugegriffen. Eine Instanz dieser Klasse, und damit eine Verbindung zu einer Registry erhält
man durch die Klasse java.rmi.LocateRegistry.
package java.rmi.registry;
public class LocateRegistry {
static Registry createRegistry(int port);
static Registry getRegistry();
static Registry getRegistry(int port);
static Registry getRegistry(String host);
static Registry getRegistry(String host, int port);
}
Code 105: Class LocateRegistry
- 145 -
Transport - Remote Method Invocation (RMI)
Mit den verschiedenen Methoden kann auf eine Registry, die auf dem angegebenen Host und
dem angegebenen Port läuft zugegriffen werden. Interessant ist aber auch die Methode
createRegistry(), wodurch eine Anwendung ihre eigene Registry erzeugen, und damit Objekte zur Verfügung stellen kann.
5.3.4.3. Objekt registrieren
Nachdem man sich zu einer Registry verbunden, oder selbst eine erzeugt hat, können mit den
Methoden einer Registry-Instanz die entfernten Objekte registriert oder geholt werden.
package java.rmi.registry;
public interface Registry extends Remote {
void bind(String name, Remote obj);
String[] list();
Remote lookup(String name);
void rebind(String name, Remote obj);
void unbind(String name);
}
Code 106: Interface Registry
Durch den Aufruf von bind() bzw. rebind() wird ein Objekt unter dem angegebenen Namen registriert. Mit list() kann eine Liste aller registrierten Namen ausgegeben werden.
5.3.4.4. Der vollständige Server
Der RMIServer stellt beide nötigen Dienste, Registry und entferntes Objekt, zur Verfügung.
package de.fhr.vs_iw.rmi;
public final class RMIServer {
public static void main(String[] args) {
try {
if (args.length <= 0) {
System.err.println("Eine Proxy-Klasse muß angegeben werden");
return;
}
IAccountingProxy localProxy = Utilities.loadProxy(args[0]);
IAccountingService nextService
= localProxy.connect(Utilities.shiftArgs(args, 1));
IAccountingService service = new AccountingService(nextService);
}
}
Registry registry
= LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
registry.rebind("RMIAccountingService", service);
} catch (Exception e) {
e.printStackTrace();
}
Code 107: Class RMIServer
- 146 -
Transport - Remote Method Invocation (RMI)
Zuerst wird von der als Kommandozeilenargument angegebenen Proxy-Klasse (von einem
Modul der Datenhaltung) eine Instanz gebildet, um damit ein Objekt der zugehörigen ServiceKlasse zu erhalten. Alle weiteren Kommandozeilenargumente werden der connect()Methode zum Laden des Service-Objekts übergeben.
Mit diesem Service-Objekt wird ein RMI-Service-Object erzeugt, das wie beschrieben
(siehe 5.3.3.2) die RMI-Methodenaufrufe weitergibt. Anschließend wird eine eigene
RMI-Registry auf dem Standardport 1099 erzeugt, und das RMI-Service-Object unter dem
Namen "RMIAccountingService" dort registriert.
5.3.5. Entfernte Buchhaltungsobjekte
Die Funktionalität der RMI-Buchhaltung basiert schließlich auf entfernten Objekten und deren lokalen Stellvertretern.
5.3.5.1. Service-Klasse
Die RMI-Service-Klasse kapselt die Service-Klasse eines anderen Buchhaltungsmoduls
(IAccountingService nextService) für die Verwendung mit RMI.
package de.fhr.vs_iw.rmi;
public final class AccountingService extends UnicastRemoteObject
implements IAccountingService {
private HashMap<Integer, Account> accountMap
= new HashMap<Integer, Account>();
private HashMap<Integer, AccountingEntry> entryMap
= new HashMap<Integer, AccountingEntry>();
private final IAccountingService nextService;
protected AccountingService(IAccountingService next) throws IOException {
super();
nextService = next;
}
public IAccount getAccountByNumber(Integer number)
throws IOException, AccountingException {
Account account = accountMap.get(number);
if (account == null) {
account = new Account(nextService.getAccountByNumber(number));
accountMap.put(number, account);
}
return account;
}
public IAccountingEntry getAccountingEntryById(Integer id)
throws IOException, AccountingException {
AccountingEntry entry = entryMap.get(id);
if (entry == null) {
entry = new AccountingEntry(nextService.getAccountingEntryById(id));
entryMap.put(id, entry);
}
return entry;
}
- 147 -
Transport - Remote Method Invocation (RMI)
public Double calculateBalance(Integer number)
throws IOException, AccountingException {
return nextService.calculateBalance(number);
}
...
}
<More Service-Methods>
Code 108: Class AccountingService (RMI)
Die Klasse ist von UnicastRemoteObject abgeleitet, und ruft diese (super()) im Konstruktor auf. Jede Methode der RMI-Service-Klasse ruft lediglich die korrespondierende Methode
des gekapselten Service-Objekts auf. Als einzige Funktionalität werden die Objekte für Konten und Buchungen in HashMaps verwaltet.
5.3.5.2. Konto-Klasse
Die RMI-Konto-Klasse unterscheidet sich vom Prinzip her nicht von der Service-Klasse.
Auch hier werden nur Methodenaufrufe an ein anderes gekapseltes Kontenobjekt weitergegeben.
package de.fhr.vs_iw.rmi;
public final class Account extends UnicastRemoteObject
implements IAccount {
private final IAccount nextAccount;
}
protected Account(IAccount next) throws IOException {
super();
nextAccount = next;
}
public Set<Integer> getCreditEntries() throws IOException {
return nextAccount.getCreditEntries();
}
public Set<Integer> getDebitEntries() throws IOException {
return nextAccount.getDebitEntries();
}
public String getDescription() throws IOException {
return nextAccount.getDescription();
}
public Integer getNumber() throws IOException {
return nextAccount.getNumber();
}
public AccountType getType() throws IOException {
return nextAccount.getType();
}
public void setDescription(String description) throws IOException {
nextAccount.setDescription(description);
}
public void setType(AccountType type) throws IOException {
nextAccount.setType(type);
}
Code 109: Class Account (RMI)
- 148 -
Transport - Remote Method Invocation (RMI)
5.3.5.3. Buchungs-Klasse
Die RMI-Buchungs-Klasse weicht auch nicht von Aufbau und Funktionalität der
RMI-Service-Klasse bzw. der RMI-Konto-Klasse ab.
package de.fhr.vs_iw.rmi;
public final class AccountingEntry extends UnicastRemoteObject
implements IAccountingEntry {
private IAccountingEntry nextEntry;
protected AccountingEntry(IAccountingEntry next) throws IOException {
super();
nextEntry = next;
}
public Double getAmount() throws IOException {
return nextEntry.getAmount();
}
public Integer getCreditAccount() throws IOException {
return nextEntry.getCreditAccount();
}
public Integer getDebitAccount() throws IOException {
return nextEntry.getDebitAccount();
}
public Integer getId() throws IOException {
return nextEntry.getId();
}
public String getText() throws IOException {
return nextEntry.getText();
}
public void setAmount(Double amount) throws IOException {
nextEntry.setAmount(amount);
}
public void setCreditAccount(Integer number)
throws IOException, AccountingException {
nextEntry.setCreditAccount(number);
}
public void setDebitAccount(Integer number)
throws IOException, AccountingException {
nextEntry.setDebitAccount(number);
}
public void setText(String text) throws IOException {
nextEntry.setText(text);
}
}
Code 110: Class AccountingEntry (RMI)
5.3.6. Aufruf der entfernten Buchhaltung
Zum Aufruf der entfernten Buchhaltung muß die beim Start des Servers registrierte Instanz
des entfernten Service-Objekts abgerufen werden.
- 149 -
Transport - Remote Method Invocation (RMI)
5.3.6.1. Anfrage an den Namensdienst
Dazu wird auf der Clientseite die lookup()-Methode der Klasse java.rmi.Naming verwendet, die ein entferntes Objekt direkt, durch Angabe ein Art URL in der Form
//host:port/name
abruft. Bei diesem Vorgang wird automatisch (seit Java 5) ein lokales Stellvertreter-Objekt
(Stub) erzeugt, das man direkt zum definierten Remote-Interface "casten" und verwenden
kann (Strichwort: dynamische Proxy-Generierung).
5.3.6.2. RMI-Proxy-Klasse
Die Proxy-Klasse muß dann auf der Clientseite nur noch diesen lookup()-Aufruf durchführen, wobei nur die Ausnahme NotBoundException abgefangen werden muß, die auftritt, falls
das gewünschte Objekt mit dem angegebenen Namen nicht registriert ist.
package de.fhr.vs_iw.rmi;
public final class RMIProxy implements IAccountingProxy {
private IAccountingService remoteService = null;
public RMIProxy() {}
public void close() {
remoteService = null;
}
public IAccountingService connect(String[] args) throws IOException {
if (args.length <= 0) {
throw new IllegalArgumentException(
"Als Argument muß der Host[:Port]"
+ " der RMI-Registry angegeben werden");
}
try {
remoteService = (IAccountingService)
Naming.lookup("//" + args[0] + "/RMIAccountingService");
} catch (NotBoundException e) {
throw new IOException(
"Entferntes IAccountingService-Objekt nicht gebunden");
}
return remoteService;
}
}
Code 111: Class RMIProxy
Da die Service-Schnittstelle der Buchhaltung als Remote-Interface verwendet wurde, kann das
Objekt, das man durch den Aufruf der lookup()-Methode erhält, direkt von der connect()Methode zurückgegeben werden, ohne daß dafür ein Adapter erforderlich wäre. Und da auch
die Konto- und die Buchungs-Schnittstelle von java.rmi.Remote abgeleitet wurden, werden
deren Methoden entfernt aufgerufen, wobei diese Objekte nicht unter einem Namen registriert
werden müssen, weil RMI automatisch dafür sorgt, daß ein Client nur den Stub eines entfernten Objekts erhält, wenn es als Rückgabewert einer Methode auftritt.
- 150 -
Transport - Remote Method Invocation (RMI)
5.3.7. Codebase
Eine der Möglichkeiten von Java besteht darin, daß Java-Klassen zur Laufzeit dynamisch von
jeder beliebigen URL (sofern nicht durch Sicherheitseinstellungen eingeschränkt) in eine Virtuelle Maschine nachgeladen werden können. Dadurch können Java-Programme ausgeführt
werden, die auf einem System nicht im Vorfeld installiert wurden [77].
Für RMI bedeutet das konkret, daß die class-Dateien der entfernten Objekte und Schnittstellen nicht lokal auf einem Rechner vorhanden sein müssen, sondern bei Aufruf der lookup()Methode automatisch nachgeladen werden können.
Abbildung 70: Nachladen von Java-Klassen
Damit das RMI-System fehlende Klassen auffinden kann, muß eine Codebase eingerichtet
werden. Dies ist normalerweise ein HTTP- oder FTP-Server, der die notwendigen classDateien zur Verfügung stellt. Es können aber auch "file:///" URLs verwendet werden,
wenn sich Client und Server auf demselben System befinden, oder aber die Dateien auf andere Weise (NFS, CIFS, SMB, etc.) zugänglich sind.
Bei der RMI-Registry wird dann durch die Eigenschaft java.rmi.server.codebase angegeben unter welcher URL die Codebase eingerichtet wurde. Beim Start der Virtuellen Maschine für den RMI-Server bzw. die RMI-Registry muß die Einstellung als Kommandozeilenparameter angegeben werden.
-Djava.rmi.server.codebase=http://webserver/classdir/
Diese Einstellung wird dann jedem Client, der nach einem entfernten Objekt anfragt, automatisch mitgeteilt.
5.3.8. Fazit
Durch die Verwendung von RMI können mit Java sehr einfach verteilte Systeme realisiert
werden. Ein Problem besteht erst dann, wenn ein verteiltes System plattform- und systemunabhängig sein soll, weil RMI auf die ausschließliche Verwendung unter Java ausgelegt und
im Kern der Sprache integriert ist.
- 151 -
Transport - CORBA
5.4. CORBA
5.4.1. Allgemeines
CORBA (Common Object Request Broker Architecture) ist ein plattform- und systemunabhängiger Standard der Object Management Group (OMG) [79] zur Entwicklung von verteilten, objektorientierten Systemen mit dem IIOP-Protokoll, für den es entsprechende Anbindungen an verschiedene Programmierumgebungen gibt [80]. Der Standard gliedert sich grob
in die folgenden kozeptionellen Bestandteile.
5.4.1.1. Object Request Broker (ORB)
Ein ORB ist allgemein ein Prozess, der als Vermittler die Kommunikation von verteilten Objekten ermöglicht. Er übernimmt die Datenübertragung zwischen den Objekten und kümmert
sich um die Konvertierung in ein einheitliches Datenformat (sog. Marshalling), was z.B. bei
unterschiedlicher Byte-Reihenfolge (Endian) auf den beteiligten Systemen notwendig ist.
Jedes Objekt, dessen Daten und Funktionen entfernt aufrufbar sein soll, muß an einen ORB
gebunden werden.
5.4.1.2. Interoperable Object Reference (IOR)
Eine IOR dient als systemübergreifende Referenz auf verteilte Objekte, die durch ORBs zur
Verfügung gestellt werden. Die IOR enthält alle notwendigen Information zum Zugriff auf ein
derartiges Objekt:




Kommunikationsprotokoll
Netzwerk-Adresse und Port (bei TCP)
Angaben zur Dekodierung (Byte-Reihenfolge, Verschlüsselung, Kompression)
Eindeutige Identität der Objekt-Instanz
.
Damit kann ein Kommunikationskanal aufgebaut werden, der den Zugriff auf die verteilten
Objekte wie auf dem lokalen System ermöglicht.
5.4.1.3. Portable Object Adapter (POA)
Der POA dient als Bindeglied zwischen ORB und Objekt-Instanzen auf dem Server. Dies
trennt die plattformunabhängige (CORBA)-Schnittstelle eines Objekts von der Implementierung in einer bestimmten Programmiersprache (sog. Servant). Ein POA instanziert Servants,
erzeugt die entsprechenden Objektreferenzen und leitet Client-Anfragen weiter.
Um die Servant-Implementierung in objektorientierter Weise zu vereinfachen, ist vorgesehen,
dass eine Servant-Klasse gleichzeitig zum eigenen POA wird, indem die Klasse die dazu notwendige Funktionalität erbt (z.B. bei Java von org.omg.PortableServer.Servant).
- 152 -
Transport - CORBA
5.4.1.4. Tie-Mechanismus
Für Programmiersprachen, bei denen die „normale“ Art der Vererbung der POA-Funktionen
nicht erwünscht bzw. nicht möglich ist (z.B. bei Java wegen fehlender Mehrfachvererbung),
wurde der Tie-Mechanismus vorgesehen.
Dabei wird zu jedem Servant eine eigene POA-Tie-Klasse generiert (siehe 5.4.2.4), welche
die Servant-Implementierung kapselt und „nach außen“ die selbe anwendungsspezifische
Schnittstelle wie der Servant anbietet. Dadurch kann diese Zusatzklasse völlig transparent in
die eigene Anwendung eingebunden werden.
5.4.1.5. CORBA-Dienste
Zusätzlich zum Verfahren für den Aufruf entfernter Objekte definiert CORBA noch einige
Dienste mit einer definierten IDL-Schnittstelle (siehe 5.4.2) die bei der Verwaltung der Objekte und dem Aufbau eines verteilten Systems hilfreich sind [81].















Naming Service
Trading Service
Event Service
Lifecycle Service
Relationship Service
Externalization Service
Persistent Object Service
Concurrency Control Service
Transaction Service
Property Service
Licensing Service
Object Collection Service
Query Service
Time Service
Security Service
Der wichtigste Dienst ist der Naming Service, der für benannte und registriete Objekte einer
Anwendung die notwendigen Objektreferenzen (IOR) liefert.
5.4.1.6. Implementierungen
Da CORBA nur ein allgemeiner Standard ist, benötigt man zur Software-Entwicklung eine
konkrete Implementierung für die verwendete Programmiersprache und Zielplattform.






TAO
MICO
JacORB
omniORB
Orbacus
Bestandteil von Java
http://www.cs.wustl.edu/~schmidt/TAO.html
http://www.mico.org/
http://www.jacorb.org/
http://omniorb.sourceforge.net/
http://web.progress.com/en/orbacus/index.html
http://java.sun.com/j2ee/corba/index.html
Jede Implementierung enthält prinzipiell den statischen Teil von CORBA (ORB, Dienste), der
konfiguriert und auf einem Server ausgeführt wird.
Dazu kommt ggf. ein programmiersprachenabhängiger „Generator“ (IDL-Compiler), der die
spezifischen Schnittstellen und Klassen (sog. Artefakte: POA, Tie) zur Verbindung von
CORBA mit der eigenen Anwendung erzeugt.
- 153 -
Transport - CORBA
5.4.2. Interface Definition Language
CORBA basiert auf einer eigenen Beschreibungssprache, die von Plattformen und Programmiersprachen unabhängig ist, um einen Dienst, der entfernt aufrufbar sein soll, zu beschreiben. In dieser Beschreibung werden im wesentlichen Schnittstellen definiert, deren Methodenaufrufe an ein entferntes System übertragen werden. Zu den Schnittstellen werden weitere
notwendige Bestandteile des Systems definiert (Ausnahmen, Enumerationen, Arrays), die
dann zusammen mit den Basisdatentypen von CORBA verwendet werden können.
5.4.2.1. Buchhaltungs-IDL
Für die Buchhaltungsanwendung bedeutet dies, daß alle Funktionalitäten der Buchhaltungsschnittstellen nochmals als CORBA-IDL definiert werden müssen.
module corba {
typedef sequence<long> CorbaIntegerSet;
exception CorbaAccountingException {
string message;
};
exception CorbaIOException {
string message;
};
enum CorbaAccountType {
ACTIVE, AMOUNT, EXPENSE, PASSIVE, PROCEEDS
};
interface CorbaAccount {
CorbaIntegerSet getCreditEntries()
raises (CorbaIOException);
CorbaIntegerSet getDebitEntries()
raises (CorbaIOException);
string getDescription()
raises (CorbaIOException);
long getNumber()
raises (CorbaIOException);
CorbaAccountType getType()
raises (CorbaIOException);
void setDescription(in string description)
raises (CorbaIOException);
void setType(in CorbaAccountType type)
raises (CorbaIOException);
};
- 154 -
Transport - CORBA
interface CorbaAccountingEntry {
double getAmount()
raises (CorbaIOException);
long getCreditAccount()
raises (CorbaIOException);
long getDebitAccount()
raises (CorbaIOException);
long getId()
raises (CorbaIOException);
string getText()
raises (CorbaIOException);
void setAmount(in double amount)
raises (CorbaIOException);
void setCreditAccount(in long creditAccount)
raises (CorbaAccountingException, CorbaIOException);
void setDebitAccount(in long debitAccount)
raises (CorbaAccountingException, CorbaIOException);
void setText(in string text)
raises (CorbaIOException);
};
interface CorbaAccountingService {
double calculateBalance(in long number)
raises (CorbaAccountingException, CorbaIOException);
void createAccount(in long number, in CorbaAccountType type,
in string description)
raises (CorbaAccountingException, CorbaIOException);
long createAccountingEntry(in long debitAccount, in long creditAccount,
in double amount, in string text)
raises (CorbaAccountingException, CorbaIOException);
CorbaAccount getAccountByNumber(in long number)
raises (CorbaAccountingException, CorbaIOException);
CorbaIntegerSet getAccountingEntries()
raises (CorbaIOException);
CorbaAccountingEntry getAccountingEntryById(in long id)
raises (CorbaAccountingException, CorbaIOException);
CorbaIntegerSet getAccounts()
raises (CorbaIOException);
void removeAccount(in long number)
raises (CorbaIOException, CorbaAccountingException);
void removeAccountingEntry(in long id)
raises (CorbaIOException, CorbaAccountingException);
};
};
Resource 13: CORBA Accounting.idl
Von der Logik her entspricht der Aufbau der IDL vollkommen den Schnittstellen und Klassen
aus dem Buchhaltungsmodel. Die Kontotypen werden als Enumeration definiert und die beiden bekannten Typen für Ausnahmen eingeführt.
Anstatt eines Set zur Auflistung der Kontonummern bzw. Buchungs-IDs wird in der IDL ein
benutzerdefinierter Datentyp als sequence angegeben. Die sequence definiert hier als Inhaltstyp den Basisdatentyp long, könnte aber auch entfernte Objekte zurückgeben.
Alle Definitionen werden von der globalen Angabe module umschlossen, die zur Abgrenzung
von Namensräumen dient. Für den Aufbau von hierarchischen Namensräumen können die
module Angaben geschachtelt werden.
- 155 -
Transport - CORBA
5.4.2.2. Zuordnung der IDL-Elemente zu Java-Typen
Für die meisten Programmiersprachen gibt es eine Zuordnung von Datentypen zu den Elementen der IDL. Hier werden die definierten Schnittstellen und die CORBA-Basis-Typen
entsprechend auf Java-Klassen und Java-Typen abgebildet (IDL-to-Java-Mapping).
Einfache Datentypen
boolean
char
wchar
octet
string
wstring
short
unsigned short
boolean
char
char
byte
java.lang.String
java.lang.String
short
short
long
unsigned long
long long
unsigned long long
float
double
integer
integer
long
long
float
double
long double
unmapped
Tabelle 13: Zuordnung der Basisdatentypen
Die einfachen Datentypen der IDL werden in Java durch die Wrapper-Klassen der primitiven
Java-Datentypen behandelt. Für andere Angaben in der IDL sind entsprechend komplexere
Java-Konstrukte notwendig.
Komplexe IDL-Angaben
module
Java-Package
Schnittstelle (org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity)
interface
Stub-Klasse (org.omg.CORBA.portable.ObjectImpl)
enum
Klasse (org.omg.CORBA.portable.IDLEntity)
exception Klasse, Ausnahme (org.omg.CORBA.UserException)
sequence
Array vom entsprechenden Inhaltstyp
Tabelle 14: Zuordnung der komplexen IDL-Angaben
Die Java-Klassen für eine CORBA-basierte Anwendung werden je nach Typ von den entsprechenden CORBA-Klassen abgeleitet bzw. müssen bestimmte CORBA-spezifische Schnittstellen implementieren.
5.4.2.3. Java-Artefakte generieren
Dieses Java-to-IDL-Mapping wird nicht manuell durchgeführt. Dafür gibt es den IDLCompiler (idlj.exe) aus dem Java-SDK, der aus einer IDL-Datei alle benötigten JavaArtefakte (Klassen und Schnittstellen) erzeugt.
Für den IDL-Compiler werden folgende Kommandozeilenargumente angegeben, die den Vorgang steuern.

-pkgPrefix


-td
-fallTie
zusätzliches Präfix für eine module Angabe, zur Einordnung der generierten Klassen in die gewünschte Java-Package-Hierarchie
Target Directory: das Zielverzeichnis für die erzeugten Klassen
gibt an, daß alle möglichen Artefakte erzeugt werden sollen, auch die
für die TIE-Anbindung
- 156 -
Transport - CORBA
<?xml version="1.0" standalone="yes"?>
<project basedir=".." default="idl-generator">
<property file="${basedir}/ant/build.properties" />
<target name="idl-generator">
<exec executable="${jdk.home}/bin/idlj.exe">
<arg line="-pkgPrefix corba de.fhr.vs_iw" />
<arg line="-td ${basedir}/generated" />
<arg line="-fallTie" />
<arg value="${basedir}/resources/corba/Accounting.idl" />
</exec>
</target>
</project>
Resource 14: Ant-Build-Datei corba-idlj.xml
Der IDL-Compiler kann aus Eclipse heraus durch diese Ant-Build-Datei aufgerufen werden,
sofern auch das bin-Verzeichnis des angegebenen JDK in den PATH aufgenommen wurde.
5.4.2.4. Erzeugte Klassen und Schnittstellen
Durch den IDL-Compiler wird für die Buchhaltung eine größere Anzahl an Java-Klassen und
Schnittstellen erzeugt.
Klasse
_CorbaAccountingEntryStub
_CorbaAccountingServiceStub
_CorbaAccountStub
CorbaAccount
CorbaAccountHelper
CorbaAccountHolder
CorbaAccountingEntry
CorbaAccountingEntryHelper
CorbaAccountingEntryHolder
CorbaAccountingEntryOperations
CorbaAccountingEntryPOA
CorbaAccountingEntryPOATie
CorbaAccountingException
CorbaAccountingExceptionHelper
CorbaAccountingExceptionHolder
CorbaAccountingService
CorbaAccountingServiceHelper
CorbaAccountingServiceHolder
CorbaAccountingServiceOperations
CorbaAccountingServicePOA
CorbaAccountingServicePOATie
CorbaAccountOperations
CorbaAccountPOA
CorbaAccountPOATie
CorbaAccountType
CorbaAccountTypeHelper
CorbaAccountTypeHolder
CorbaIntegerSetHelper
Java-Typ
Klasse
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Klasse
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Schnittstelle
Klasse
Klasse
Klasse
Klasse
Klasse
Klasse
- 157 -
Beschreibung
Lokaler Stellvertreter
Lokaler Stellvertreter
Lokaler Stellvertreter
Schnittstelle mit Mehrfachvererbung
Werkzeug-Klasse
Behälter für Objekte des Typs
Schnittstelle mit Mehrfachvererbung
Werkzeug-Klasse
Behälter für Objekte des Typs
Schnittstelle mit Geschäftsmethoden
Superklasse für normale Anbindung
Superklasse für TIE-Anbindung
Klasse für definierte Ausnahme
Werkzeug-Klasse
Behälter für Objekte des Typs
Schnittstelle mit Mehrfachvererbung
Werkzeug-Klasse
Behälter für Objekte
Schnittstelle mit Geschäftsmethoden
Superklasse für normale Anbindung
Superklasse für TIE-Anbindung
Schnittstelle mit Geschäftsmethoden
Superklasse für normale Anbindung
Superklasse für TIE-Anbindung
Enumeration
Werkzeug-Klasse
Behälter für Objekte des Typs
Werkzeug-Klasse
Transport - CORBA
CorbaIntegerSetHolder
CorbaIOException
CorbaIOExceptionHelper
CorbaIOExceptionHolder
Klasse
Klasse
Klasse
Klasse
Behälter für Objekte des Typs
Klasse für definierte Ausnahme
Werkzeug-Klasse
Behälter für Objekte des Typs
Tabelle 15: Erzeugte CORBA-Artefakte
Für jedes in der IDL definierte Artefakt werden mehrere Java-Dateien erzeugt, die jeweils
verschiedene Funktionalitäten übernehmen und deren Name mit einem entsprechenden Suffix
versehen wird.
Suffix
Beschreibung
lokale Stellvertreterklasse für entfernte Objekte, die den notwendigen Code für
Stub
CORBA-Kommunikation der Anwendung implementiert
Hilfsklasse mit den notwendigen Methoden, um aus einer Referenz auf ein
Helper
entferntes Objekt ein lokales Stellvertreterobjekt zu erstellen
Behälter-Klasse für ein Objekt des entsprechenden Typs, wird benötigt, da Java
Holder
keine out-Parameter bei Methoden unterstützt, CORBA und andere Programmiersprachen aber schon
Schnittstelle, die nur die definierten Geschäftsmethoden aus der IDL-Datei
Operations
enthält
Portable Object Adapter: Basisklasse zur Ableitung einer eigenen Klasse, die
POA
die Funktionalität von entfernten Objekten im Sinne der Geschäftslogik implementiert, und auf einem Server als Servant instanziert wird
Adapter für die TIE-Anbindung einer Klasse, bei der die eigene Klasse nicht
POATie
davon abgeleitet wird, sondern nur die Schnittstelle mit den Methoden implementiert, damit die Vererbungsstruktur der eigenen Anwendung erhalten bleibt.
Schnittstellen mit Mehrfachvererbung fassen die Operations-Schnittstelle mit
kein
den CORBA-Kommunikationsschnittstellen (Object, IDLEntity) zusammen,
und werden dann von einer Anwendung für die konkreten Aufrufe verwendet
Resource 15: Bedeutung der CORBA-Suffixe
Durch diese Kombination aus anwendungsspezifisch erzeugten Dateien und den im JDK mitgelieferten CORBA-Klassen wird die Kommunikationsschicht der Anwendung realisiert. Was
fehlt ist nun die Implementierung der Geschäftslogik auf Client- und Server-Seite.
5.4.3. Verwendung der erzeugten Klassen auf der Serverseite
Auf der Serverseite werden die generierten POA- bzw. POATie-Klassen verwendet, um die
Geschäftslogik der Buchhaltung zu Implementieren, bzw. um wieder auf die Klassen eines
Moduls aus der Datenhaltung zuzugreifen, welches die Geschäftslogik bereits implementiert.
5.4.3.1. CORBA-Server
Die Klasse CorbaServer enthält zum einen die main()-Methode zum Start des Server und ist
zum anderen von CorbaAccountingServicePOA abgleitet, damit eine Instanz der Klasse als
Servant für den entfernten Aufruf der Methoden der Service-Schnittstelle dienen kann.
- 158 -
Transport - CORBA
package de.fhr.vs_iw.corba;
public final class CorbaServer extends CorbaAccountingServicePOA {
private
= new
private
= new
private
Map<Integer, CorbaAccountingEntry> accountingEntries
HashMap<Integer, CorbaAccountingEntry>();
Map<Integer, CorbaAccount> accounts
HashMap<Integer, CorbaAccount>();
final IAccountingService next;
private CorbaServer(final IAccountingService next) {
this.next = next;
}
public void createAccount(int number, CorbaAccountType type,
String description);
public int createAccountingEntry(int debitAccount, int creditAccount,
double amount, String text);
public CorbaAccount getAccountByNumber(int number)
throws CorbaAccountingException, CorbaIOException {
try {
CorbaAccount account = accounts.get(number);
if (account == null) {
IAccount nextaccount = next.getAccountByNumber(number);
Servant servant = new CorbaServerAccount(nextaccount);
rootPoa.activate_object(servant);
org.omg.CORBA.Object ref = rootPoa.servant_to_reference(servant);
account = CorbaAccountHelper.narrow(ref);
accounts.put(number, account);
}
return account;
} catch (IOException e) {
throw new CorbaIOException(e.getMessage());
} catch (AccountingException e) {
throw new CorbaAccountingException(e.getMessage());
} catch (ServantAlreadyActive e) {
throw new CorbaIOException(e.getMessage());
} catch (WrongPolicy e) {
throw new CorbaIOException(e.getMessage());
} catch (ServantNotActive e) {
throw new CorbaIOException(e.getMessage());
}
}
public
public
public
public
public
public
}
int[] getAccountingEntries();
double calculateBalance(int number);
CorbaAccountingEntry getAccountingEntryById(int id);
int[] getAccounts();
void removeAccount(int number);
void removeAccountingEntry(int id);
...
Code 112: Class CorbaServer (Methoden)
Die Klasse fungiert als Adapter von CORBA zu einer Service-Klasse der Buchhaltung. Die
Methodenaufrufe werden direkt umgesetzt, und in erster Linie müssen nur die Ausnahmen aus
dem Buchhaltungsmodell in ihre korrespondierenden CORBA-Ausnahmen umgeformt werden. Lediglich bei der Rückgabe der Konten bzw. Buchungsobjekte (CorbaAccount bzw.
- 159 -
Transport - CORBA
CorbaAccountingEntry) ist mehr Aufwand erforderlich. Das Objekt muß als Servant instanziert mit activate_object() zuerst aktiviert werden. Anschließend kann man sich durch
die Methode servant_to_reference() die Objekt-Referenz des gerade aktivierten Objekts
holen. Aus dieser Referenz wird durch die Helfer-Klasse mit narrow() ein Stub-Objekt er-
zeugt, das dann an den Client geschickt wird. Damit es nicht zu Fehlern kommt, weil jedes
Objekt nur einmal aktiviert werden darf, werden alle Servant-Objekte in HashMaps verwaltet.
In der main-Methode wird der CorbaServer dann einmal instanziert und stellt die Funktionalitäten als entferntes Objekt zur Verfügung.
package de.fhr.vs_iw.corba;
public final class CorbaServer extends CorbaAccountingServicePOA {
...
private static final String CORBA_NAME = "CorbaAccountingService";
private static POA rootPoa = null;
public static void main(String[] args) {
if (args.length < 1) {
System.err.println(
"Host, Port und Proxy-Klasse müssen angegeben werden");
return;
}
try {
// process command line args, first is host, second is port
String[] params = { "-ORBInitialHost", args[0],
"-ORBInitialPort", args[1] };
IAccountingProxy proxy = Utilities.loadProxy(args[2]);
IAccountingService next
= proxy.connect(Utilities.shiftArgs(args, 2));
// initialize ORB and object adapter
ORB orb = ORB.init(params, null);
rootPoa
= POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootPoa.the_POAManager().activate();
// initantiate the server implementation
Servant servant = new CorbaServer(next);
org.omg.CORBA.Object ref = rootPoa.servant_to_reference(servant);
// get root naming context
org.omg.CORBA.Object namingref
= orb.resolve_initial_references("NameService");
NamingContextExt ncref = NamingContextExtHelper.narrow(namingref);
// bind service object to name
NameComponent[] path = ncref.to_name(CORBA_NAME);
ncref.rebind(path, ref);
}
}
// let corba do its job
orb.run();
} catch (final Exception e) {
e.printStackTrace();
}
Code 113: Class CorbaServer (main)
- 160 -
Transport - CORBA
Doch vor der Instanzierung eines Servant-Objekts werden die CORBA-Klassen initialisiert.
Eine ORB-Instanz (Object Request Broker) übernimmt bei CORBA die Kommunikation mit
anderen Systemen, bzw. mit anderen ORBs auf diesen Systemen, und wird mit den Parametern -ORBInitialHost und -ORInitialPort für den Zugriff auf den ORB-Daemon instanziert, die in einem String-Array angegeben werden.
Zusätzlich ist noch eine Instanz des RootPOA notwendig der die Verwaltung und Aktivierung
aller über CORBA angebotenen Objekte übernimmt, und selbst wie ein entferntes Objekt abgerufen wird. Der CorbaServer wird als Servant instanziert und über die Methode narrow()
erhält man eine Referenz darauf, die für entfernte Aufrufe verwendet werden kann.
Diese Referenz wird dann an den Namensdienst gebunden (rebind()), damit sie über den
angegebenen Namen (NameComponent[]) leicht abrufbar ist.
5.4.3.2. Konto-Klasse
Die Klasse CorbaServerAccount dient nur als Adapter für IAccount-Objekte der Datenhaltungsschicht, gibt die Methodenaufrufe weiter und formt auftretende Ausnahmen um.
package de.fhr.vs_iw.corba;
public final class CorbaServerAccount extends CorbaAccountPOA {
private final IAccount next;
protected CorbaServerAccount(IAccount next) {
this.next = next;
}
public int[] getDebitEntries() throws CorbaIOException {
try {
Set<Integer> set = next.getDebitEntries();
int[] ar = new int[set.size()];
int i = 0;
for (Integer id : set) {
ar[i++] = id;
}
return ar;
} catch (IOException e) {
throw new CorbaIOException(e.getMessage());
}
}
public void setType(CorbaAccountType type) throws CorbaIOException {
try {
next.setType(AccountType.getAccountTypeById(type.value()));
} catch (IOException e) {
throw new CorbaIOException(e.getMessage());
}
}
}
...
Code 114: Class CorbaServerAccount
Wie auch in der Service-Klasse müssen hier die Sets zu Arrays umgewandelt werden.
- 161 -
Transport - CORBA
5.4.3.3. Buchungs-Klasse
Auch die Buchungs-Klasse dient nur als Adapter für die Datenhaltungsschicht und jede Instanz kapselt ein IAccountingEntry-Objekt.
package de.fhr.vs_iw.corba;
public final class CorbaServerEntry extends CorbaAccountingEntryPOA {
private final IAccountingEntry next;
protected CorbaServerEntry(IAccountingEntry next) {
this.next = next;
}
public double getAmount() throws CorbaIOException {
try {
return next.getAmount();
} catch (IOException e) {
throw new CorbaIOException(e.getMessage());
}
}
public void setCreditAccount(int creditAccount)
throws CorbaAccountingException, CorbaIOException {
try {
next.setCreditAccount(creditAccount);
} catch (IOException e) {
throw new CorbaIOException(e.getMessage());
} catch (AccountingException e) {
throw new CorbaAccountingException(e.getMessage());
}
}
}
...
Code 115: Class CorbaServerEntry
Die Methodenaufrufe werden direkt weitergegeben und die Ausnahmen entsprechend zu den
in der IDL definierten CORBA-Ausnahmen adaptiert.
5.4.3.4. Der ORB-Daemon
Die Kommunikation und alle entfernten Aufrufe werden zwar von ORB-Instanzen erledigt,
damit sich diese aber untereinander auffinden können ist eine zentrale Anlaufstelle notwendig, der ORB-Daemon. Dieser stellt persistente Referenzen auf die am verteilten System beteiligten Dienste und Objekte bereit. Im Java-Development-Kit ist eine ORB-DaemonImplementierung vorhanden, die direkt verwendet werden kann.
bin/orbd.exe -port 9050
Beim Start kann ein beliebiger Port angegeben werden, auf dem der ORB-Daemon Anfragen
entgegennehmen soll, sonst wird der Standardport 3895 verwendet.
- 162 -
Transport - CORBA
5.4.4. Verwendung der erzeugten Klassen auf der Clientseite
Auf der Clientseite wird im Prinzip dieselbe Vorgehensweise verfolgt, wie auf der Serverseite. Die vom IDL-Compiler generierten CORBA-Client-Schnittstellen (siehe 5.4.2.4,
CorbaAccountingService, CorbaAccount, CorbaAccountingEntry) werden mit den zugehörigen Stub-Klassen für die CORBA-Kommunikation verwendet und durch Adapter-Klassen
wieder an die Schnittstellen des Buchhaltungsmodells angebunden.
5.4.4.1. Service-Klasse
Die Service-Klasse implementiert die Service-Schnittstelle aus dem Buchhaltungsmodell
(IAccountingService) und kapselt ein CorbaAccountingService-Objekt.
package de.fhr.vs_iw.corba;
public final class AccountingService implements IAccountingService {
private final CorbaAccountingService corba;
protected AccountingService(CorbaAccountingService corba) {
this.corba = corba;
}
private
= new
private
= new
Map<Integer, AccountingEntry> accountingEntries
HashMap<Integer, AccountingEntry>();
Map<Integer, Account> accounts
HashMap<Integer, Account>();
public Double calculateBalance(Integer number)
throws IOException, AccountingException {
try {
return corba.calculateBalance(number);
} catch (CorbaAccountingException e) {
throw new AccountingException(e.message, e);
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
public IAccount getAccountByNumber(Integer number)
throws IOException, AccountingException {
try {
Account account = accounts.get(number);
if (account == null) {
account = new Account(corba.getAccountByNumber(number));
accounts.put(number, account);
}
return account;
} catch (CorbaAccountingException e) {
throw new AccountingException(e.message, e);
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
- 163 -
Transport - CORBA
public Set<Integer> getAccounts() throws IOException {
try {
int[] ar = corba.getAccounts();
HashSet<Integer> set = new HashSet<Integer>();
for (int i : ar) {
set.add(i);
}
return set;
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
...
}
Code 116: Class AccountingService (CORBA)
In den Methoden getAccountByNumber() bzw. getAccountingEntryById() werden entsprechende Adapter-Objekte für Konten bzw. Buchungen zurückgegeben, die über CORBA
als entfernte Objekte aufgerufen werden. Diese Adapter-Objekte werden wie auf dem Server
in HashMaps verwaltet.
5.4.4.2. Konto-Klasse
Die Konto-Klasse implementiert entsprechend die IAccount-Schnittstelle und gibt alle Methodenaufrufe an ein CorbaAccount-Objekt weiter. Die CORBA-Ausnahmen werden entsprechend zu Ausnahmen aus dem Buchhaltungsmodell zurückgewandelt, die Elemente der
übertragenen Arrays wieder in Sets aufgenommen und auch der Kontotyp entsprechend dem
generierten IDL-Kontotyp (CorbaAccountType) übertragen.
package de.fhr.vs_iw.corba;
public final class Account implements IAccount {
private final CorbaAccount corba;
protected Account(CorbaAccount corba) {
this.corba = corba;
}
public Set<Integer> getCreditEntries() throws IOException {
try {
int[] ar = corba.getCreditEntries();
HashSet<Integer> set = new HashSet<Integer>();
for (int i : ar) {
set.add(i);
}
return set;
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
- 164 -
Transport - CORBA
public void setType(AccountType type) throws IOException {
try {
corba.setType(CorbaAccountType.from_int(type.getId()));
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
}
...
Code 117: Class Account (CORBA)
5.4.4.3. Buchungs-Klasse
Wie auch die Konto-Klasse dient auch die Buchungs-Klasse als Adapter auf die entsprechende Buchhaltungsschnittstelle (IAccountingEntry), und zwar für ein entferntes
CorbaAccountingEntry-Objekt.
package de.fhr.vs_iw.corba;
public final class AccountingEntry implements IAccountingEntry {
private final CorbaAccountingEntry corba;
protected AccountingEntry(CorbaAccountingEntry corba) {
this.corba = corba;
}
public Double getAmount() throws IOException {
try {
return corba.getAmount();
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
public void setDebitAccount(Integer debitAccount)
throws IOException, AccountingException {
try {
corba.setDebitAccount(debitAccount);
} catch (CorbaAccountingException e) {
throw new AccountingException(e.message, e);
} catch (CorbaIOException e) {
IOException ioe = new IOException(e.message);
ioe.initCause(e);
throw ioe;
}
}
}
...
Code 118: Class AccountingEntry (CORBA)
- 165 -
Transport - CORBA
5.4.4.4. Proxy-Klasse
Die Proxy-Klasse übernimmt den Aufruf der entfernten CORBA-Service-Servant. Als Parameter wird der Host und der Port des ORB-Daemon benötigt, um die Kommunikation zu initialisieren.
package de.fhr.vs_iw.corba;
public final class CorbaProxy implements IAccountingProxy {
public CorbaProxy() {}
public void close() {}
public IAccountingService connect(String[] args) throws IOException {
if (args.length < 2) {
throw new IllegalArgumentException(
"Hostname und Port müssen angegeben werden");
}
try {
// process command line args, first is host, second is port
String[] params = { "-ORBInitialHost", args[0],
"-ORBInitialPort", args[1] };
// initialize the ORB
ORB orb = ORB.init(params, null);
// get root naming context
org.omg.CORBA.Object namingref
= orb.resolve_initial_references("NameService");
NamingContextExt ncref = NamingContextExtHelper.narrow(namingref);
// get the corba bank object by its name
NameComponent[] path = ncref.to_name(CORBA_NAME);
org.omg.CORBA.Object ref = ncref.resolve(path);
CorbaAccountingService service
= CorbaAccountingServiceHelper.narrow(ref);
return new AccountingService(service);
} catch (Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
private static final String CORBA_NAME = "CorbaAccountingService";
}
Code 119: Class CorbaProxy
Zuerst wird wieder ein ORB instanziert, der eine Verbindung zum ORB-Daemon und dann
auch zum ORB auf dem CORBA-Server herstellt. Damit wird eine Referenz zum Namensdienst geholt, wodurch wiederum die Referenz auf den im Server instanzierten Servant durch
den registrierten Namen abgerufen kann. Durch den Aufruf von narrow() der Helfer-Klasse
erhält man ein Stub-Objekt für die Referenz auf das entfernte Objekt, mit dem man die entfernten Methoden aufrufen kann. Das CorbaAccountingService-Objekt wird dann von einem AccountingService-Objekt gekapselt und von der connect()-Methode zurückgegeben.
- 166 -
Transport - CORBA
5.4.5. JACORB
Der JacORB [78] ist eine freie JavaImplementierung des CORBA-Standards als
Alternative zur CORBA-Implementierung von SUN. Er arbeitet erwiesenermaßen gut mit den
CORBA-Implementierungen für andere Programmiersprachen (z.B. C++) zusammen, was die
Interoperabilität, die für CORBA eigentlich grundsätzlich gefordert wird, garantiert.
5.4.5.1. Features
Die Implementierung von JacORB umfaßt im wesentlichen folgende Elemente:




















multithreaded ORB mit hoher Performance
100% Java, kompatibel mit JDK 1.1 bis JDK 1.6
IDL-Compiler für IDL-Mapping 2.3
natives IIOP, GIOP 1.2 und bidirektionales GIOP
POA (Portable Object Adapter) mit POA-Monitor-GUI
AMI (Asynchronous Method Invocation)
Unterstützung für Quality of Service (QoS)
ETF (Extensible Transport Framework)
Dynamic Invocation Interface (DII)
Dynamic Skeleton Interface (DSI)
Portable Interceptors
OMG Interoperable Naming Service mit GUI
verbessertes IIOP über SSL mit KeyStoreManager
OMG Notification-Service und Event-Service
Transaktions-Service
Trading-Service
CORBA 2.3 Code-Unterstützung
OMG Data Distribution Service (DDS)
Interface Repository mit GUI
Implementaion Repository mit GUI
5.4.5.2. Installation
Zur Verwendung des JacORB muß das entsprechende Paket für Java heruntergeladen und
entpackt werden.
http://www.jacorb.org/download.html
Dem Paket liegen Installationsanweisungen bei, um die Quellen mit Ant vollständig neu zu
kompilieren, und dabei die Pfade passend zum Installationsort einzurichten. Dies ist jedoch
nicht unbedingt nötig, da man die Pfade zu den nötigen Bibliotheken auch manuell bei Start
einer JVM angeben kann. Die Bibliotheken müssen hier auch nicht zum CLASSPATH von Eclipse hinzugefügt werden.
- 167 -
Transport - CORBA
5.4.5.3. Verwendung
Die JacORB-Klassen werden nicht direkt zur Programmierung verwendet. Das oben angegebene CORBA-Programm der Buchhaltung wird exakt so verwendet und durch Kommandozeilenparameter wird lediglich die Verwendung der ORB-Klasse von JacORB eingestellt.
Wegen der Angabe der Pfade und Parameter ist es günstig diese einmalig für eine JacORBInstallation in eine Batch-Datei (jacorbsettings.cmd) zu schreiben und diese beim Start
von JacORB-basierten Programmen zu verwenden.
@echo off
set LIB=D:\classpath\JacORB_2.3.0_beta2\lib
set RT=c:\Program Files\Java\jdk1.6.0\jre\lib\rt.jar
set BOOTCLASSPATH=%LIB%\jacorb.jar;%LIB%\logkit-1.2.jar;
%LIB%\avalon-framework-4.1.5.jar;%RT%;%CLASSPATH%
set JACOPTIONS=-Djacorb.home=D:\classpath\JacORB_2.3.0_beta2
-Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB
-Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton
Resource 16: jacorbsettings.cmd
Hier werden die Bibliotheken von JacORB und die darin enthaltenen Klassen zur Verwendung bei CORBA-Anwendungen Ausgehend von diesen Einstellungen wird dann jeweils eine
JVM für die entsprechenden Java-Programme gestartet.
Im Gegensatz zur mitgelieferten CORBA-Implementierung muß bei JacORB nicht der orbd,
sondern der NameServer separat gestartet werden, und zwar durch folgende Batch-Datei.
@echo off
call jacorbsettings.cmd
java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS%
org.jacorb.naming.NameServer -Djacorb.naming.ior_filename=d:/NS_Ref
Resource 17: jacorbnaming.cmd
Die zuvor definierten Einstellungen werden beim Start einer JVM angegeben und damit die
Klasse des NameServers aufgerufen. Als zusätzlicher Parameter wird eine Datei angegeben,
in der der NameServer seine IOR zur späteren Verwendung hinterlegt.
Die Klasse CorbaServer wird auf dieselbe Weise mit den JacORB-Einstellungen aus einer
Batch-Datei (jacorbserver.cmd) heraus gestartet.
@echo off
call jacorbsettings.cmd
java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS%
de.fhr.vs_iw.corba.CorbaServer localhost 9050
de.fhr.vs_iw.local.LocalProxy
Resource 18: jacorbserver.cmd
Die von der Implementierung der CorbaServer-Klasse her muß zwar die geforderte Angabe
von Host und Port erfolgen, diese wird aber von JacORB nicht verwendet. Der JacORB lädt
über seine Konfiguration die IOR des NameServer für alle weiteren Operationen.
- 168 -
Transport - CORBA
Für den Client gilt natürlich selbiges, und es muß nicht die Implementierung angepaßt, sondern nur die Startparameter verwendet werden.
@echo off
call jacorbsettings.cmd
java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS%
de.fhr.vs_iw.Client de.fhr.vs_iw.corba.CorbaProxy localhost 9050
Resource 19: jacorbclient.cmd
Auch hier ist die Angabe von Host und Port nur wegen der originalen CORBAImplementierung notwendig, da sonst eine Ausnahme wegen fehlender Parameter geworfen
wird.
5.4.5.4. Einstellungen
Jede JacORB-Instanz lädt die globale Konfigurationsdatei orb.properties aus dem
CLASSPATH der Anwendung. Darin werden alle relevanten Einstellungen vorgenommen. Eine
Beispieldatei, in der alle möglichen Einstellungen erklärt werden, ist im JacORB-Paket enthalten. Die wichtigsten Einstellungen die darin aber angepaßt werden sollten sind die folgenden:
jacorb.config.dir = D:/eclipse/workspace/VS_IW_Complete/resources/jacorb
ORBInitRef.NameService = file:/d:/NS_Ref
Das ist zum einen ein Verzeichnis, in dem weitere spezifische Konfigurationsdateien für einzelne ORB-Instanzen abgelegt werden können. Zum anderen ist es die Angabe der Datei mit
der IOR des NameServers, womit eine ORB-Instanz erst in der Lage ist, ihren Dienst zu verrichten. Hier kann eine beliebige URL zu der Datei angegeben werden, falls der NameServer
auf einem anderen Host gestartet wurde und die Datei, z.B. über HTTP, bereitgestellt wird.
5.4.6. Fazit
Wenn in der Praxis ein verteiltes System realisiert werden soll, muß man sich auf jeden Fall
überlegen ob die Verwendung von CORBA den Vorgang nicht erheblich vereinfachen würde.
In diesem Beispiel hier ist die Angelegenheit allerdings relativ umständlich, weil auf beiden
Seiten der CORBA-Datenübertragung die CORBA-Klassen durch Adapter an die vorgegebenen Buchhaltungsschnittstellen angepaßt werden müssen.
Wenn aber eine Anwendung keine eigenen Kommunikationsschnittstellen vorgibt und direkt
auf die generierten CORBA-Schnittstellen aufsetzen kann, ist die Verwendung von CORBA
auf jeden Fall ein möglicher Ansatz.
- 169 -
Java-Enterprise - Übersicht
6. Java-Enterprise
6.1. Übersicht
In der Spezifikation der J2EE (Java 2 Enterprise Edition) [45] werden Softwarekomponenten
und Dienste definiert, die primär in der Programmiersprache Java erstellt werden. Die Spezifikation dient dazu, einen allgemein akzeptierten Rahmen zur Verfügung zu stellen, um auf
dessen Basis aus modularen Komponenten verteilte, mehrschichtige Anwendungen entwickeln zu können. Klar definierte Schnittstellen zwischen den Komponenten und Schichten
sollen dafür sorgen, daß Softwarekomponenten unterschiedlicher Hersteller interoperabel
sind, wenn sie sich an die Spezifikation halten, und daß die verteilte Anwendung gut skalierbar ist [08].
Abbildung 71: Java 2 Enterprise Komponenten-Model
- 170 -
Java-Enterprise - Übersicht
Anwendungen, die sich auf dieses Model stützen und die angegebenen Komponenten und
Dienste verwenden, sind theoretisch auf jedem zertifizierten J2EE-Applikations-Server ablauffähig.
6.1.1. Komponenten
Im Groben gliedert sich der Aufbau einer Anwendung und damit des Applikations-Servers in
verschiedenen logische Komponenten, die als Container bezeichnet werden und prinzipiell in
jedem Applikations-Server vorhanden sein müssen [08].
6.1.1.1. EJB-Container
Der Enterprise-JavaBeans-Container stellt die Laufzeitumgebung für Enterprise JavaBeans
(siehe 6.3) bereit, die die Geschäftslogik einer Anwendung implementieren und den Zugriff
auf die Daten der Anwendung gestatten. Er arbeitet als Vermittlungsschicht (Middleware)
zwischen den Clients einer Anwendung und den Daten in einer Datenbank [46].
6.1.1.2. Application-Client-Container
Als Application-Client-Container (AAC) wird ein Satz notwendiger Bibliotheken bezeichnet,
die eine Java-Anwendung benötigt, um aus einer separat laufenden Virtuellen Maschine heraus auf die EJBs eines Applikations-Servers, der sich auch auf einem anderen Host befinden
kann, zuzugreifen [47].
6.1.1.3. Web-Container
Der Web-Container stellt eine Laufzeitumgebung und Frameworks für web-basierte ClientAnwendungen, die durch Servlets und Java-Server-Pages (JSPs) realisiert werden (siehe 7)
zur Verfügung.
6.1.1.4. Applet-Container
Als Teil einer web-basierten Client-Anwendung können auch Applets zum Einsatz kommen.
Von dem her ist der Applet-Container aber nur eine Ergänzung zum Web-Container, der sich
im Zusammenspiel mit einem Applikations-Server nicht anders verhält als bei eigenständigen
Applets.
6.1.1.5. Datenbank
Als weitere Infrastrukturkomponente kommt zur persistenten Speicherung der Daten ein Datenbankmanagementsystem (DBMS) zum Einsatz, das nicht zwingend Teil des eigentlichen
Applikations-Servers, sondern in der Regel unabhängig ist [08]. Der Applikations-Server
stellt lediglich einen einheitlichen Zugriffsweg (JDBC, Persistenz-Manager) dafür bereit.
- 171 -
Java-Enterprise - Übersicht
6.1.2. Dienste
Die Komponenten eines Applikations-Servers müssen verschiedene spezifizierte Dienste und
technische Funktionalitäten zur Verfügung stellen, die von Anwendungen genutzt werden
können. Außerdem kapselt der Server dadurch den Zugriff auf die Ressourcen des zugrundeliegenden Betriebssystems [08].
6.1.2.1. JMS
Der Java Messaging Service (JMS) stellt eine API für den asynchronen Austausch von Nachrichten zwischen den Komponenten zur Verfügung [15]. Als Empfänger der Nachrichten fungieren z.B. Message Driven Beans (siehe 6.3.4).
6.1.2.2. JMX
Die Java Management Extension (JMX) [16] ist eine Spezifikation zur Überwachung und
Steuerung von Java-Anwendungen. Auch die Kommunikation zwischen Anwendungen innerhalb einer VM wird dadurch vereinfacht. Durch ein Model von Adaptern und Connectoren
läßt sich sehr leicht eine externe Steuerung der Objekte der Anwendung realisieren, z.B. mit
einem HTTP-Adapter über einen Web-Browser oder mit einem SNMP-Adapter durch andere
Verwaltungs-Tools [48].
6.1.2.3. JAAS
Der Java Authentication und Authorization Service (JAAS) [07] ist eine API, um Dienste zur
Authentifizierung und Zugriffsberechtigung in Java-Programmen bereitzustellen. JAAS orientiert sich dabei an den Pluggable Authentication Modules (PAM) und unterstützt eine benutzerbasierte Autorisierung [50].
6.1.2.4. JTA
Die Java Transaction API (JTA) [51] ist eine Art Protokoll, das der Anwendung die Steuerung
der Transaktionsverwaltung erlaubt, die wiederum vom Java Transaction Service (JTS) [52]
nach der OMG Object Transaction Spezifikation implementiert wird [08].
6.1.2.5. JavaMail
Die JavaMail-API [53] erlaubt den Zugriff auf verbreitete Mail-Dienste, wie z.B. SMTP,
POP3, IMAP oder NNTP [08].
6.1.2.6. JAF
Das Java Beans Activation Framework (JAF) [49] bietet die Möglichkeit, verschiedene Daten
anhand des MIME-Headers zu erkennen und dementsprechend bestimmte Funktionalitäten
oder Enterprise Java Beans zu aktivieren.
- 172 -
Java-Enterprise - Übersicht
6.1.2.7. JCA
Die Java Connector Architecture (JCA) [56] ist eine Schnittstellen-Spezifikation zur Integration von heterogenen Anwendungen in die J2EE-Plattform, um den Austausch von Daten und
die Verbindung von Geschäftsprozessen zu vereinfachen [57].
6.1.2.8. JAXR
Die Java-API for XML-Registries (JAXR) [54] stellt einen transparenten Zugriff auf so genannte Business-Registries, wie z.B. ebXML oder UDDI, zur Verfügung [08].
6.1.2.9. JAX-RPC
Die Java-API for XML-Based Remote Procedure Calls (JAX-RPC) [55] dient zum Zugriff
auf entfernte RPC-Dienste, die als Web-Service angeboten werden, über das SOAP-Protokoll.
6.1.3. Verfügbare Implementierungen
Die angegeben Dienste stellen in erster Linie Spezifikation und Definition dar, wobei es von
SUN jeweils eine Referenz-Implementierung gibt. Verbreitet sind aber etliche kommerzielle
und freie Implementierungen der Standards. Applikations-Server und Web-Container sind
meist separate Produkte die aber oft in Kombination vertrieben werden [08].
6.1.3.1. Open Source Server






Apache Geronimo (benutzt Jetty)
JBoss
(benutzt Apache Tomcat)
JOnAS
(benutzt Apache Tomcat)
GlassFish
(benutzt Apache Tomcat)
Enhydra Enterprise (benutzt Apache Tomcat)
Sun Java System Application Server (Referenz-Implementierung)
6.1.3.2. Proprietäre Server





ATG Dynamo Application Server (DAS)
BEA WebLogic
SAP Web Application Server
Oracle Application Server
IBM WebSphere
6.1.3.3. Web-Container




Apache Tomcat
Caucho Technology Resin
Enhydra Server
Jetty
Open Source
proprietär
Open Source
Open Source
- 173 -
Java-Enterprise - Der JBoss-Application-Server
6.2. Der JBoss-Application-Server
6.2.1. Allgemeines
Für dieses Tutorial wird der JBoss-Application-Server [02] [03] verwendet, eine zertifizierte
Anwendungs-Plattform nach dem J2EE (Java 2 Enterprise Edition) Standard. Vor allem die
vereinfachte Entwicklung von Anwendungen mit Enterprise Java Beans (EJB) 3.0, bei der
Annotations statt Deployment-Descriptoren benutzt werden, wird unterstützt. Der Applikations-Server bietet verschiedene frei konfigurierbare Dienste an, die jeweils in einem eigenen
"Service Archive" verpackt und auf dem Server "deployed" werden [04]. Er enthält als Servlet-Container für Web-Anwendungen den "Tomcat" [06] von der Apache-Foundation [05].
6.2.2. Installation
Vorraussetzung für die Verwendung von JBoss ist ein installierter Java-Development-Kit
(JDK) [01], das Java-Runtime-Environment (JRE) alleine reicht nicht aus. Eine Umgebungsvariable JAVA_HOME muß auf den Pfad des JDK gesetzt werden, damit der JBoss die Programme und Tools des JDK finden und verwenden kann.
Abbildung 72: Windows Umgebungsvariablen
Alternativ kann auch die Batch-Datei (siehe 6.2.4.1), die zum Starten des Servers verwendet
wird, abgeändert und der Pfad zum JDK dort direkt als Variable eingetragen werden.
- 174 -
Java-Enterprise - Der JBoss-Application-Server
6.2.2.1. Aus dem beiliegenden Paket
Der JBoss-Application-Server kann direkt aus dem an der FH vorbereiteten Archiv
http://java.fh-regensburg.de/download/jboss-4.2.2.GA-package.zip
entpackt, gestartet (siehe 6.2.4.1) und verwendet werden. Das Paket enthält den JBoss in der
Version 4.2.2.GA mit der Unterstützung von EJB 3.0, sowie den vorkonfigurierten Datenquellen MySqlDS (siehe 6.2.3.2), OracleDS (siehe 6.2.3.4), PostgresDS (siehe 6.2.3.3), und
den dafür nötigen JDBC-Treibern.
6.2.2.2. Originalinstallation von jboss.org
Der JBoss kann auch direkt von der Download-Seite des Herstellers
http://labs.jboss.com/portal/jbossas/download
heruntergeladen werden. Wie dort angegeben muß zur Unterstützung von EJB 3.0 der Installer verwendet werden, das Herunterladen und Entpacken eines Archivs ist dafür nicht ausreichend. Die Installation über Java-Web-Start direkt gestartet werden. Alternativ kann der Installer als Paket von sourceforge.net heruntergeladen und gestartet werden:
http://prdownloads.sourceforge.net/jboss/jems-installer-1.2.0.BETA3.jar
java -jar jems-installer-1.2.0.BETA3.jar
Abbildung 73: Paket-Auswahl bei der JBoss-Installation
- 175 -
Java-Enterprise - Der JBoss-Application-Server
Der dialogbasierte Installer (erstellt mit IzPack [09]) führt durch die Installation von JBoss.
Wichtig dabei ist die korrekte Auswahl des Typs der Installation. Nur bei der Auswahl von
ejb3
oder
ejb3-clustered
ist später die Unterstützung für EJB 3.0 verfügbar. Da bei allen anderen Installations-Typen
die nötigen Bibliotheken für EJB 3.0 nicht installiert werden, sind diese eher uninteressant.
6.2.2.3. Verzeichnisstruktur
Abbildung 74:
JBoss-Verzeichnisstruktur
bin Startskripte (siehe 6.2.4.1)
client jar-Dateien für externe Client-Anwendungen
docs Beiliegende Dokumente
dtd Document Type Definitions (DTDs) zum Validieren der
XML-Konfigurationsdateien
examples Vorlagen für verschiedene Dienste
binding-manager Konfiguration der Port-Nummern
jca Vorlagen für verschiedene Datenquellen / Datenbanken
jms Vorlagen für die Persistenz des Java-Messaging
jmx EJB-Management-Beans
netboot Das Netboot-Archiv, mit dem ein JBoss ohne Installation gestartet werden kann
varia Diverse Beispiele
licenses Lizenzdokumente der verwendeten Komponenten
schema XML-Schemas der Deployment-Descriptoren
tests Testergebnisse von JBoss.org
lib Bibliotheken für den Start des Servers
scripts Skripte für zusätzliche Installationsvorgänge
server Hauptverzeichnis mit verschiedenen möglichen Serverkonfigurationen
default Standardkonfiguration
conf Konfigurationsdateien für den Server
data Datenverzeichnis
hypersonic Datendateien der Hypersonic-Datenbank
wsdl Schemata der verfügbaren Web-Services
deploy Die Applikationen und Dienste auf dem Server
lib Bibliotheken die von Anwendungen benötigt werden
log Ablage der Log-Dateien des Servers
tmp Ablage von ausgelagerten Stateful-Session-Beans und
des Inhalts ausgepackter Archive
work Hier werden JSP-Seiten von Tomcat zu Servlets compiliert
6.2.3. Datenbankanbindung
Zum sinnvollen Betrieb von JBoss ist eine Datenbankanbindung notwendig. Alle Anwendungen, die auf dem Server laufen, können die konfigurierten Datenbankanbindungen zum Speichern ihrer Daten verwenden. Auch interne Verwaltungsdaten der Servers, wie z.B. zur Benutzerauthentifizierung mit JAAS (Java Authentication and Authorization Service) [07] werden in einer Datenbank gespeichert.
- 176 -
Java-Enterprise - Der JBoss-Application-Server
Die Anbindungen werden in Dateien mit der Endung ...-ds.xml definiert. Diese Dateien
werden im Deploy-Verzeichnis des Servers
server/default/deploy/
abgelegt, wo der Server die Datei automatisch findet und verarbeitet. In einer JBossInstallation ist eine Reihe von Beispieldateien für die Konfiguration verschiedener Datenbanken im Verzeichnis
docs/examples/jca/
enthalten. Dort müssen nur die passenden Parameter eingetragen und die Dateien im DeployVerzeichnis abgelegt werden. Im folgende ist jede Datenquelle in einer eigenen Datei definiert, die Angaben könnten aber jederzeit zusammengefaßt werden.
6.2.3.1. Hypersonic
Die Hypersonic-Datenbank-Engine [14] ist eine integrierte relationale Java-DatenbankEngine und wird beim JBoss mitgeliefert. Sie ist als Standard-Datenquelle im DeployVerzeichnis in der Datei hsqldb-ds.xml unter dem Namen DefaultDS voreingestellt und
muß nicht erst konfiguriert werden.
<?xml version="1.0" encoding="UTF-8" ?>
<datasources>
<local-tx-datasource>
<jndi-name>DefaultDS</jndi-name>
<connection-url>
jdbc:hsqldb:${jboss.server.data.dir}${/}hypersonic${/}localDB
</connection-url>
<driver-class>org.hsqldb.jdbcDriver</driver-class>
<user-name>sa</user-name>
<password></password>
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<idle-timeout-minutes>0</idle-timeout-minutes>
<track-statements />
<metadata><type-mapping>Hypersonic SQL</type-mapping></metadata>
<depends>jboss:service=Hypersonic,database=localDB</depends>
</local-tx-datasource>
<mbean code="org.jboss.jdbc.HypersonicDatabase"
name="jboss:service=Hypersonic,database=localDB">
<attribute name="Database">localDB</attribute>
<attribute name="InProcessMode">true</attribute>
</mbean>
</datasources>
Resource 20: Hypersonic-Datenquelle hsqldb-ds.xml
Die Daten und Einstellungen der Hypersonic-Datenbank-Engine werden im Unterverzeichnis
server/default/data/hypersonic
in der voreingestellten Datenbank-Datei localDB abgelegt. Diese Datenbank muß nicht explizit angelegt werden, da bei Hypersonic nichtvorhandene Datenbanken automatisch erzeugt
werden.
- 177 -
Java-Enterprise - Der JBoss-Application-Server
6.2.3.2. MySQL
MySQL ist ein freies Open-Source SQL-Datenbankverwaltungssystem [10], das momentan in
der Version 5.0 vorliegt und von der Homepage des Herstellers MySQL AB [11] heruntergeladen werden kann.
http://dev.mysql.com/downloads/mysql/5.0.html
Auf die Installation und Einrichtung eines MySQL-Servers soll in diesem Zusammenhang
nicht weiter eingegangen werden. Lediglich der Zugriff auf einen vorhandenen Server ist hier
von Interesse. Dazu wird der ein JDBC-Treiber für MySQL benötigt, der auch vom Hersteller
heruntergeladen werden kann.
http://dev.mysql.com/downloads/connector/j/5.0.html
Die heruntergeladene Datei mysql-connector-java-5.1.5.zip muß entpackt und das darin
enthalte Jar-Archiv mit den Treiber-Klassen mysql-connector-java-5.1.5-bin.jar im
Lib-Verzeichnis des Servers (unter der entsprechenden Konfiguration, hier default)
server/default/lib
abgelegt werden. Falls der JBoss-Server gerade läuft, muß er neu gestartet werden, damit die
Klassen aus dem Jar-Archiv verfügbar sind.
Nun muß der JBoss zu Verwendung der MySQL-Datenbank konfiguriert werden. Dazu wird
die Vorlage
docs/examples/jca/mysql-ds.xml
entsprechend angepaßt und im Deploy-Verzeichnis abgelegt.
<?xml version="1.0" encoding="UTF-8" ?>
<datasources>
<local-tx-datasource>
<jndi-name>MySqlDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/jboss</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>jboss</user-name>
<password>jboss</password>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
Resource 21: MySQL-Datenquelle mysql-ds.xml
In der XML-Datei wird die Datenquelle unter dem Namen „MySqlDS“ eingerichtet. Die darin
angegebene Datenbank muß auf dem MySQL-Server vorhanden sein, und der angegebene
Benutzer eingerichtet sein, und darauf Zugriff haben.
- 178 -
Java-Enterprise - Der JBoss-Application-Server
6.2.3.3. PostgreSQL
Ein anderes verbreitetes Open-Source Datenbank-Verwaltungs-System ist PostgreSQL [19].
Auch dafür ist der passende JDBC-Treiber erforderlich, der unter
http://jdbc.postgresql.org/download.html
heruntergeladen werden kann und im Lib-Verzeichnis abgelegt werden muß. Im VorlagenVerzeichnis existiert auch für eine Datei für PostgreSQL, die nach dem Anpassen der Verbindungsparameter im Deploy-Verzeichnis des Servers gespeichert wird.
<?xml version="1.0" encoding="UTF-8" ?>
<datasources>
<local-tx-datasource>
<jndi-name>PostgresDS</jndi-name>
<connection-url>
jdbc:postgresql://[servername]:[port]/[database name]
</connection-url>
<driver-class>org.postgresql.Driver</driver-class>
<user-name>[benutzername]</user-name>
<password>[passwort]</password>
<metadata>
<type-mapping>PostgreSQL 7.2</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
Resource 22: PostegreSQL-Datenquelle postgres-ds.xml
Die Datenquelle würde hier mit dem Namen PostgresDS eingerichtet und die Angaben müssen für den entsprechenden PostgreSQL-Datenbank-Server erfolgen.
6.2.3.4. Oracle
Auch die Anbindung an die zentrale Oracle-Datenbank der Fakultät ist von Interesse. Dafür
ist der entsprechende JDBC-Treiber für Oracle
ojdbc14.jar
aus einer Oracle-Installation oder von der Oracle-Downloadseite
http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html
erforderlich, der im Lib-Verzeichnis des JBoss-Servers vorhanden sein muß. Danach wird die
Datenquellen-Konfigurations-Datei
/docs/examples/jca/oracle-ds.xml
editiert und im Deploy-Verzeichnis von JBoss gespeichert, um die Datenquelle mit dem Namen OracleDS zu definieren.
- 179 -
Java-Enterprise - Der JBoss-Application-Server
<?xml version="1.0" encoding="UTF-8" ?>
<datasources>
<local-tx-datasource>
<jndi-name>OracleDS</jndi-name>
<connection-url>
jdbc:oracle:thin:@fbim.fh-regensburg.de:1523:ora10g
</connection-url>
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<user-name>[nds-kürzel]</user-name>
<password>[nds-kürzel]</password>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
</exception-sorter-class-name>
<metadata>
<type-mapping>Oracle10g</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
Resource 23: Oracle-Datenquelle oracle-ds.xml
In der Datei muß lediglich der Benutzername und das Paßwort angepaßt werden, damit jeder
Benutzer Zugriff auf seine eigenen Daten hat.
Bei der Übertragung der Daten zu Oracle muß aber zum Beispiel auf reservierte Wörter geachtet werden. So kann die Kontonummer in einer Oracle-Datenbank nicht als "number" gespeichert werden, da "number" bei Oracle ein reserviertes Wort ist. Dies muß durch eine entsprechende Annotation berücksichtigt werden.
6.2.4. Verwendung
Nach der erfolgreichen Installation kann der JBoss gestartet und bestimmte Dienste über die
voreingestellten TCP-Ports angesprochen werden.








1098 RMI-Registry
1099 Naming-Service
4444 RMI-Object-Port (JRMP)
4445 RMI-Object-Port, PooledInvoker
8009 Tomcat-AJP-Port
8080 Tomcat-HTTP-Connector
8083 Web-Service-Port
8093 UIL2-Port für JMS
Von dieser Liste sind vor allem der Port 8080, von Tomcat für Aufrufe von WebAnwendungen mit einem Browser, und der Port 1099, um über den Namensdienst auf Enterprise JavaBeans zuzugreifen, wichtig.
- 180 -
Java-Enterprise - Der JBoss-Application-Server
6.2.4.1. Einfacher Start
Gestartet werden kann der JBoss grundsätzlich immer mit den mitgelieferten Skripten. Wie
o.g. wird unter Windows die Batch-Datei
bin/run.bat
aufgerufen. Diese setzt noch einige Umgebungsvariablen und Einstellungen für Java und startet dann die JVM mit der JBoss-Main-Klasse aus dem Archiv run.jar. Die JVM mit dem
JBoss-Server läuft dann in einem Konsolenfenster ab, wo Statusmeldungen und etwaige Fehler (Ausnahmen) sofort zu sehen sind.
Abbildung 75: Konsolenfenster eines laufenden JBoss
Beendet wird der Server dann entweder durch das Drücken von Strg+C im Konsolenfenster
oder durch den Aufruf des Shutdown-Skripts
bin/shutdown.bat
das dem laufenden Server über JMS das Herunterfahren signalisiert.
- 181 -
Java-Enterprise - Der JBoss-Application-Server
6.2.4.2. JBoss als Windows-Systemdienst
Eine Java-Anwendung kann nicht einfach beliebig als Window-Systemdienst gestartet werden, da die Windows-API einige Anforderungen stellt, wie sich ein Dienst zu verhalten hat.
Zu diesem Zweck gibt es unter
http://javaservice.objectweb.org/
das Tool JavaService, das auf der einen Seite den Aufruf der JVM mit einer beliebigen
Klasse für den gewünschten Dienst kapselt, und sich auf der anderen Seite konform zur API
für Windows-Dienste verhält. Das Archiv mit der derzeit aktuellen JavaService-Version
JavaService-2.0.10.zip
enthält neben dem Service-Tool auch einige vorgefertigte Installationsskripten, unter anderem
für den JBoss. Die Vorraussetzung dafür sind lediglich die beiden Umgebungsvariablen
JAVA_HOME
(Absoluter Pfad zum installierten JDK) und
JBOSS_HOME
(Absoluter Pfad zur Verzeichnis der JBoss-Installation).
die von den Skripten ausgewertet und entsprechend bei einem solchen Java-Dienst eingetragen werden. Somit kann der JBoss relativ einfach durch Aufruf von
InstallJBoss.bat [depends_on] [-auto / -manual]
installiert werden. Die optionale Angabe [depends_on] gibt einen bereits vorhandenen
Dienst an, der für den Start des neuen JBoss-Dienstes erforderlich ist, z.B. MySQL. Außerdem kann mit [-auto / -manual] angegeben werden, ob der Dienst beim Systemstart automatisch mitgestartet werden soll. Standardmäßig wird ein neu installierter Java-Service so
eingestellt, daß er beim Systemstart mitgestartet wird.
Anschließend ist der JBoss-Server als Windows-Systemdienst eingerichtet und wie alle anderen Dienste unter
Systemsteuerung -> Verwaltung -> Dienste -> JBoss
verfügbar, und kann dort gestartet und beendet werden. Direkt nach der Installation muß der
Dienst einmal manuell gestartet oder aber der Rechner neu gebootet werden.
Über den Status und die Operationen des Servers kann man sich bei dieser Betriebsvariante
über die Log-Datei informieren.
server/default/log/server.log
Die darin enthaltenen Informationen sind in der Standardeinstellung noch wesentlicher detaillierter als die Ausgaben im Konsolenfenster.
- 182 -
Java-Enterprise - Der JBoss-Application-Server
6.2.4.3. Deployment von Anwendungen
Um eine eigene Anwendung auf dem Server ablaufen zu lassen, muß diese, einschließlich
irgendwelcher nötigen Zusatzdateien und Konfigurationen, auf dem Server deployed werden.
Dabei sind die verwendeten Datei- und Archiv-Typen und -endungen von Bedeutung. Alle
Archive sind ZIP-Archive, nur mit entsprechend angepaßter Endung [18].
.sar
.rar
.har
.jar
.war
.ear
.aop
.xml
Service-Archive für spezielle Dienste, besonders für MBeans
Resource Archive, z.B. für Konnektoren zu Datenbanken
Hibernate-Archive, um eine oder mehrere Hibernate-Instanzen zu deployen
Java-Archive (ganz allgemein)
Web-Archive für Webanwendungen, Servlets, JSP- und HTML-Seiten
Enterprise Application Archive für komplette J2EE-Applikationen
Archiv für die aspektorientierte Programmierung
Konfigurationsdatei im XML-Format:
*aop.xml
Definition von Aspekten bei der aspektorientierten Programmierung
*deployer.xml Konfiguration eines Deployers für bestimmte Dateitypen
*ds.xml
Definition von Resource-Adaptern und Datenquellen
*service.xml Beschreibung von Server-Diensten
.wsr Web-Service-Repository des zukünftigen JBoss.Net-Subsystems
.bsh BeanShell-Skript. BeanShell ist ein in Java geschriebener Interpreter für Java
Auch die Startreihenfolge hängt grundsätzlich von der Endung ab. Über den standardmäßig
eingestellten DeploymentSorter werden die Dateien und Archive in folgender Reihenfolge
verarbeitet bzw. gestartet:
*deployer.xml, .sar, .rar, *ds.xml, *service.xml, .har, .jar, .war, .wsr, .ear, .zip, .bsh
Damit wird z.B. sichergestellt, daß für eine Web-Applikation die auf EJBs zugreift, diese
auch verfügbar sind, da EJBs vor Web-Applikationen gestartet werden. Ein in
*deployer.xml definierter zusätzlicher Deployer kann in diese Reihenfolge allerdings weitere Typen einfügen.
Um den Deploy-Vorgang anzustoßen, werden alle nötigen Dateien der Anwendung im Deploy-Verzeichnis
server/default/deploy/
des laufenden JBoss-Servers abgelegt. Dieses Verzeichnis und alle Dateien darin werden
ständig auf Änderungen hin überwacht, und sobald dort neue Dateien hinzukommen werden
diese von JBoss verarbeitet und die darin spezifizierten Anwendungen gestartet.
6.2.5. Verwaltung über die JMX-Konsole
Die JMX-Konsole [16] dient zur webbasierten Statusabfrage und Verwaltung des JBossServers. In der Ausgangskonfiguration ist sie in einem Web-Browser unter der URL
http://localhost:8080/jmx-console/
abrufbar. Dort werden alle konfigurierten Dienste und MBeans nach Typ getrennt aufgelistet.
- 183 -
Java-Enterprise - Der JBoss-Application-Server
6.2.5.1. System-Eigenschaften
Im Abschnitt "jboss" wird unter dem Eintrag "name=SystemProperties,type=Service"
das MBean für die Anzeige der System-Eigenschaften aufgerufen. Die Methode showAll()
zeigt eine Auflistung aller konfigurierten Einstellungen des laufenden JBoss bzw. der Betriebssystem-Umgebung an. Dies kann zum einen bei Problemen hilfreich sein, zum anderen
können diese Eigenschaften auch in eigenen Anwendungen oder Konfigurationsdateien verwendet werden. Anhang A zeigt eine Auflistung der Eigenschaften.
6.2.5.2. JNDI-Übersicht
Ebenfalls im Abschnitt "jboss" befindet sich der Eintrag "service=JNDIView", der zur Anzeige des JNDI-MBean führt. Hier gibt es die Methode list(), die eine Übersicht über alle
registrierten Namensräume von Anwendungen und deren Einträge im JNDI-Verzeichnis des
Servers anzeigt. Der allgemeine Java-Namensraum listet z.B. die vorher konfigurierten Datenquellen MySqlDS, OracleDS und DefaultDS auf.
java: Namespace
+++|
+++++++|
|
|
|
++|
+++|
+|
+-
XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
DefaultDS (class: javax.sql.DataSource)
SecurityProxyFactory (class:
org.jboss.security.SubjectSecurityProxyFactory)
OracleDS (class: javax.sql.DataSource)
DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
MySqlDS (class: javax.sql.DataSource)
comp (class: javax.naming.Context)
JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
jaas (class: javax.naming.Context)
+- HsqlDbRealm (class:
|
org.jboss.security.plugins.SecurityDomainContext)
+- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
+- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
timedCacheFactory (class: javax.naming.Context)
TransactionPropagationContextExporter (class:
org.jboss.tm.TransactionPropagationContextFactory)
StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
Mail (class: javax.mail.Session)
comp.ejb3 (class: javax.naming.Context)
NonContext: null
TransactionPropagationContextImporter (class:
org.jboss.tm.TransactionPropagationContextImporter)
TransactionManager (class: org.jboss.tm.TxManager)
Resource 24: JNDI-Ansicht des allgemeinen Java-Namensraumes
Diese Ansicht ist eine mögliche Anlaufstelle, um festzustellen ob eine eigene Anwendung
richtig auf dem Server deployed wurde und verfügbar ist, bzw. um den genauen Namen unter
dem die Anwendung erreichbar ist, zu erhalten.
- 184 -
Java-Enterprise - Der JBoss-Application-Server
6.2.5.3. Hypersonic-Manager
Der Datenbank-Manager ist eine einfache Benutzeroberfläche zur Verwaltung der integrierten
Hypersonic-Datenbank. Aufgerufen wird der Manager aus der JMX-Konsole über das MBean
"database=localDB,service=Hypersonic" im Abschnitt "jboss". Davon muß die Methode startDatabaseManager() aufgerufen werden.
Abbildung 76: Hypersonic Datenbank-Manager
Links wird das Schema der vorhandenen Datenbanken und Tabellen als Baumstruktur angezeigt. Über das Menü "Command" erzeugt man SQL-Befehle die manuell vervollständigt werden müssen und dann mit "Execute SQL" an die Datenbank geschickt werden. Die Oberfläche ist allerdings sehr spartanisch, weil alle Aktionen nur auf diese Weise über SQL erfolgen
können, lediglich das Laden und Speichern von SQL-Skripten ist möglich.
Außerdem kann sich der Datenbank-Manager noch mit Anderen HSQL-Datenbanken als der
integrierten Datenbank über das Netzwerk verbinden.
- 185 -
Java-Enterprise - Der JBoss-Application-Server
6.2.6. Produktiver Betrieb
Auf die Möglichkeit JBoss als Applikations-Server im Produktiven Betrieb einzusetzen soll
hier nicht näher eingegangen werden. Jedoch ein wichtigster Schritt dafür wäre die Sicherung
der Administrations-Schnittstellen gegen unbefugten Zugriff. Ein Einstieg zu diesem Thema
kann unter folgenden URL gefunden werden:
http://sourceforge.net/docman/display_doc.php?docid=20143&group_id=22866
6.2.7. Integration in die IDE
Für den JBoss-Server gibt es die auf Eclipse 3.2.1 basierende Entwicklungsumgebung
"JBoss Eclipse IDE" [38] auf der entsprechenden Webseite bei jboss.com zum download:
http://labs.jboss.com/portal/jbosside/download/index.html
Das angebotene ZIP-Archive JBossIDE-2.0.0.Beta2-ALL.zip enthält die vollständige Eclipse-Entwicklungsumgebung mit den JBoss-Plugins, und muß nur heruntergeladen und entpackt werden.
In der Eclipse Version 3.4.1 für Java-Enterprise Entwickler ist diese JBoss-Anbindung bereits
enthalten.
6.2.7.1. Eclipse-Projekt anlegen
Wenn man im Eclipse unter File -> New -> Project... ein neues Projekt anlegen will,
erscheinen im Auswahlfenster für einen Projektassistenten etliche JBoss-spezifische Einträge:

EJB 3.0 Project
Enterprise Java Beans 3.0 Projekt [45]

JBoss AOP Project
Projekt zur aspektorientierten Programmierung mit JBoss [40]

JBoss Cache
Projekt das einen Cache zum Zwischenspeichern von Datenbankzugriffen einbindet [39]

J2EE Projects
Projekt mit älteren Enterprise Java Beans [45]

JBoss jBPM
Projekt mit der JBoss-Ablauf-Verwaltung [41]

JBoss Rules
Projekt zur Entwicklung von Geschäftsrichtlinien mit JBoss
[42]
Für unsere Zwecke wird ein "EJB 3.0 Project" benötigt. Optional kann auch ein "normales" Java-Projekt erzeugt, und bei diesem dann die EJB 3.0 Bibliotheken hinzugefügt werden.
- 186 -
Java-Enterprise - Der JBoss-Application-Server
Abbildung 77: Eclipse-Dialog "New Project"
Für das neue Projekt muß wie gewohnt zuerst ein Name vergeben werden. Als nächstes fragt
der Assistent dann nach einem JBoss-Server den man konkret verwenden will, und dessen
Bibliotheken eingebunden werden sollen.
Neben den Standard-Bibliotheken des verwendeten JDK, sind in dem Projekt dann auch sofort die "JBoss EJB3 Libraries", also alle Archive aus dem JBoss-Verzeichnis, die für die
Entwicklung von Enterprise Java Beans 3 notwendig sind, verfügbar und bereits in den "Java
Build Path" eingebunden.
- 187 -
Java-Enterprise - Der JBoss-Application-Server
Abbildung 78: Eclipse-Dialog "Select a JBoss configuration"
6.2.7.2. JBoss-Konfiguration anlegen
Falls noch keine JBoss-Konfiguration vorhanden ist, muß mit "Create a JBoss Server"
eine angelegt werden. Dafür wird zuerst die Version des verwendeten Servers, in unserem
Fall "JBoss AS 4.0", ausgewählt.
Abbildung 79: Eclipse-Dialog "New Server" - Define
- 188 -
Java-Enterprise - Der JBoss-Application-Server
Im nächsten Dialogfenster wird ein eindeutiger Name für den Server vergeben, und das BasisVerzeichnis, in das der Server zuvor entpackt bzw. installiert wurde, angegeben. Daraufhin
werden die vorhandenen Konfigurationen des Servers erkannt und aufgelistet. Mit "Finish"
wird der Server dann endgültig im Eclipse angelegt.
Abbildung 80: Eclipse-Dialog "New Server" - Create
Der neu angelegte JBoss-Server erscheint dann in der Liste der verfügbaren Server und kann
nun bei Projekten ausgewählt werden.
6.2.7.3. Den Server steuern
Nach dem Anlegen eines JBoss-Servers ist im Package-Explorer neben den eigentlichen JavaProjekten ein neues Projekt Servers vorhanden,
das alle angelegten Server enthält.
Dadurch kann ein Server direkt aus Eclipse heraus
gesteuert werden, sofern er nicht, wie oben beschrieben (siehe 6.2.4.2), als Systemdienst eingerichtet wurde. In diesem Fall kommt es zu Konflikten, wenn der Systemdienst nicht zuvor manuell beendet wird.
Abbildung 81: Eclipse Package Explorer - Servers
- 189 -
Java-Enterprise - Der JBoss-Application-Server
In der Servers-Ansicht werden der Server und sein aktueller Status aufgelistet. Über die
Schaltflächen oder das Kontext-Menü kann er gestartet und gestoppt werden.
Abbildung 82: Eclipse Servers-Ansicht
Sollte Eclipse die Ansicht nach dem Anlegen eines Servers nicht selbständig einblenden, kann
sie im Menü unter
Window -> Show View -> Other... -> Server -> Servers
aufgerufen werden. Die Ausgaben des Servers erfolgen, wie auch bei anderen Programmen, in
einem Konsolenfenster von Eclipse.
Abbildung 83: Eclipse-Console mit laufendem JBoss
6.2.7.4. Deployment eigener Anwendungen
Grundsätzlich sollte das Deployment von selbst erstellten Anwendungen direkt aus der Oberfläche heraus auf einem eingerichteten Server erfolgen können. Da sich jedoch die
"JBoss Eclipse IDE" in der immer noch in einem Beta-Stadium befindet, ist dies nicht möglich. Das Deployment muß daher manuell (siehe 6.2.4.3) oder durch Ant-Skripte erfolgen, die
alle nötigen Dateien in ein Archiv verpacken und dieses in das Deploy-Verzeichnis des JBossServers kopieren.
- 190 -
Java-Enterprise - Enterprise JavaBeans
6.3. Enterprise JavaBeans
6.3.1. Allgemeines
Enterprise JavaBeans sind die Komponenten innerhalb eines J2EE-Servers, mit denen eine
Anwendung implementiert wird. Sie sind aus einfachen Java-Klassen (POJO) aufgebaut und
nutzen die Dienste, die ein Applikations-Server zur Verfügung stellt. Durch zusätzliche XMLKonfigurationsdateien (Deployment-Descriptor) werden Eigenschaften von EJBs definiert,
die nicht hart codiert sind [73].
6.3.1.1. Annotations
In der Version 3 der Enterprise Java (JSR-220) [66] wird die Konfiguration und Beschreibung
der Klassen durch Annotations vorgenommen und die Klassen müssen keine Verwaltungsbzw. Typ-Schnittstellen (Component-Interface) implementieren, was ihre Entwicklung im
Vergleich zu den vorherigen Versionen 1 und 2 stark vereinfacht, da wesentlich weniger
komplexe Deployment-Descriptoren zusätzlich zu den Klassen der Anwendung geschrieben
werden müssen und keine technischen Vorgaben bestehen.
Durch die Annotations werden wie bei JAXB oder Hibernate die Meta-Informationen zu der
Aufgabe, die eine Bean-Klasse innerhalb der Anwendung übernehmen soll, und ihrem Verhalten direkt im Quellcode beschrieben und vom Applikations-Server ausgewertet.
6.3.1.2. Home-Interface
Das Home-Interface einer Bean dient zum Erzeugen, Laden oder Löschen von Instanzen der
Bean-Klasse. Weil eine Client-Anwendung nicht direkt auf die Komponenten eines Applikations-Servers zugreifen kann, muß eine Schnittstelle für diese Funktionalitäten definiert werden. Bei EJB3 ist es allerdings nicht mehr notwendig, daß eine Bean-Klasse ein HomeInterface besitzt, weil man über die lookup()-Methode des EJBContext von außerhalb auf
Bean-Objekte und andere Ressourcen zugreifen kann, die dafür vorgesehen sind.
6.3.1.3. Business-Interface
Das Business-Interface wird vom Entwickler frei definiert (POJI) und gibt die Methoden für
die Geschäftslogik einer Anwendung an. Es ist für Session-Beans und Message-Driven-Beans
immer erforderlich. Wenn eine Klasse nur eine einzige Schnittstelle implementiert, wird diese
Schnittstelle als lokales Business-Interface verwendet. Bei mehreren Schnittstellen müssen
das/die Business-Interface(s) durch Annotations explizit angegeben werden.


definiert ein lokales Business-Interface innerhalb des Applikations-Servers
@Remote definiert ein Business-Interface das über RMI aufgerufen werden kann
@Local
Eine Schnittstelle kann nicht gleichzeitig beide Funktionalitäten erfüllen. Folgende Schnittstellen werden bei der Suche nach einem gültigen Business-Interface grundsätzlich ignoriert:



java.io.Serializable
java.io.Externalizable
alle Schnittstellen aus dem Paket javax.ejb
- 191 -
Java-Enterprise - Enterprise JavaBeans
6.3.1.4. Life-Cycle und Callback-Methoden
Jedes Objekt einer Bean-Klasse durchläuft innerhalb eines Applikations-Servers mehrere Zustände, wodurch sich ein eigener Lebenszyklus (Life-Cycle) für jedes Objekt ergibt, der aber
je nach Art der Bean-Klasse unterschiedlich ist. Damit auf diesen Zyklus der Bean-Objekte,
unabhängig von der Geschäftslogik der Anwendung eingegangen werden kann, ist die Verwendung von Callback-Methoden vorgesehen. Die Methoden müssen bei den alten EJB Versionen, je nach Bean-Typ, über eine bestimmte Schnittstelle (javax.ejb.SessionBean,
javax.ejb.EntityBean, java.ejb.SessionBean) implementiert werden.
Bei EJB3 gibt es die Annotations, um eine beliebige Methode als Callback-Methode für ein
bestimmtes Ereignis des Lebenszyklus anzugeben [74].
EJB2-Methode
EJB3-Annotation
Bean-Typ
ejbActivate()
@PostActivate
Session
Entity
ejbPassivate()
@PrePassivate
Session
Entity
ejbCreate()
@PostConstruct
ejbRemove()
@PreDestroy
@PreRemove
@PostRemove
ejbPostCreate()
@PostConstruct
ejbLoad()
@PostLoad
Entity
ejbStore()
@PrePersist
@PostPersist
@PreUpdate
@PostUpdate
Entity
setSessionContext()
setEntityContext()
unsetEntityContext()
setMessageDrivenContext()
Session
@Resource
Entity
@PersistenceContext Entity
Message
Session
Entity
Message
Session
Entity
Message
Session
Entity
Message
Beschreibung
wird vor den Aufruf
einer Bean-Methode
aufgerufen
wird nach dem Aufruf einer BeanMethode aufgerufen
wird vor dem Initialisieren einer neuen
Instanz aufgerufen
wird vor dem entfernen eines Objekts
aufgerufen
wird nach dem Initialisieren einer neuen
Instanz aufgerufen
wird beim Laden aus
der Datenbank aufgerufen
wird beim Speichern
der Daten einer Entity-Bean in der Datenbank aufgerufen
gibt den Kontext
einer Bean an
Tabelle 16: Bean-Callback-Methoden und Annotations
Die Zuordnung der EJB3-Annotations zu den alten Callback-Methoden darf in dieser Tabelle
nicht als absolut festgelegt aufgefaßt werden. Die Annotations haben zum Teil eine leicht
abweichende Semantik und erlauben eine feiner abgestufte Kontrolle der Beans.
Innerhalb des Lebenszyklus eines Bean-Objekts werden auch bestimmte andere Vorgänge
definiert, für die nicht unbedingt eine Callback-Methode aufgerufen wird (z.B. DependencyInjection), aber unter Umständen über andere Mechanismen beeinflußt werden können.
- 192 -
Java-Enterprise - Enterprise JavaBeans
6.3.2. Session-Beans
Session-Beans bilden die Vorgänge der Geschäftslogik ab, bei denen ein Nutzer auf das System zugreift. Sie bedienen sich dabei in der Regel mehrerer Entity-Beans um die Auswirkungen eines Vorgangs darzustellen. Man unterscheidet zwei Arten von Session-Beans [73].
6.3.2.1. Zustandslose Session-Beans
Zustandslose Session-Beans speichert keine Informationen aus vorherigen Aufrufen und unterscheiden auch nicht von welchem Client aus der Aufruf erfolgt ist. Bei jedem Methodenaufruf müssen alle notwendigen Parameter übergeben werden. Session-Bean-Objekte der gleichen Klasse haben daher keine eigene Identität und sind untereinander nicht unterscheidbar.
Vom Applikations-Server wird in der Regel ein bestimmte Anzahl Beans instanziert
(ejbCreate) und in einem Pool abgelegt. Bei einem Aufruf der Bean-Klasse wird dann ein
beliebiges Bean-Objekt aus dem Pool verwendet. Der Container kann die Anzahl der Beans
jederzeit reduzieren (ejbRemove). Der Lebenszyklus einer zustandslosen Session-Bean sieht
daher folgendermaßen aus.
Abbildung 84: Lebenszyklus einer zustandslosen Session-Bean
Eine Klasse für zustandslose Session-Beans wird bei EJB3 durch die Annotation @Stateless
gekennzeichnet.
6.3.2.2. Zustandsbehaftete Session-Beans
Zustandsbehaftete Session-Beans können in jeder Instanz Informationen speichern, die bei
aufeinanderfolgenden Aufrufen immer wieder zur Verfügung stehen. Durch die Vergabe einer
eindeutigen ID durch den Applikations-Server sind die Bean-Instanzen unterscheidbar, und
die richtige Zuordnung zu den Aufrufen des Systems ist möglich. Durch diese Zuordnung
können die Bean-Objekte nicht beliebig aus einem Pool verwendet und bei zu großer Anzahl
einfach verworfen werden. Dafür hat der Container die Möglichkeit die Beans in einen passiven Zustand zu versetzen und sie dabei aus dem Arbeitsspeicher zu entfernen und anderweitig
zu speichern, bis ein Bean-Objekt wieder bei einem Aufruf benötigt wird.
Abbildung 85: Lebenszyklus einer zustandsbehafteten Session-Bean
Eine Klasse für zustandsbehaftete Session-Beans wird durch die Annotation @Stateful gekennzeichnet. Zur Initialisierung bzw. Löschung einer Bean-Instanz können Methoden mit
@Init bzw. @Remove annotiert werden.
- 193 -
Java-Enterprise - Enterprise JavaBeans
6.3.2.3. Dependency-Injection
Damit der Aufbau eines Bean-Objekts einfach bleibt, muß es sich nicht selbst um benötigte
Ressourcen kümmern. Es bekommt die gewünschten Ressourcen durch Dependency-Injection
vom Container zur Verfügung gestellt. Dazu müssen lediglich die nötigen Instanzvariablen in
der Klasse definiert und durch Annotations gekennzeichnet werden. Dies entspricht dem Prinzip "Inversion of Control" (IoC) oder "don't call us, we call you", und drückt aus, daß die
Verantwortlichkeit der Programmausführung stärker beim Applikations-Server liegt.
Annotation
Beschreibung
gibt eine Instanzvariable als Ressource an, die vom Container inji@Resource
ziert werden soll
@Resources
faßt mehrere @Resource Annotation
@PersistenceContext injiziert einen EntityManager zur Verwaltung von Entity-Beans
Tabelle 17: Annotation für Dependency-Injection
Durchgeführt wird die Dependency-Injection durch den Container bevor Methoden aus einem
Business-Interface oder Callback-Methoden für den Lebenszyklus aufgerufen werden. Im
Konstruktor einer Bean-Klasse muß darauf geachtet werden, daß diese Ressourcen dort nicht
verfügbar sind.
Die Art der Resource wird durch den Typ der Instanzvariablen bestimmt.
@Resource(mappedName = "java:/DefaultDS")
private DataSource ds;
@Resource(mappedName = "java:/ConnectionFactory")
private ConnectionFactory factory;
Der zusätzliche Parameter mappedName gibt den JNDI-Namen einer vorhandenen Ressource
an, für die der Container eine Referenz injizieren soll.
6.3.2.4. Interceptoren (AOP)
Durch Interceptoren wird der Ansatz der aspektorientierten Programmierung (AOP) realisiert.
Eine Bean-Methode enthält dabei
nur die Funktionalitäten, die von der
Geschäftslogik der Anwendung
vorgesehen sind.
Allgemeine Funktionalitäten, wie
z.B. Logging, Timing oder Authentifizierung, werden von Interceptoren übernommen, die den Aufruf
von beliebigen Methoden "umschließen" und die zusätzliche
Funktionalität davor bzw. danach
einfügen.
Abbildung 86: Interceptor-Aufruf
- 194 -
Java-Enterprise - Enterprise JavaBeans
Durch die Annotation @AroundInvoke wird eine Methode als Interceptor definiert. Jede Klasse darf nur eine solche Methode besitzen, die aber die folgende Signatur haben muß.
@AroundInvoke
public Object intercept (InvocationContext ctx) throws Exception {
// Funktionalitäten vor dem Methodenaufruf
...
Object result = ctx.proceed();
...
// Funktionalitäten nach dem Methodenaufruf
return result;
}
Code 120: Interceptor-Methode
Über den InvocationContext kann die Interceptor-Methode auf die Parameter des ursprünglichen Methodenaufrufs zugreifen. Durch die Methode proceed() wird der Aufruf der ursprünglichen Methode fortgesetzt, sofern die Implementierung der Interceptor-Methode den
Aufruf nicht verhindern will. Schließlich kann auch der Rückgabewert des Methodenaufrufs
ausgewertet und verändert werden.
Soll die Methode in einer separaten Interceptor-Klasse implementiert werden, muß dies bei
der Bean-Klasse durch die zusätzliche Annotation @Interceptor(MyInteceptor.class)
angegeben werden.
6.3.2.5. EntityManager
Der im folgenden Beispiel verwendete EntityManager ist ein weiterer Teil der JavaPersistence-API und damit des im JBoss verwendeten Hibernate-Frameworks, der in
Kapitel 4.6 noch nicht angesprochen wurde. Im Prinzip besitzt ein EntityManager dieselben
Methoden zum Arbeiten mit persistenten Objekten, wie eine Hibernate-Session. Er ist aber für
die Verwendung in einem Applikations-Server zusammen mit einem PersistenceContext
vorgesehen. Man muß sich nicht selbst um die Konfiguration der Datenbankanbindung, der
persistenten Klassen und das Öffnen von Hibernate-Sitzungen kümmern, sondern kann die
Instanz des EnitityManagers, die man vom Container erhält, ohne weiteres sofort verwenden.
6.3.2.6. Service-Klasse
Die Service-Klasse wird als zustandslose Session-Bean (@Stateless) aufgebaut. Die Schnittstelle IExtendedAccountingService wird implementiert kann durch die entsprechende Annotation (@Remote) direkt als Business-Interface für entfernte Aufrufe angegeben werden.
Durch Dependency-Injection erhält die Klasse eine Instanz des Hibernate-EntityManagers
(@PersistenceContext), zur Verwaltung von Entity-Beans. Der angegebene unitName wird
später in einer separaten Konfigurationsdatei (persistence.xml) genau definiert.
- 195 -
Java-Enterprise - Enterprise JavaBeans
package de.fhr.vs_iw.ejb;
@Stateless
@Remote(IExtendedAccountingService.class)
public class EJBAccountingService implements IExtendedAccountingService {
@PersistenceContext(unitName = "EJBAccountingServiceDS")
private EntityManager manager;
public EJBAccountingService() {}
public IAccount accountChange(IAccount account) {
return manager.merge(account);
}
public IAccountingEntry accountingEntryChange(IAccountingEntry entry) {
return manager.merge(entry);
}
public Double calculateBalance(Integer number)
throws IOException, AccountingException {
double saldo = 0.0d;
Account account = (Account)getAccountByNumber(number);
for (IAccountingEntry entry : account.getDebits()) {
saldo += entry.getAmount();
}
for (IAccountingEntry entry : account.getCredits()) {
saldo -= entry.getAmount();
}
return saldo;
}
public void createAccount(Integer number, AccountType type,
String description) throws AccountingException {
if (number <= 0) {
throw new AccountingException("Die Nummer darf nicht negativ sein");
}
checkAccountNumber(number, false);
Account acc = new Account();
acc.setNumber(number);
acc.setType(type);
acc.setDescription(description);
manager.persist(acc);
}
public Integer createAccountingEntry(Integer debit, Integer credit,
Double amount, String text) throws AccountingException {
checkAccountNumber(debit, true);
checkAccountNumber(credit, true);
if (debit.equals(credit)) {
AccountingEntry.exceptionIdenticalAccounts();
}
AccountingEntry entry = new AccountingEntry();
entry.setText(text);
entry.setAmount(amount);
entry.setDebit(manager.find(Account.class, debit));
entry.setCredit(manager.find(Account.class, credit));
manager.persist(entry);
return entry.getId();
}
- 196 -
Java-Enterprise - Enterprise JavaBeans
public IAccount getAccountByNumber(Integer number)
throws AccountingException {
checkAccountNumber(number, true);
return manager.find(Account.class, number);
}
public Set<Integer> getAccountingEntries() {
ArrayList list = (ArrayList)manager.createQuery(
"SELECT id FROM accountingentry").getResultList();
return new HashSet<Integer>(list);
}
public IAccountingEntry getAccountingEntryById(Integer id)
throws AccountingException {
checkAccountingEntryId(id);
return manager.find(AccountingEntry.class, id);
}
public Set<Integer> getAccounts() {
ArrayList list = (ArrayList)manager.createQuery(
"SELECT number FROM account").getResultList();
return new HashSet<Integer>(list);
}
public void removeAccount(Integer number)
throws AccountingException {
try {
final Account account = checkAccountNumber(number, true);
manager.remove(account);
} catch (final RuntimeException e) {
throw new AccountingException(e);
}
}
public void removeAccountingEntry(Integer id)
throws AccountingException {
try {
final AccountingEntry entry = checkAccountingEntryId(id);
entry.getDebit().getDebits().remove(entry);
entry.getCredit().getCredits().remove(entry);
manager.remove(entry);
} catch (final RuntimeException e) {
throw new AccountingException(e);
}
}
}
...
Code 121: Class EJBAccountingService
Diese Session-Bean verhält sich im Prinzip wie eine Kombination aus der RMI-ServiceKlasse (siehe 5.3.5.1) und der Hibernate-Service-Klasse (siehe 4.6.6.1). Über den Applikations-Server können die Methoden der Klasse von einem Client entfernt aufgerufen werden.
Die Implementierung der Methoden benutzt dann die Funktionalitäten des HibernateEntityMangers um mit den Konten- und Buchungsobjekten (Entity-Beans) zu Arbeiten, und
so die Buchhaltungsdaten zu verwalten.
Beim Löschen von Buchungsobjekten ist darauf zu achten, daß die Objekte auch aus den zugehörigen Sets bei den zugeordneten Konten entfernt werden, da sonst eine Referenz darauf
bestehen bleibt und so der EntityManager in einen inkonsistenten Zustand kommt.
- 197 -
Java-Enterprise - Enterprise JavaBeans
6.3.3. Entity-Beans
Durch Entity-Beans werden die dauerhaften (persistenten) Daten einer Anwendung modelliert, und zur Laufzeit zur Verfügung gestellt. In der Regel stellt eine Entity-Bean-Klasse ein
real existierendes Objekt (Person, Rechnung, Konto, Buchung) dar. Die Daten einer BeanInstanz stammen z.B. aus einem Datensatz einer Datenbank [73].
Die Instanzen von Entity-Beans dienen auf dem Applikations-Server als Datenzugriffsobjekte
(Data Access Object - DAO) und werden vom Container verwaltet, wodurch sich auch wieder
ein bestimmter Lebenszyklus ergibt.
Abbildung 87: Lebenszyklus einer Entity-Bean
Durch diesen Lebenszyklus sind Entity-Beans an den Applikations-Server gebunden. Um die
Daten an einen Client zu übertragen wird das Entwicklungs-Muster "Datentransferobjekt"
(Data Transfer Object - DTO) verwendet und ein separates Objekte verwendet.
Bei der Java Enterprise Edition 5 und dem Enterprise JavaBeans 3.0 Standard gibt es diese
Unterscheidung zwischen DAO und DTO mittlerweile nicht mehr. Die Entity-Beans werden
im Sinne der Java-Persistence-API verwaltet und besitzen auch keinen vorgegebenen Lebenszyklus mehr. Ein persistentes Objekt kann von der Persistenzschicht der Anwendung abgetrennt, in anderen Anwendungsteilen verwendet, und später wieder angefügt werden
(siehe 4.6.5.1). Eine Entity-Bean-Klasse wird durch die Annotation @Entity gekennzeichnet.
6.3.3.1. Bean-Managed-Persistence
Die Persistenz der Daten kann auf verschiedene Weise realisiert werden. Man kann den
Zugriff auf die persistenten Daten in den Methoden der Entity-Beans selbst implementieren,
was als Bean-Managed-Persistence (BMP) bezeichnet wird. Dies würde z.B. dem Ansatz wie
in Kapitel 4.5 entsprechen, eine direkte Datenbankverbindung zu öffnen, und über eigene
SQL-Anweisungen manuell auf die Daten in der Datenbank zuzugreifen.
6.3.3.2. Container-Managed-Persistence
Der bessere Ansatz ist aber die vom Prinzip her bereits angesprochene Variante der Container-Managed-Persistence (CMP), wobei sich der Container um die Persistenz der Daten
kümmert. Konkret wird dies eben durch die Verwendung des EntityMangers erreicht, der die
persistenten Objekte verwaltet. Derartige persistente Entity-Bean-Klassen werden dazu mit
den Annotations der Java-Persistence-API bzw. Hibernate-Annotations, wie sie in Kapitel 4.6
beschrieben wurden, ausgezeichnet. Mit einigen Änderungen entsprechen die Klassen aus
jenem Kapitel bereits den Notwendigkeiten für Java-Enterprise Entity-Beans.
- 198 -
Java-Enterprise - Enterprise JavaBeans
6.3.3.3. FetchType
Der Parameter fetch für den FetchType bei der Annotation @OneToMany gibt an, wie die in
einer Beziehung referenzierten Objekte aus der Datenbank geladen werden sollen. Dazu gibt
es zwei mögliche Varianten

FetchType.EAGER

FetchType.LAZY
referenzierte Objekte werden zusammen mit dem Basisobjekt
instanziert und ihre Daten vollständig aus der Datenbank geladen, sofern ein Objekt nicht bereits geladen wurde
es wird dynamisch ein Proxy-Objekt für ein referenziertes Objekt erzeugt und verwendet, auf die Datenbank wird erst zugegriffen, wenn die Daten des Objekts wirklich abgerufen werden
Bei den Entity-Bean-Klassen wird der Typ EAGER verwendet, damit ein Objekt mit allen relevanten Daten und referenzierten Objekten vollständig geladen wird. Objekte, die von einem
Client verwendet werden, haben sonst später keinen Zugriff auf die Datenbank mehr, weil die
Entity-Beans vom Server abgekoppelt werden.
6.3.3.4. Entity-Bean-Prinzip
Das Prinzip bei der Arbeit mit Entity-Beans beruht bei Java-Enterprise-Anwendungen darauf,
daß die Bean-Objekte zwischen den Schichten und Komponenten einer Anwendung ausgetauscht werden. Bei entfernten Aufrufen der Enterprise-Anwendung soll dies auch die Performance, z.B. gegenüber RMI erhöhen, da nur ein größeres Objekt übertragen wird, und weitere Methodenaufrufe dann lokal erfolgen.
Abbildung 88: Ablauf des Entity-Bean-Prinzips bei einem Kontenobjekt
- 199 -
Java-Enterprise - Enterprise JavaBeans
Bei der Buchhaltung bedeutet dies konkret, daß zwar die Methoden der Service-Session-Bean,
wie bei der RMI-Implementierung des Buchhaltungssystems, entfernt aufgerufen werden, die
Methoden getAccountByNumber() und getAccountingEntryById() aber ihrerseits keine
entfernten Objekte zurückgeben, sondern Objekte der definierten Entity-Bean-Klassen. Diese
Objekte sind serialisierbar und werden vollständig zum Client übertragen, weshalb es eben
wichtig ist, daß die Objekte vom EntityManager vorher vollständig geladen wurden. Die
persistenten Objekte werden dabei natürlich von der Persistenzschicht der EnterpriseAnwendung abgekoppelt.
Der Client kann nun die Objekte und ihre Daten beliebig verwenden. Ein Problem besteht
natürlich dann, wenn der Client die Daten eines solchen Objekts ändert. Da das Entity-BeanObjekt keine Verbindung zur Datenbank mehr hat, wäre die Änderung nicht persistent, und
andere Clients würden auch nichts davon mitbekommen. Deshalb muß das Objekt nach einer
Änderung an den Applikations-Server zurückgeschickt werden.
An dieser Stelle kommen erstmals die beiden zusätzlichen Methoden der erweiterten ServiceSchnittstelle (IExtendedAccountingService), die bei der Service-Session-Bean als Business-Interface verwendet wird, zum Einsatz. Durch den Aufruf von changeAccount() bzw.
changeAccountingEntry() kann ein geändertes Konten- bzw. Buchungsobjekt wieder an
den EntityManager auf dem Applikations-Server, zum Speichern der Daten in der Datenbank,
übergeben werden. Jede Client-Anwendung der Enterprise-JavaBean-Buchhaltung muß nach
dem Ändern von Daten die entsprechende Methode aufrufen.
6.3.3.5. Konto-Klasse
Die Konto-Klasse wird als CMP-Entity-Bean mit Annotations ausgezeichnet und entsprechend implementiert.
Abbildung 89: Beziehung der Entity-Beans
Jedem Kontenobjekt werden die Soll- und Haben-Buchungen jeweils als Set von Objekten
mit der Annotation @OneToMany zugeordnet, um die 1:n-Beziehung herzustellen. Neben der
üblichen Implementierung der Konto-Schnittstellte wird die Klasse mit Getter/SetterMethoden für die Sets der Buchungsobjekte und die Kontonummer ausgestattet.
package de.fhr.vs_iw.ejb;
@Entity
@Table(name = "account")
public final class Account implements IAccount, Serializable {
private Set<AccountingEntry> credits;
private Set<AccountingEntry> debits;
public Account() {}
- 200 -
Java-Enterprise - Enterprise JavaBeans
@Transient
public Set<Integer> getCreditEntries() {
HashSet<Integer> ret = new HashSet<Integer>();
for (AccountingEntry entry : credits) {
ret.add(entry.getId());
}
return ret;
}
@Transient
public Set<Integer> getDebitEntries() {
HashSet<Integer> ret = new HashSet<Integer>();
for (AccountingEntry entry : debits) {
ret.add(entry.getId());
}
return ret;
}
@Id
public Integer getNumber() {
return number;
}
@OneToMany(mappedBy = "credit", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
public Set<AccountingEntry> getCredits() {
return credits;
}
@OneToMany(mappedBy = "debit", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
public Set<AccountingEntry> getDebits() {
return debits;
}
public void setCredits(Set<AccountingEntry> credits) {
this.credits = credits;
}
public void setDebits(Set<AccountingEntry> debits) {
this.debits = debits;
}
public void setNumber(Integer number) {
this.number = number;
}
}
...
Code 122: Class Account (EJB)
Der EntityManager lädt alle Daten für ein Konto und die Sets mit den referenzierten Buchungen, so daß eine Instanz der Konto-Bean-Klasse dann völlig unabhängig verwendet werden kann.
- 201 -
Java-Enterprise - Enterprise JavaBeans
6.3.3.6. Buchungs-Klasse
Die Buchungs-Klasse wird mit Annotations passend zur Konto-Klasse versehen. Da es nicht
mehr reicht nur die Nummern der referenzierten Soll- und Haben-Konten anzugeben, wird die
Klasse mit Instanzvariablen vom Typ Account ausgestattet. Die zugehörigen GetterMethoden werden durch @ManyToOne und @JoinColumn als Gegenstück für die 1:nBeziehung mit der Konto-Klasse angegeben.
package de.fhr.vs_iw.ejb;
@Entity @Table(name = "accountingentry")
public class AccountingEntry implements IAccountingEntry, Serializable {
private Account credit;
private Account debit;
@PersistenceContext(unitName = "EJBAccountingServiceDS")
private EntityManager manager;
public AccountingEntry() {}
@Transient
public Integer getCreditAccount() {
return credit.getNumber();
}
@Transient
public Integer getDebitAccount() {
return debit.getNumber();
}
@Id @GeneratedValue
public Integer getId() {
return id;
}
public void setCreditAccount(Integer number) throws AccountingException {
checkAccountNumber(number, true);
if (number.equals(getDebitAccount())) {
exceptionIdenticalAccounts();
}
setCredit(manager.find(Account.class, number));
}
public void setDebitAccount(Integer number) throws AccountingException {
checkAccountNumber(number, true);
if (number.equals(getCreditAccount())) {
exceptionIdenticalAccounts();
}
setDebit(manager.find(Account.class, number));
}
public void setText(String text) {
this.text = text;
}
@ManyToOne
@JoinColumn(name = "credit")
public Account getCredit() {
return credit;
}
- 202 -
Java-Enterprise - Enterprise JavaBeans
@ManyToOne
@JoinColumn(name = "debit")
public Account getDebit() {
return debit;
}
public void setCredit(Account credit) {
this.credit = credit;
}
public void setDebit(Account debit) {
this.debit = debit;
}
public void setId(Integer id) {
this.id = id;
}
...
}
Code 123: Class AccountingEntry (EJB)
Damit auch Objekte dieser Bean-Klasse unabhängig sind, wird zur Zuordnung von Soll- und
Haben-Kontenobjekten direkt ein EntityManager benutzt, der wie bei der Service-SessionBean-Klasse durch Dependency-Injection vom Container zur Verfügung gestellt wird ist.
6.3.4. Weitere Bean-Arten
Neben den Session-Beans und den Entity-Beans, die in der Regel den Großteil der benötigten
Beans in einer Anwendung darstellen, gibt es noch weitere Arten von Enterprise JavaBeans
und andere Dienste die von einem Applikations-Server zur Verfügung gestellt werden.
6.3.4.1. JMS - Message-Driven-Beans
Message-Driven-Beans (MDB) machen Java-Enterprise-Anwendungen für asynchrone
Kommunikation zugänglich. Hierzu wird der Java-Messaging-Service (JMS) [15] verwendet,
der vom JBoss zur Verfügung gestellt wird. Diese Beans werden häufig bei der Kommunikation mit Legacy-Systemen verwendet [73].
Wie die anderen Bean-Arten auch, werden MDBs von Applikations-Server verwaltet. Sie
haben dabei fast den gleichen Lebenszyklus wie zustandslose Session-Beans.
Abbildung 90: Lebenszyklus einer Message-Driven-Bean
Der Java-Messaging-Service unterstützt zwei verschiedene Varianten der Nachrichtenverarbeitung, Nachrichtenschlangen (message queues) und ein Anmelde-Versende-System (publish-subscribe - topic) [75].
- 203 -
Java-Enterprise - Enterprise JavaBeans
Bei Nachrichtenschlangen gibt es immer genau einen Abnehmer, der die eingehenden Nachrichten erhält. Wenn es für eine Nachrichtenschlange zu einem Zeitpunkt keinen Abnehmer
gibt, so schlägt des senden von Nachrichten an diese Schlange fehl. Wenn die Nachrichten
schneller eintreffen als der Abnehmer sie verarbeiten kann, werden die Nachrichten in der
Warteschlange eingereiht und der Reihe nach abgearbeitet.
Abbildung 91: Nachrichtenschlange
Beim Anmelde-Versende-System werden die Nachrichten einfach verschickt, egal ob sich
jemand als Empfänger angemeldet hat. Jeder angemeldete Empfänger erhält jede Nachricht,
und wenn eine Nachricht nicht konsumiert wird, dann ist dies unerheblich.
Abbildung 92: Anmelde-Versende-System
Im Gegensatz zu anderen Bean-Klassen muß eine Message-Driven-Bean zwingend eine bestimmte Schnittstelle, nämlich javax.jms.MessageListener, implementieren. Diese fungiert im Prinzip wie ein lokales Business-Interface.
package javax.jms;
public interface MessageListener {
void onMessage(Message message);
}
Code 124: Interface MessageListener
Wenn aufgrund der angegebenen Aktivierungskonfiguration eine Nachricht eintrifft wird die
Methode onMessage() mit einem Objekt der Klasse javax.jmx.Message aufgerufen.
Message-Driven-Beans werden durch die Annotation @MessageDriven gekennzeichnet. Bei
dieser Annotation wird durch den Parameter activationConfig angegeben, wann eine Instanz einer solchen Bean-Klasse aktiviert werden soll.
- 204 -
Java-Enterprise - Enterprise JavaBeans
package de.fhr.vs_iw.ejb;
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination",
propertyValue = "queue/vs_iw")
})
public class MessageBean implements MessageListener {
public void onMessage(Message msg) {
System.out.println("Nachricht erhalten: " + msg.toString());
}
}
Code 125: Class MessageBean
Der Parameter activationConfig ist ein Array von @ActivationConfigProperty Annotations. Jede dieser Annotations gibt eine Eigenschaft der Aktivierungskonfiguration an. Die
beiden erforderlichen Eigenschaften mit ihren möglichen Werten sind:

destinationType
gibt die Art des Nachrichtensystems an
o javax.jmx.Queue
Nachrichtenschlange
o javax.jmx.Topic
Anmelde-Versende-System (Publish-Subscribe)

destination
legt den Namen für das Ziel fest, beginnt mit einem Präfix
bei einer Nachrichtenschlange
o "queue/..."
bei einem Anmelde-Versende-System
o "topic/..."
Aufgrund dieser Angaben erstellt der JBoss eine Queue oder ein Topic, mit deren Hilfe die
Nachrichten weitergeleitet werden. Alternativ kann diese Konfiguration auch in einer separaten XML-Datei erfolgen, damit sie von den Klassen der Anwendung unabhängig ist.
<server>
<mbean code="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:service=Queue,name=queue/vs_iw">
<attribute name="JNDIName">queue/vs_iw</attribute>
<depends optional-attribute-name="DestinationManager">
jboss.mq:service=DestinationManager
</depends>
</mbean>
</server>
Resource 25: jmx-queue-service.xml
Diese Art der Konfiguration kann besonders bei einem Anmelde-Versende-System sinnvoll
sein, da hier auch oft vorkommt, daß weder der Sender noch der Empfänger einer Nachricht
als Java-Bean auf dem Server laufen, sondern jeweils als Client-Anwendungen (siehe 6.3.6.5)
realisiert werden, die den Server lediglich zur Kommunikation benutzen.
<server>
<mbean code="org.jboss.mq.server.jmx.Topic"
name="jboss.mq.destination:service=Topic,name=topic/vs_iw">
<attribute name="JNDIName">topic/vs_iw</attribute>
<depends optional-attribute-name="DestinationManager">
jboss.mq:service=DestinationManager
</depends>
</mbean>
</server>
Resource 26: jms-topic-service.xml
- 205 -
Java-Enterprise - Enterprise JavaBeans
6.3.4.2. JMX - Management-Beans
Management-Beans (MBeans) ermöglichen den Zugriff auf eine Anwendung die JavaManagement-Extension (JMX) [16]. Eine Management-Bean muß ein Management-Interface
implementieren, genauso wie jede Session-Bean ein Business-Interface implementieren muß.
Im Prinzip kann jede Bean-Klasse als Management-Bean angegeben werden, indem eine definierte Management-Schnittstelle hinzugefügt wird. Der Name einer ManagementSchnittstelle sollte normalerweise mit dem Suffix "...MBean" enden.
@Management
public interface MyManagementMBean {
void create() throws Exception;
void start() throws Exception;
void stop();
void destroy();
...
}
<More Methods>
Code 126: Interface MyManagementMBean
Jede Management-Schnittstelle kann grundsätzlich aus beliebigen Methoden bestehen. Es gibt
aber die vier Basis-Methoden create(), start(), stop() und destroy() die zwar nicht
zwingend vorhanden sein müssen, aber wenn sie vorhanden sind, als Callback-Methoden
durch den Lebenszyklus der Management-Bean aufgerufen werden.
Abbildung 93: MBean-Lebenszyklus
Der Sinn darin besteht, daß z.B. die Nachrichtenverarbeitung einer Message-Driven-Bean von
außen gestoppt und wieder gestartet werden kann, auch wenn dieser Vorgang nicht zur eigentlichen Geschäftslogik einer Anwendung gehört, aber trotzdem möglich sein soll.
Beim JBoss ist es möglich die Methoden aller MBeans von der JMX-Console aus aufzurufen.
Man kann aber natürlich jederzeit eine eigene Verwaltungsanwendung entwickeln.
- 206 -
Java-Enterprise - Enterprise JavaBeans
6.3.4.3. Service-Beans
Ein Service-Bean ist eigentlich keine Bean-Art im eigentlichen Sinne, sondern eine zusätzliche Eigenschaft für eine der anderen Bean-Klassen. Die Annotation @Service bei einer BeanKlasse bewirkt, daß diese Klasse als Singleton genau einmal instanziert wird, sofort nachdem
die Anwendung auf einem Server deployed bzw. gestartet wurde. Der Applikations-Server
sorgt dafür, daß alle Aufrufe von Methoden der Klasse durch diese eine Instanz ausgeführt
werden. Diese Annotation ist eine JBoss-spezifische Erweiterung und nicht Teil des EJBStandards.
6.3.4.4. TimerService
Der TimerService ist ein Dienst in der EJB-Spezifikation, der zu einer bestimmten Zeit den
Aufruf einer Methode einer Bean veranlaßt. Die Methode der Bean wird durch @Timeout annotiert und muß folgende Signatur haben.
@Timeout
public void timeoutHandler(Timer timer);
Wenn ein Timer abgelaufen ist wird diese Methode vom Container aufgerufen und das abgelaufene Timer-Objekt als Parameter übergeben.
Erstellt wird ein Timer-Objekt durch die create()-Methoden einer TimerService-Instanz.
package javax.ejb;
public interface TimerService {
Timer createTimer(Date initialExpiration, long intervalDuration,
Serializable info);
Timer createTimer(Date expiration, Serializable info);
Timer createTimer(long initialDuration, long intervalDuration,
Serializable info);
Timer createTimer(long duration, Serializable info);
Collection getTimers();
}
Code 127: Interface TimerService
Zugriff auf den TimerService wiederum erhält man über den SessionContext. Dieser wird
wie bereits angesprochen als Resource vom Container injiziert (siehe 6.2.3.2).
@Resource
private SessionContext ctx;
ctx.getTimerService().createTimer(interval, interval, "Hourly Backup");
Ein Timer wird vom Applikations-Server verwaltet und normalerweise auch persistent gespeichert, damit er bei einem Neustart des Servers erhalten bleibt. Beim JBoss wird das Verhalten des TimerService in der Datei ejb-timer-service.xml konfiguriert.
- 207 -
Java-Enterprise - Enterprise JavaBeans
6.3.4.5. Management-Service-Bean mit Timer-Service
Die angesprochenen Möglichkeiten für JavaBeans werden nun in einem Beispiel umgesetzt.
Der praktische Nutzen für die Buchhaltungsanwendung könnte z.B. die zeitgesteuerte Ausführung eines Backup-Vorgangs sein.
Da dieser Vorgang von der der Geschäftslogik her nicht erforderlich ist, und auch von außen
nicht aufgerufen werden soll, wird ein in einem MBean mit der folgenden ManagementSchnittstelle IBackupTimerMBean implementiert.
package de.fhr.vs_iw.ejb;
@Management
public interface IBackupTimerMBean {
void create() throws Exception;
void destroy();
}
Code 128: Interface IBackupTimerMBean
Es sind hier nur die Methoden create() und destroy() jeweils zum Starten bzw. Stoppen
des Timers notwendig, wenn eine MBean-Instanz erstellt bzw. entfernt wird. Die Schnittstelle
bzw. die beiden Methoden werden in der Klasse BackupTimer implementiert.
package de.fhr.vs_iw.ejb;
@Service
@Management(IBackupTimerMBean.class)
public final class BackupTimer implements IBackupTimerMBean {
@Resource
private SessionContext ctx;
private Timer theTimer = null;
public BackupTimer() {}
public void create() throws Exception {
if (theTimer == null) {
theTimer = ctx.getTimerService().createTimer(3600000, 3600000, "xx");
}
}
public void destroy() {
if (theTimer != null) {
theTimer.cancel();
}
theTimer = null;
}
}
@Timeout
public void timeoutHandler(Timer timer) {
if (timer == theTimer) {
System.out.println("Received Timer event: " + timer.getInfo());
} else {
timer.cancel();
}
}
Code 129: Class BackupTimer
- 208 -
Java-Enterprise - Enterprise JavaBeans
Die Klasse erhält den SessionContext durch Dependency-Injection und kann damit in der
create()-Methode der Bean-Klasse einen Timer erstellen, der alle Stunde (3600000 Sekunden) abläuft. Dadurch wird die Methode timeoutHandler aufgerufen, die dann das angesprochene Backup der Buchhaltungsdaten durchführen könnte.
Damit die create()-Methode überhaupt aufgerufen wird, muß das Bean instanziert werden,
was aber normalerweise nur geschieht, wenn ein Aufruf der Bean-Klasse erfolgt, also wenn
irgendwie von außen (lokal oder remote) zugegriffen wird. Dieses Problem wird durch die
@Service Annotation gelöst, die ja die einmalige Instanzierung einer Bean-Klasse beim deployen bzw. starten der Anwendung bewirkt. Außerdem wird dadurch der Timer auch sicher
nur einmal gestartet.
Durch den Aufruf von cancel() in der destroy()-Methode wird der Timer beendet und gelöscht, sobald die MBean-Instanz vom Server entfernt wird.
Die Methoden der Klasse BackupTimer müssen aber die Timer-Instanz bei jedem Aufruf überprüfen, weil der JBoss-Application-Server in der vorliegenden Version ein Problem bei der
gemeinsamen Verwendung der Annotations @Management und @Service hat, das bewirkt,
daß die Methoden der Management-Schnittstelle immer zweimal aufgerufen werden.
6.3.5. Deployment
Alle notwendigen Klassen und Dateien müssen nun noch in Archive verpackt und auf dem
Applikations-Server zur Verfügung gestellt werden. Bei einer Enterprise-JavaBeansAnwendung sollte nun nicht ein einfaches JAR-Archiv, das einfach nur alle Klassen und Dateien enthält, sondern ein Enterprise-Archiv (EAR) verwendet werden.
Außerdem ist hier anzumerken, daß der JBoss-Application-Server, zumindest in der vorliegenden Version 4.2.2.GA, ein Problem mit Enterprise-JavaBeans in einfachen JAR-Archiven
hat. Das Redeployment eines solchen Archivs schlägt immer fehl und der JBoss-ServerProzeß muß neu gestartet werden.
6.3.5.1. Enterprise-Archiv
Ein Enterprise-Archiv faßt andere Archive mit den einzelnen unterschiedlichen Komponenten
einer Anwendung zusammen. Dazu muß das Enterprise-Archiv, neben den untergeordneten
Archiven selbst, einen Deployment-Descriptor META-INF/application.xml enthalten.
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
<display-name>EJBAccountingService</display-name>
<module>
<ejb>beans.ejb3</ejb>
</module>
<module>
<java>persistence.par</java>
</module>
<module>
<java>model.jar</java>
</module>
</application>
Resource 27: application.xml (EJB)
- 209 -
Java-Enterprise - Enterprise JavaBeans
In diesem Deployment-Descriptor werden die einzelnen Module der Anwendung, die der Applikations-Server entpacken und laden muß, je nach ihrem Typ (<ejb>, <java>, <web>)
beschrieben.
6.3.5.2. Archiv für Session-Beans
Die Session-Beans der Buchhaltung (EJBccountingService, BackupTimer, MessageBean) werden mit einem Ant-Vorgang in ein EJB3-Archiv verpackt. Nicht alle diese BeanKlassen stellen Session-Beans dar, weshalb es bei genauer Betrachtung so ist, daß eigentlich
alle Nicht-Entity-Beans zusammen in ein Archiv verpackt werden.
<target name="ejb3">
<delete file="${basedir}/temp/beans.ejb3" />
<jar destfile="${basedir}/temp/beans.ejb3">
<fileset dir="${basedir}/bin/">
<include name="**/ejb/EJBAccountingService.class" />
<include name="**/ejb/BackupTimer.class" />
<include name="**/ejb/IBackupTimerMBean.class" />
<include name="**/ejb/MessageBean.class" />
</fileset>
<fileset dir="${basedir}/modules/">
<include name="**/ejb/EJBAccountingService.java" />
<include name="**/ejb/BackupTimer.java" />
<include name="**/ejb/IBackupTimerMBean.java" />
<include name="**/ejb/MessageBean.java" />
</fileset>
</jar>
</target>
Resource 28: Ant-Vorgang für EJB3-Archiv
Wichtig dabei ist, daß die Endung des Archivs wirklich exakt ejb3 ist, damit die BeanKlassen durch den JBoss-Application-Server richtig verarbeitet werden. Dafür ist nach dem
EJB3-Standard kein zusätzlicher XML-Deployment-Descriptor notwendig, da die Annotations der Klassen verwendet werden und ausreichen.
6.3.5.3. Archiv für Entitiy-Beans
Auch die Entity-Beans (Account, AccountingEntry) werden durch einen Ant-Vorgang in
ein separates JAR-Archiv verpackt. Seit JBoss 4.2 wird nicht mehr die spezielle DateiErweiterung par (Persistence Archive) verwendet, sondern die Standard-Endung jar.
<target name="entity">
<delete file="${basedir}/temp/persistence.jar" />
<jar destfile="${basedir}/temp/persistence.jar">
<fileset dir="${basedir}/bin/">
<include name="**/ejb/Account.class" />
<include name="**/ejb/AccountingEntry.class" />
</fileset>
<fileset dir="${basedir}/modules/">
<include name="**/ejb/Account.java" />
<include name="**/ejb/AccountingEntry.java" />
</fileset>
- 210 -
Java-Enterprise - Enterprise JavaBeans
<metainf dir="${basedir}/resources/ejb">
<include name="persistence.xml" />
</metainf>
</jar>
</target>
Resource 29: Ant-Vorgang für Persistence-Archiv
Als zusätzlicher Deployment-Descriptor ist die XML-Datei persistence.xml notwendig, die
die Verwaltung der Entity-Beans durch den Applikations-Server definiert.
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="EJBAccountingServiceDS">
<jta-data-source>java:/MySqlDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect" />
</properties>
</persistence-unit>
</persistence>
Resource 30: Deployment-Descriptor persistence.xml
Hier wird der Name der Persistence-Unit definiert, auf den der @PersistenceContext für
den in den Bean-Klassen verwendeten EntityManager verweist (siehe 6.3.2.6). Mit den weiteren Angaben wird das Verhalten der Instanz des EntityManager konfiguriert. Der Name
einer definierten Datenquelle, sowie einige zusätzliche Parameter für Hibernate werden angegeben.
6.3.5.4. Archiv für weitere Klassen
Zusätzliche Klassen, die für die Anwendung notwendig sind, werden durch einen weiteren
Ant-Vorgang in eine zusätzliche jar Datei verpackt.
<target name="model">
<delete file="${basedir}/temp/model.jar" />
<jar destfile="${basedir}/temp/model.jar">
<fileset dir="${basedir}/bin/">
<include name="**/model/*.class" />
</fileset>
<fileset dir="${basedir}/main/">
<include name="**/model/*.java" />
</fileset>
</jar>
</target>
Resource 31: Ant-Vorgang für JAR-Archiv
Konkret sind das hier die Schnittstellen und Klassen aus dem Buchhaltungsmodell
(de.fhr.vs_iw.model), auf denen die Bean-Klassen basieren.
- 211 -
Java-Enterprise - Enterprise JavaBeans
6.3.5.5. Gemeinsame Ant-Build-Datei
Die verschiedenen Vorgänge zur Erstellung der Teil-Archive können in einer gemeinsamen
Ant-Build-Datei zusammengefaßt werden.
<?xml version="1.0" encoding="UTF-8"?>
<project name="EJBAccountingService" default="deploy" basedir="..">
<property file="${basedir}/ant/build.properties" />
<target name="ejb3">
...
</target>
<target name="entity">
...
</target>
<target name="model">
...
</target>
<target name="package-ear" depends="ejb3, entity, model">
<delete file="${basedir}/temp/EJBAccountingService.ear" />
<ear destfile="${basedir}/temp/EJBAccountingService.ear"
appxml="${basedir}/resources/ejb/application.xml">
<fileset dir="${basedir}/temp/">
<include name="beans.ejb3" />
<include name="persistence.jar" />
<include name="model.jar" />
</fileset>
<metainf dir="${basedir}/resources/ejb/">
<include name="jms-topic-service.xml" />
</metainf>
</ear>
</target>
<target name="deploy" depends="undeploy, package-ear">
<copy file="${basedir}/temp/EJBAccountingService.ear"
todir="${jboss4.deploy}" overwrite="true" />
</target>
<target name="undeploy">
<delete file="${jboss4.deploy}/EJBAccountingService.ear"
failonerror="false" />
</target>
</project>
Resource 32: Ant-Build-Datei enterprise-deploy-jboss4.xml
Mit den Teil-Archiven und dem Deployment-Descriptor application.xml wird in einem
weiteren Ant-Vorgang, der von den o.g. Vorgängen abhängig ist, das Enterprise-Archiv für
die Buchhaltung erstellt.
Im META-INF Unterverzeichnis innerhalb des Archivs wird die Konfigurationsdatei für das
Anmelde-Versende-System (jms-topic-service.xml, siehe 6.3.4.1) mit aufgenommen,
damit der JBoss das entsprechende Topic anlegen kann.
Nach dem Erstellen wird das Enterprise-Archiv in das Deploy-Verzeichnis des JBossApplication-Servers kopiert und so die Anwendung zur Verfügung gestellt und gestartet.
- 212 -
Java-Enterprise - Enterprise JavaBeans
6.3.5.6. Einstellungen
Zur Konfiguration aller verwendeten Ant-Build-Scripte dient die Datei build.properties.
# Projekt-Eigenschaften
version = 3.3
manifest = ${basedir}/temp/MANIFEST.MF
developer = Florian Lutz
defaultmain = de.fhr.vs_iw.Client
# Externe Pfade
jdk.home = C:/Program Files/Java/jdk1.6.0
jboss4.home = D:/daemon/jboss-4.2.2.GA
jboss5.home = D:/daemon/jboss-5.1.0.GA
axis.home = D:/classpath/axis
hibernate.home = D:/classpath/hibernate
jacorb.home = D:/classpath/jacorb
# Modul-Eigenschaften
jboss4.deploy = ${jboss4.home}/server/default/deploy/
jboss5.deploy = ${jboss5.home}/server/default/deploy/
webservice.path = de/fhr/vs_iw/webservice
# Sonstiges
sun.javadoc = http://java.sun.com/javase/6/docs/api/
Resource 33: build.properties
Darin werden vor allem Pfade zu Ressourcen eingestellt, die sich außerhalb des EclipseProjekt-Verzeichnisses befinden, wie z.B. der Deploy-Pfad für den JBoss-Application-Server,
und auch noch weitere Angaben zum Projekt gemacht.
- 213 -
Java-Enterprise - Enterprise JavaBeans
6.3.6. Client-Anwendung
Nun ist die Buchhaltung als Java-Enterprise-Anwendung auf dem JBoss-Application-Server
verfügbar. Was fehlt ist eine Client-Anwendung, die auf den Server zugreift.
6.3.6.1. Einbinden der nötigen Bibliotheken
Für die Clientseite einer Enterprise-Anwendung liefert der JBoss-Application-Server eine
Reihe von Bibliotheken mit. Diese Bibliotheken befinden sich alle im Verzeichnis
client/
einer JBoss-Installation, und sind im Prinzip die Gleichen, wie auch im lib- oder deployVerzeichnis, nur daß sie im client-Verzeichnis noch mal separat zusammengefaßt wurden.
Alle diese Client-Bibliotheken müssen in den CLASSPATH der Anwendung aufgenommen werden.
Dieser Satz von Bibliotheken stellt den bereits erwähnten Application-Client-Container
(AAC) einer Java-Enterprise-Anwendung dar.
6.3.6.2. Aufruf der Anwendung auf dem Server
Die Anwendung arbeitet auf der Clientseite eigentlich direkt mit einer Session-Bean-Instanz
der Anwendung (IExtendedAccountingService) auf dem Server bzw. einem StellvertreterObjekt, welches das entsprechende entfernte Business-Interface der Bean implementiert und
für die Verbindung zum Applikations-Server sorgt.
Die Proxy-Klasse der Enterprise-Buchhaltung muß daher nur auf die Service-Session-Bean
auf dem Server zugreifen. Die Benutzeroberfläche kann dann die Methoden der Session-Bean,
die ja von den bekannten Buchhaltungsschnittsstellen stammen, direkt verwenden.
package de.fhr.vs_iw.ejb;
public final class EJBProxy implements IAccountingProxy {
public EJBProxy() {}
public void close() {}
public IAccountingService connect(String[] args) throws IOException {
try {
InitialContext ctx = new InitialContext();
return (IAccountingService)ctx.lookup(JNDI_NAME);
} catch (Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
}
private static final String JNDI_NAME
= "EJBAccountingService/EJBAccountingService/remote";
Code 130: Class EJBProxy
- 214 -
Java-Enterprise - Enterprise JavaBeans
Der Zugriff auf den Applikations-Server erfolgt über den InitialContext. Man erhält
Zugriff auf die gewünschte Session-Bean oder ein anderes Objekt auf dem Server durch die
lookup()-Methode unter Verwendung des zugehörigen JNDI-Namens. Den richtigen JNDINamen erhält man entweder durch die entsprechende Kenntnis des Aufbaus und der Konfiguration einer Anwendung oder aber einfach über die JNDI-Übersicht (siehe 6.2.5.2) des JBossApplication-Servers.
Zur Auflösung des Namens wird der JNDI-Namensdienst verwendet. Die Datei
jndi.properties (siehe 6.3.6.4) wird vom InitialContext automatisch gesucht und geladen, um die notwendigen Parameter zum Zugriff auf den JNDI-Namensdienst zu erhalten.
6.3.6.3. Senden einer Nachricht an die MessageBean
Auch wenn die eingeführte Message-Driven-Bean (siehe 6.3.4.1) mit der Geschäftslogik der
Buchhaltungsanwendung nichts zu tun hat, und eigentlich gar nicht benötigt wird, soll an einem einfachen Beispiel demonstriert werden, wie eine Nachricht gesendet wird, die einen
Aufruf der Bean bewirkt.
package de.fhr.vs_iw.ejb;
public final class MessageClient {
private MessageClient() {}
private static final String QUEUE_NAME = "queue/vs_iw";
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
Queue queue = (Queue)ctx.lookup(QUEUE_NAME);
QueueConnectionFactory factory
= (QueueConnectionFactory)ctx.lookup("QueueConnectionFactory");
QueueConnection con = (QueueConnection)factory.createConnection();
QueueSession session = con.createQueueSession(true,
Session.SESSION_TRANSACTED);
Message m = session.createMessage();
m.setStringProperty("bla", "blubb");
m.setStringProperty("foo", "bar");
QueueSender sender = session.createSender(queue);
sender.send(queue, m);
session.commit();
}
}
sender.close();
session.close();
con.close();
Code 131: Class MessageClient
Auch hier wird über den InitialContext mit dem JNDI-Namen die Nachrichtenschlange
aufgerufen, welche für die MessageBean definiert wurde. Anschließend muß eine Verbindung
zu dieser Nachrichtenschlange hergestellt werden. Dazu wird, ebenfalls über JNDI, eine entsprechende Verbindungs-Fabrik (QueueConnectionFactory) vom Server abgerufen, mit der
- 215 -
Java-Enterprise - Enterprise JavaBeans
dann eine Verbindung (QueueConnection) erstellt wird. Da auch das Messaging transaktionsbasiert abläuft, muß eine Sitzung (QueueSession) eröffnet werden.
Über ein Message-Objekt wird nun die Nachricht erzeugt, indem verschiedene Parameter,
Werte und JMS-Daten angegeben werden.
Anschließend kann über einen QueueSender für die Sitzung eine Nachricht an die Nachrichtenschlange verschickt werden. Durch commit() erfolgt dann der transaktionsorientierte Versand. Schließlich werden der Sender, die Sitzung und die Verbindung zur Nachrichtenschlange noch geschlossen.
6.3.6.4. Allgemeine Client-Einstellungen
Neben den Java-Klassen sind noch einige weitere Dateien bzw. Einstellungen für die Anwendungen notwendig.
In der Datei jndi.properties werden Einstellungen zum Zugriff auf den JNDINamensdienst des JBoss-Application-Servers vordefiniert. Wenn die Datei im CLASSPATH
vorhanden ist, wird sie vom InitialContext automatisch geladen und verarbeitet.
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=127.0.0.1:1099
Resource 34: jndi.properties
In der Datei wird im wesentlichen die URL eingestellt, unter der der JNDI-Namensdienst erreichbar ist. Dies ist der Host des JBoss-Application-Servers (hier 127.0.0.1 - localhost) und
der Port des Dienstes (standardmäßig 1099).
Man kann aber auch auf eine solche Properties-Datei verzichten und die notwendigen Angaben direkt im Client-Programm vornehmen. Dazu werden die o.g. Einstellungen als String in
einer Hashtable gespeichert.
String host = "localhost";
String port = "1099";
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
env.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
env.put("java.naming.provider.url", host + ":" + port);
InitialContext ctx = new InitialContext(env);
Code 132: InitialContext mit Hashtable
Mit dieser Hashtable wird dann der InitialContext erzeugt. Dadurch können die Einstellungen für Host und Port des JNDI-Dienstes auch zur Laufzeit vorgenommen werden.
- 216 -
Java-Enterprise - Enterprise JavaBeans
Die Datei log4j.properties wird von den Klassen des Apache Logging Service Project
Log4j verwendet. Dieses Logging-Framework wird von den JBoss-Klassen sowohl auf der
Server- als auch auf der Client-Seite verwendet.
log4j.rootLogger=debug, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}%5p%c{1}:%L-%m%n
Resource 35: log4j.properties
In der Datei werden Logger-Objekte definiert, und auch auf welche Weise die Ausgaben erfolgen sollen, in welchem Format und mit welchem Ziel. Mit den obigen Angaben erfolgen
alle Ausgaben aller Log-Level kontinuierlich auf die Konsole in folgendem Format:
02:34:14,556 DEBUG SocketManager:499 - Begin WriteTask.run
Für eine genaue Übersicht über die Konfgurationsmöglichkeiten des Logging-Frameworks
sollte die zugehörige Dokumentation herangezogen werden [76].
- 217 -
Java-Enterprise - Web-Services
6.4. Web-Services
6.4.1. Allgemeines
Beim JBoss in der Version 4.2.2.GA sind die JBoss-Web-Services-1.0.5 bereits integriert [21]
[22] [23]. Auf der Serverseite enthält das Archiv
server/default/deploy/jbossws.sar
die nötigen Klassen und Dateien, so daß ein Web-Service ohne weitere Vorbereitung auf dem
Server deployed und gestartet werden kann. Für Web-Service-Clients müssen die Bibliothek
client/jbossws-client.jar
und einige weitere dafür grundlegende Bibliotheken eingebunden werden. Die Implementierung der JBoss-Web-Services entspricht dem WS-I Basic-Profile [24] und stellt drei Arten
von Endpunkten zur Verfügung:



RPC/literal
Document/literal
Message style
In der ursprünglichen Web-Services-Spezifikation gibt es dann noch die Arten:


RPC/encoded
Document/encoded
die von JBoss allerdings nicht unterstützt werden, da sie nicht WS-I konform sind. Anhand
der WS-I Spezifikation wird ein Web-Service schließlich in einer WDSL-Datei definiert, die
die verfügbaren Methoden des Dienstes und die Art ihres Aufrufs als XML-Nachrichten beschreibt. Da dieses Format frei spezifiziert und unabhängig ist, sind Web-Services, die auf
derselben WSDL-Beschreibung beruhen, im Prinzip System- und Plattformunabhängig. (Java,
C++, VB, C#, .NET). Deshalb gibt es für viele Programmiersprachen Tools, die aus einer
WSDL-Datei die nötigen Artefakte erzeugen, um den beschriebenen Web-Service anzubieten
bzw. darauf zuzugreifen.
Zur Übertragung der Daten kommt bei den Web-Services normalerweise das auf XML basierende SOAP-Protokoll zum Einsatz [26]. SOAP definiert ein Header-Element, das zusätzliche
Informationen beinhalten kann, und ein Body-Element, das dann die entsprechende WSDLkonforme Nachricht enthält.
Ursprünglich wurde für die Web-Services im JBoss das AXIS-Framework [32] der ApacheFoundation [05] verwendet. Bei der Version 4 haben die Entwickler begonnen, basierend auf
AXIS-1.1, die Web-Service-Funktionalitäten selbst zu implementieren. In der vorliegenden
Version 4.2.2 ist diese Entwicklung größtenteils abgeschlossen und alle Teile, die von AXIS
stammen, wurden ersetzt. Allerdings gibt es noch Defizite bei den JBoss-Web-Service-Tools
(siehe 6.4.3.1) für die automatisierte Erstellung von Web-Service-Clients, dort scheint die
Implementierung noch nicht vollständig zu sein.
- 218 -
Java-Enterprise - Web-Services
6.4.1.1. RPC-Style vs. Document-Style
Diese beiden Arten eines Web-Services unterscheiden sich in der spezifizierten
WSDL-Struktur [25]. Bei RPC ist der Name der aufgerufenen Methode als XML-Element im
XML-Baum der SOAP-Nachricht enthalten, bei Document nicht.
Der Hintergrund dafür ist die Idee, daß beim RPC-Style, wie bei RPC allgemein üblich, nur ein
einzelner Methodenaufruf ausgeführt, und dessen Rückgabewert als Antwort zurückgeliefert
werden soll.
Der Document-Style zielt auf den vereinbarten Austausch von komplexen Daten-Dokumenten
im XML-Format bei einer aufwendigen Geschäftslogik ab. Dabei ist der Name der aufgerufenen Methode weniger wichtig, da nur die Verarbeitung des Anfragedokuments an sich von
Belang ist. Und auch bei der Antwort wird ein aufwendigeres Dokument erzeugt, daß eine
festgelegte Struktur hat und sich nicht mehr auf den ursprünglich aufgerufenen Methodennamen beziehen muß.
RPC/literal:
Document/literal:
<soap:envelope>
<soap:body>
<myMethod>
<x>5</x>
<y>5.0</y>
</myMethod>
</soap:body>
</soap:envelope>
<soap:envelope>
<soap:body>
<xElement>5</xElement>
<yElement>5.0</yElement>
</soap:body>
</soap:envelope>
Tabelle 18: Vergleich von SOAP-Nachrichten für RPC-Style und Document-Style
Dies führt auch dazu, daß der Document-Style vollkommen WSDL- bzw. SOAP-Schema konform ist, und sehr leicht mit XML-Tools validiert werden kann, was bei RPC-Style schwierig
ist, da die Namen der aufgerufenen Methoden vom Benutzer bestimmt werden.
6.4.1.2. literal vs. encoded
Der Unterschied zwischen den Unterarten literal und encoded besteht darin, daß bei
literal keine Informationen über den Datentyp von Parametern und Rückgabewerten im
SOAP-Protokoll mitübertragen wird.
literal:
encoded:
<soap:envelope>
<soap:body>
<myMethod>
<x>5</x>
<y>5.0</y>
</myMethod>
</soap:body>
</soap:envelope>
<soap:envelope>
<soap:body>
<myMethod>
<x xsi:type="xsd:int">5</x>
<y xsi:type="xsd:float">5.0</y>
</myMethod>
</soap:body>
</soap:envelope>
Tabelle 19: Vergleich von SOAP-Nachrichten für literal und encoded
Die Datentyp-Information ist normal nur Overhead, der die Performance verringert [25].
- 219 -
Java-Enterprise - Web-Services
6.4.2. Die Buchhaltung als Web-Service-RPC-Endpunkt
Ein Web-Service wird in Java durch eine Endpunkt-Schnittstelle (SEI - Service Endpoint Interface) definiert. Da ein Web-Service bzw. SOAP keine verteilten Objekte unterstützt, muß
hier von den bisher verwendeten bekannten Schnittstellen mit ihrer Objekthierarchie etwas
Abstand genommen, und eine neue "flache" Schnittstelle definiert werden, die sich aber vollständig an den Methoden der bekannten Schnittstellen orientiert.
6.4.2.1. Service-Schnittstelle
Die Methoden der Service-Schnittstelle können an sich unverändert bleiben, jedoch die beiden Factory-Methoden getAccountByNumber() und getAccountingEntryById(), die bisher die entsprechenden Objekte lieferten, entfallen. Dadurch ergibt sich folgende Schnittstelle:
package de.fhr.vs_iw.webservice;
@WebService(targetNamespace = "http://webservice.vs_iw.fhr.de")
public interface IWebAccounting extends Remote {
Double calculateBalance(Integer number)
throws RemoteException, IOException, AccountingException;
void createAccount(Integer number, int type, String description)
throws RemoteException, IOException, AccountingException;
Integer createAccountingEntry(Integer debit, Integer credit,
Double amount, String text)
throws RemoteException, IOException, AccountingException;
Integer[] getAccountingEntries()
throws RemoteException, IOException;
Integer[] getAccounts()
throws RemoteException, IOException;
void removeAccount(Integer number)
throws RemoteException, IOException, AccountingException;
void removeAccountingEntry(Integer id)
throws RemoteException, IOException, AccountingException;
}
...
Code 133: Interface IWebAccounting
Weitere Abweichungen ergeben sich durch die drei Grundregeln für zulässige Web-ServiceEndpunkt-Schnittstellen bei Java:



Die Schnittstelle muß von java.rmi.Remote abgeleitet sein
Alle Methoden müssen mit der throws-Angabe java.rmi.RemoteException versehen werden, wobei Superklassen davon nicht zulässig sind
Die Typen der Parameter und Rückgabewerte sind auf jene Typen beschränkt, welche
in der JAX-RPC-1.1 Spezifikation angegeben sind, wobei keine der ParameterKlassen in irgendeiner Weise von java.rmi.Remote abgeleitet sein darf
Schließlich wird die Schnittstelle noch mit der Annotation @WebService gekennzeichnet, wodurch alle Methoden später zu Methoden des Web-Services werden.
- 220 -
Java-Enterprise - Web-Services
6.4.2.2. Konto-Schnittstelle
Wie oben erwähnt ist für Web-Services eine "flache" Schnittstelle erforderlich. Deshalb werden die Methoden der Konto-Schnittstelle in die IWebAccounting-Schnittstelle integriert.
public interface IWebAccounting extends Remote {
...
Integer[] getCreditEntries(Integer number)
throws RemoteException, IOException;
Integer[] getDebitEntries(Integer number)
throws RemoteException, IOException;
String getDescription(Integer number)
throws RemoteException, IOException;
int getType(Integer number)
throws RemoteException, IOException;
void setDescription(Integer number, String description)
throws RemoteException, IOException;
void setType(Integer number, int type)
throws RemoteException, IOException;
}
Code 134: Konto-Methoden für Interface IWebAccounting
Sie müssen lediglich in der Art abgeändert werden, daß jede einzelne Methode die Nummer
des gewünschten Kontos als Parameter übernimmt und die Grundregeln für EndpunktSchnittstellen beachtet.
6.4.2.3. Buchungs-Schnittstelle
Mit der Buchungs-Schnittstelle wird auf die gleiche Weise verfahren, wie mit der KontoSchnittstelle. Die Methoden werden um die Buchungs-ID als Parameter erweitert und in die
IWebAccounting-Schnittstelle integriert.
public interface IWebAccounting extends Remote {
...
Double getAmount(Integer id)
throws RemoteException, IOException;
Integer getCreditAccount(Integer id)
throws RemoteException, IOException;
Integer getDebitAccount(Integer id)
throws RemoteException, IOException;
String getText(Integer id)
throws RemoteException, IOException;
void setAmount(Integer id, Double amount)
throws RemoteException, IOException;
void setCreditAccount(Integer id, Integer number)
throws RemoteException, IOException, AccountingException;
void setDebitAccount(Integer id, Integer number)
throws RemoteException, IOException, AccountingException;
void setText(Integer id, String text)
throws RemoteException, IOException;
}
Code 135: Buchungs-Methoden für Interface IWebAccounting
- 221 -
Java-Enterprise - Web-Services
6.4.2.4. Implementierung
Das so entstandene Interface muß nun in einer Klasse implementiert werden. Damit die Geschäftslogik nicht noch mal neu programmiert werden muß, soll der Web-Service auf die bereits vorhandenen Bean-Klassen der Buchhaltung zurückgreifen. Dazu wird im Konstruktor
der Klasse das Service-Bean über seinen JNDI-Namen referenziert und alle Methodenaufrufe
werden lediglich weitergegeben.
package de.fhr.vs_iw.webservice;
@WebService(
name = "WebAccounting",
serviceName = "WebAccounting",
targetNamespace = " http://webservice.vs_iw.fhr.de",
endpointInterface = "de.fhr.vs_iw.webservice.IWebAccounting")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class WebAccountingImpl implements IWebAccounting {
private final IExtendedAccountingService ejbservice;
private final static String JNDI_NAME
= "EJBAccountingService/EJBAccountingService/remote";
public WebAccountingImpl() throws NamingException {
ejbservice = (IExtendedAccountingService)
new InitialContext().lookup(JNDI_NAME);
}
...
public Double calculateBalance(Integer number)
throws AccountingException, IOException {
return ejbservice.calculateBalance(number);
}
public Integer[] getAccounts() throws IOException {
final Set<Integer> set = ejbservice.getAccounts();
return set.toArray(new Integer[0]);
}
...
public Double getAmount(Integer id) throws IOException {
try {
IAccountingEntry entry = ejbservice.getAccountingEntryById(id);
return entry.getAmount();
} catch (AccountingException e) {
throw new RuntimeException(e);
}
}
...
}
<Service-Methods>
<Account-Methods>
<AccountingEntry-Methods>
Code 136: Class WebAccountingImpl
- 222 -
Java-Enterprise - Web-Services
Der Aufruf der einzelnen Konten- und Buchungsobjekte erfolgt innerhalb der Methoden, und
es wird nur der gewünschte Wert zurückgegeben. Weiterhin wird der Web-Service mit den
entsprechenden Annotations beschrieben. Die Klasse selbst erhält die Annotation
@WebService, mit zusätzlichen Angaben für die Bezeichnung des Services und des gewünschten XML-Namespace. Hier wird das implementierte Interface noch mal explizit als
Endpunkt-Schnittstelle angegeben und der Typ des Web-Service wird mit der Annotation
@SOAPBinding für die SOAP-Nachrichten auf RPC gesetzt.
Eine Tücke beim JBoss 4.2.2 besteht allerdings darin, daß der angegebene targetNamespace
nicht verwendet wird. Der XML-Namespace für den Web-Service wird aus dem Java-PaketNamen generiert und lautet dann in diesem Fall:
http://webservice.vs_iw.fhr.de/jaws
In neueren Versionen des JBoss (4.2) bzw. der integrierten JBoss-WS (3.0) wurde dieses
Fehlverhalten behoben.
6.4.2.5. Deployment
Der Web-Service wird wie eine Web-Anwendung in ein war Archiv verpackt und vom JBoss
wie ein Servlet behandelt. Dazu ist folgende web.xml Beschreibungsdatei nötig:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>WebAccountingService</servlet-name>
<servlet-class>
de.fhr.vs_iw.webservice.WebAccountingService
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WebAccountingService</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Resource 36: Web-Service-Descriptor web.xml
Dadurch wird angegeben, daß der Web-Service, unter dem Namen bzw. URL-Pfad
/WebAccountingService verfügbar sein soll. Nun kann über ein Ant-Script alles Nötige in
ein entsprechendes war Archiv verpackt werden.
<?xml version="1.0" encoding="UTF-8" ?>
<project name="WebAccountingService" default="ws" basedir="..">
<property file="${basedir}/ant/build.properties" />
<target name="ws">
<delete file="${basedir}/temp/webservice.war" />
<war warfile="${basedir}/temp/webservice.war"
webxml="${basedir}/resources/webservice/web.xml">
- 223 -
Java-Enterprise - Web-Services
<classes dir="${basedir}/bin">
<include name="**/webservice/IWebAccounting.class" />
<include name="**/webservice/WebAccountingImpl.class" />
</classes>
<classes dir="${basedir}/modules">
<include name="**/webservice/IWebAccounting.java" />
<include name="**/webservice/WebAccountingImpl.java" />
</classes>
</war>
<copy file="${basedir}/temp/webservice.war"
todir="${jboss4.deploy}" overwrite="true" />
</target>
</project>
Resource 37: Ant-Build-Datei webservice-deploy.xml
Mit diesem Build-Script werden die Klassen für den Web-Service in die war Archiv-Datei
webservice.war gepackt. Die Datei web.xml dient als Deployment-Descriptor und wird in
das WEB-INF-Verzeichnis des Archivs gepackt. Das fertige Archiv wird in das DeployVerzeichnis des JBoss-Servers kopiert. Die weiteren benötigten Klassen der referenzierten
EJBs und des Buchhaltungs-Modells müssen bereits auf dem JBoss-Server verfügbar sein.
Deshalb bietet es sich an, den Web-Service mit den EJB-Klassen zusammen als EnterpriseArchiv zu verpacken, und alles zusammen zu deployen. Dazu wird das o.g. war Archiv als
Web-Modul im Deployment-Descriptor application.xml des Enterprise-Archivs eingetragen, und bei dessen der Erzeugung mit aufgenommen.
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
<display-name>EJBAccountingService</display-name>
<module>
<ejb>beans.ejb3</ejb>
</module>
<module>
<java>persistence.jar</java>
</module>
<module>
<java>model.jar</java>
</module>
<module>
<web>
<web-uri>webservice.war</web-uri>
<context-root>WebAccountingService</context-root>
</web>
</module>
</application>
Resource 38: application.xml (Web-Service)
Außerdem sollte der Ant-Vorgang zur Erstellung des Web-Archivs in die Ant-Build-Datei für
das Enterprise-Archiv (enterprise-deploy-jboss4.xml) integriert werden, damit alles in
einem Vorgang verarbeitet und deployed werden kann.
- 224 -
Java-Enterprise - Web-Services
6.4.3. JBoss-Web-Service-Tools
Aus der Java-Schnittstelle, die den Endpunkt des Web-Service spezifiziert, können alle weiteren Artefakte (Beschreibungsdateien und Client-Klassen) automatisiert erzeugt werden.
Dazu sind die JBoss-Web-Service-Tools erforderlich, die sich im JBoss-Verzeichnis in
client/jbossws-client.jar
befinden. Das Archiv enthält eine Batch-Datei wstools.bat, mit der die Web-Service-Tools
auf der Kommandozeile aufgerufen werden können. Dabei muß als man Argumente
wstools.bat –dest <zielverzeichnis> -config <konfigurationsdatei>
ein Zielverzeichnis und eine Konfigurationsdatei angeben.
6.4.3.1. Verwendung mit Ant
Günstiger ist die Einbindung der Web-Service-Tools als Ant-Task, der die gleichen Argumente erfordert. Der classpath mit den benötigten Bibliotheken muß definiert werden.
<?xml version="1.0" encoding="UTF-8" ?>
<project name="AccountingServiceWeb" default="generate" basedir="..">
<property file="${basedir}/ant/build.properties" />
<property name="ws.package" value="de/fhr/vs_iw/webservice" />
<path id="classpath">
<pathelement path="${basedir}/bin" />
<pathelement path="${java.home}" />
<pathelement path="${jboss4.home}/lib/endorsed/xercesImpl.jar" />
<fileset dir="${jboss4.home}/client">
<include name="jbossws-client.jar" />
<include name="jbossall-client.jar" />
<include name="jboss-xml-binding.jar" />
<include name="activation.jar" />
<include name="mail.jar" />
<include name="log4j.jar" />
<include name="javassist.jar" />
<include name="jbossretro-rt.jar" />
<include name="jboss-backport-concurrent.jar" />
</fileset>
</path>
<taskdef name="wstools" classname="org.jboss.ws.tools.ant.wstools">
<classpath refid="classpath" />
</taskdef>
<target name="generate">
<wstools dest="${basedir}/generated/${ws.package}"
config="${basedir}/src/${ws.package}/wstools-java2wsdl.xml" />
<sleep seconds="3" />
<wstools dest="${basedir}/generated/"
config="${basedir}/src/${ws.package}/wstools-wsdl2java.xml" />
</target>
</project>
Resource 39: Ant-Build-Datei webservice-generate.xml
- 225 -
Java-Enterprise - Web-Services
6.4.3.2. WSDL-Datei
Die jeweils spezifizierte Konfigurationsdatei für die Web-Service-Tools gibt an, was genau,
mit welchen Parametern erzeugt werden soll. Zur Erzeugung der WSDL-Datei muß die Konfiguration ein <java-wsdl>-Element enthalten.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration xmlns="http://www.jboss.org/jbossws-tools"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.org/jbossws-tools
http://www.jboss.org/jbossws-tools/schema/jbossws-tool_1_0.xsd">
<java-wsdl>
<service name="WebAccountig" style="rpc"
endpoint="de.fhr.vs_iw.webservice.WebAccounting"/>
<namespaces
target-namespace="http://www.fh-regensburg.de/vs_iw/webservice"
type-namespace="http://www.fh-regensburg.de/vs_iw/webservice/types"/>
<mapping file="jaxrpc-mapping.xml"/>
<webservices servlet-link="WebAccountingService"/>
</java-wsdl>
</configuration>
Resource 40: WSTools-Konfiguration wstools-java2wsdl.xml
Durch ein <service>-Element wird mit folgenden Attributen, der Web-Service angegeben:



Die Bezeichnung des Web-Services
style
Die Art des Services (rpc, document, bare)
endpoint Die Java-Endpunkt-Schnittstelle
name
Das <namepaces>-Element legt die zu verwendenden XML-Namensräume fest:


target-namespace
type-namspace
XML-Namensraum für die Methoden von diesem Web-Service
Separater Namensraum für die verwendeten Parametertypen
Das Element <mapping-file> gibt an, ob und wo eine Datei mit den Datentyp-Zuordnungen
für JAX-RPC erzeugt werden soll. Schließlich wird noch mit dem Element <webservices>
und seinem Attribut servlet-link, der Name des Servlets angegeben, das den Web-Service
später zur Verfügung stellen soll.
In der erzeugten WSDL-Datei fehlt noch die zu diesem Web-Service passende SOAPAdresse, unter der Service später verfügbar ist. An der Stelle des Platzhalters
REPLACE_WITH_ACTUAL_URL wird die URL des entsprechenden Web-Service-Sevlets
http://localhost:8080/WebAccountingService
unter JBoss, hier für den lokalen Rechner, eingetragen. Die vollständige WSDL-Datei für den
Buchhaltungs-Service ist im Anhang E zu finden.
- 226 -
Java-Enterprise - Web-Services
Der JBoss-Server bietet aber auch die Funktionalität, das WSDL-Schema eines Web-Service,
der auf dem Server deployed wurde, direkt abzurufen. Dazu wird die Service-URL
http://localhost:8080/WebAccountingService?wsdl
mit dem Parameter ?wsdl aufgerufen. Das generierte Schema enthält bereits die richtige
SOAP-Adresse und kann direkt, durch Angabe der URL, verwendet werden, was für einen
dynamischen Client besonders interessant ist.
6.4.3.3. Client-Artefakte
Die Client-Artefakte werden durch eine Konfiguration mit einem <wsdl-java>-Element aus
einer WSDL-Datei erzeugt. Dies kann die soeben generierte Datei sein, aber auch jede andere,
von einem anderen spezifizierten Web-Service.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration xmlns="http://www.jboss.org/jbossws-tools"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.org/jbossws-tools
http://www.jboss.org/jbossws-tools/schema/jbossws-tool_1_0.xsd">
<global>
<package-namespace package="de.fhr.vs_iw.webservice.client"
namespace="http://www.fh-regensburg.de/vs_iw/webservice" />
</global>
<wsdl-java file="de/fhr/vs_iw/webservice/wsdl/WebAccountigService.wsdl">
</wsdl-java>
</configuration>
Resource 41: WSTools-Konfiguration wstools-wsdl2java.xml
Hier wird in einem globalen Abschnitt eine zusätzliche Einstellung definiert. Durch ein
<package-namespace>-Element wird angegeben, daß ein bestimmter XML-Namensraum
einer bestimmten Java-Package zugeordnet werden soll, der die erzeugten Dateien dann angehören.
6.4.3.4. Erfahrungen mit den JBoss-Web-Service-Tools
Leider hat sich in der Praxis gezeigt, daß die JBoss-Web-Service-Tools bzw. die Implementierung der generierten Client-Klassen nach dem JAX-RPC-Standard bei den JBoss-WebServices noch nicht vollständig genug für eine einfache und unkomplizierte Verwendung ist.
Die Arbeit damit gestaltet sich bei der vorliegenden Version 1.0.3 noch schwierig bis unmöglich, weshalb für die Java-Client-Seite des Buchhaltungs-Web-Service ein anderes Framework zu Einsatz kommt.
- 227 -
Java-Enterprise - Web-Services
6.4.4. Web-Services mit AXIS
Das AXIS-Framework [32] ist eine Implementierung des SOAP-Protokolls [26] von der Apache Foundation [05], war die erste ihrer Art, und hat sich in der Vergangenheit vielfach bewährt. Deshalb wird die Implementierung der Client-Seite des Buchhaltungs-Web-Service
damit ausgeführt.
6.4.4.1. Installation
Um das AXIS-Framework auf der Clientseite verwenden zu können, ist lediglich der Download des Pakets (aktuelle Version 1.4) von der Homepage der Apache-Foundation nötig.
http://ws.apache.org/axis/java/releases.html
Die darin enthaltenen Bibliotheken aus dem lib-Unterverzeichnis müssen nach dem entpacken im CLASSPATH aufgenommen werden.
6.4.4.2. Ant-Vorgang
In den AXIS-Bibliotheken ist ein Ant-Vorgang zum Erzeugen der Client-Artefakte aus der
Web-Service-Beschreibungsdatei vorhanden. Die Beschreibung wird allerdings nicht aus der
vorhandenen WSDL-Datei, sondern direkt von einer URL des laufenden JBoss-Servers bezogen. Dies hat den Vorteil, daß die SOAP-Adresse bereits richtig eingetragen ist, und es wird
auch gleich der richtige XML-Namespace des JBoss verwendet.
Dieser Namenraum (http://webservice.vs_iw.fhr.de/jaws) wird einem Java-Paket
(de.fhr.vs_iw.webservice.axis) zugeordnet, damit die später erzeugten Klassen richtig
eingeordnet werden.
<?xml version="1.0" standalone="yes"?>
<project basedir=".." default="generate-client">
<property file="${basedir}/ant/build.properties" />
<target name="generate-client">
<path id="classpath">...</path>
<taskdef resource="axis-tasks.properties" classpathref="classpath" />
<axis-wsdl2java output="${basedir}/generated"
testcase="true"
verbose="true"
url="http://localhost:8080/WebAccountingService?wsdl">
<mapping namespace="http://www.fh-regensburg.de/vs_iw/webservice"
package="de.fhr.vs_iw.webservice" />
<mapping namespace="http://webservice.vs_iw.fhr.de/jaws"
package="de.fhr.vs_iw.webservice.axis" />
</axis-wsdl2java>
</target>
</project>
Resource 42: Ant-Build-Datei axis-generate-client.xml
- 228 -
Java-Enterprise - Web-Services
6.4.4.3. Client-Artefakte
Aus den Angaben in der WSDL-Beschreibung wurden die nötigen Schnittstellen und Klassen
generiert, die, unter Verwendung der von AXIS bereistgestellten Funktionalitäten, den Zugriff
auf einen konkreten Web-Service von Java aus ermöglichen.
Klasse
AccountingException
IOException
WebAccounting_PortType
WebAccounting_Service
WebAccounting_ServiceLocator
WebAccountingBindingStub
Beschreibung
Buchhaltungsausnahme
Kommunikationsausnahme
Schnittstelle mit den Methoden der Geschäftslogik
Schnittstellte mit Methoden zum Zugriff auf
den Web-Service
Hilfsklasse um ein Port-Objekt für den WebService zu erhalten
Lokale Stellvertreterklasse, die alle Methodenaufrufe an den Web-Service weiterleitet
Tabelle 20: Von AXIS erzeugte Client-Klassen
Die hier automatisch erzeugte Schnittstelle WebAoccounting_PortType entspricht im Prinzip
der ursprünglichen, von Hand definierten, Web-Service-Schnittstelle, was ja auch dem Sinn
der Verwendung von Web-Services entspricht. Nur wird diese Schnittstelle durch die Namensraumzuordnung einem anderen Java-Paket zugeordnet und es darf wegen der Verbindung mit AXIS auch keine Verwechslung erfolgen.
6.4.5. Client-Implementierung
Die erzeugten Klassen und Schnittstellen können nun verwendet werden, um eine ClientAnwendung im Sinne der Logik der Buchhaltung zu implementieren.
6.4.5.1. Proxy-Klasse
Die Proxy-Klasse verwendet eine Instanz der erzeugten Klasse WebServiceLocator um eine
Verbindung mit dem Anbieter des Web-Service herzustellen. Die SOAP-Adresse des WebService braucht nicht mehr explizit angegeben zu werden, da sie bei der Generierung durch
die WSDL-Datei vorgegeben wurde.
Durch den Aufruf der Methode getWebAccountingPort() erhält man einen Port für den
Web-Service, der über die Web-Service-Schnittstelle WebAccounting angesprochen wird,
und durch eine Instanz der Stellvertreterklasse WebAccountingBindingStub realisiert wird.
Durch diesen Port können nun die Methoden des entfernten Web-Service aus Java heraus aufgerufen werden.
Allerdings entspricht ja die Web-Service-Schnittstelle nicht mehr der ursprünglichen Buchhaltungsschnittstelle, weshalb eine Adapter-Klasse für den Port notwendig ist, die wieder auf
der ursprünglichen Buchhaltungsschnittstelle basiert und als Service-Klasse instanziert wird.
- 229 -
Java-Enterprise - Web-Services
package de.fhr.vs_iw.webservice;
public final class WebServiceProxy implements IAccountingProxy {
public WebServiceProxy() {}
public void close() {}
}
public IAccountingService connect(String[] args) throws IOException {
try {
WebAccountingServiceLocator locator
= new WebAccountingServiceLocator();
WebAccounting port = locator.getWebAccountingPort();
return new AccountingService(port);
} catch (Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
Code 137: Class WebServiceProxy
Mit dem Port-Objekt wird eine Instanz der Service-Klasse gebildet, die dann von der
connect()-Methode der Proxy-Klasse zurückgegeben wird.
6.4.5.2. Service-Klasse
Die Methoden der Service-Klasse rufen die entsprechenden Methoden des Web-Service bzw.
des Stellvertreter-Objekts für den Web-Service (Port) auf. Dabei müssen vor allem die Ausnahmen, für die aus der WSDL-Beschreibung neue Klassen generiert wurden, auf die originalen Ausnahmen aus dem Buchhaltungsmodell zurückgeführt werden.
package de.fhr.vs_iw.webservice;
public final class AccountingService implements IAccountingService {
private final Map<Integer, AccountingEntry> accountingEntries
= new HashMap<Integer, AccountingEntry>();
private Map<Integer, Account> accounts = new HashMap<Integer, Account>();
private final WebAccounting port;
public AccountingService(WebAccounting port) {
this.port = port;
}
public Double calculateBalance(Integer number)
throws IOException, AccountingException {
try {
return port.calculateBalance(number);
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
} catch (de.fhr.vs_iw.webservice.axis.AccountingException e) {
throw new AccountingException(e.getMessage1());
}
}
- 230 -
Java-Enterprise - Web-Services
...
public IAccount getAccountByNumber(Integer number)
throws IOException, AccountingException {
try {
port.checkAccountNumber(number);
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
} catch (de.fhr.vs_iw.webservice.axis.AccountingException e) {
throw new AccountingException(e.getMessage1());
}
Account account = accounts.get(number);
if (account == null) {
account = new Account(number, port);
accounts.put(number, account);
}
return account;
}
public Set<Integer> getAccounts() throws IOException {
try {
Integer[] ar = port.getAccounts();
HashSet<Integer> set = new HashSet<Integer>();
if (ar != null) {
for (Integer i : ar) {
set.add(i);
}
}
return set;
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
}
}
...
}
Code 138: Class AccountingService (Web-Services)
Die Auflistung der Kontonummern bzw. Buchungs-IDs muß manuell von einem int-Array
wieder in einen Set umgewandelt werden.
Damit von der flachen Web-Service-Schnittstelle ausgehend wieder eine objektorientierte
Verwendung des Systems möglich ist, werden die entsprechenden Methoden der WebService-Schnittstelle durch Konten- und Buchungsobjekte adaptiert.
Die Service-Methoden getAccountByNumber() bzw. getAccountingEntryById() erzeugen
mit dem verwendeten Web-Service-Port jeweils ein Adapter-Objekt für die angegebene Kontonummer bzw. Buchungs-ID. Diese Adapter-Objekte werden von der Service-Klasse in
HashMaps verwaltet, damit nicht unnötig viele Instanzen erzeugt werden.
6.4.5.3. Konto-Klasse
Bei der Konto-Klasse werden auch nur die passenden Methoden des Web-Service-Port aufgerufen. Jedes Objekt der Klasse kennt dabei seine Kontonummer, die bei jedem Web-ServiceAufruf mit übergeben wird.
- 231 -
Java-Enterprise - Web-Services
package de.fhr.vs_iw.webservice;
public final class Account implements IAccount {
private final Integer number;
private final WebAccounting port;
protected Account(Integer number, WebAccounting port) {
this.number = number;
this.port = port;
}
public Set<Integer> getCreditEntries() throws IOException {
try {
Integer[] ar = port.getCreditEntries(number);
HashSet<Integer> set = new HashSet<Integer>();
for (Integer i : ar) {
set.add(i);
}
return set;
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
}
}
public Integer getNumber() {
return number;
}
public AccountType getType() throws IOException {
try {
return AccountType.getAccountTypeById(port.getType(number));
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
}
}
public void setType(AccountType type) throws IOException {
try {
port.setType(number, type.getId());
} catch (de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
}
}
}
...
Code 139: Class Account (Web-Service)
Auch bei der Konto-Klasse werden die Auflistungen der Soll- bzw. Haben-Buchungen manuell wieder zu Sets umgeformt.
Zusätzlich muß der Kontotyp speziell behandelt werden, da Elemente einer Java-Enumeration
natürlich nicht direkt übertragen werden können. Dafür werden die Methoden getId() und
getAccountTypeId() der Klasse AccountType verwendet, wodurch der Kontotyp als numerischer Wert übertragen wird.
- 232 -
Java-Enterprise - Web-Services
6.4.5.4. Buchungs-Klasse
Für die Buchungs-Klasse gilt natürlich dasselbe wie für die Konto-Klasse. Alle Methodenaufrufe werden unter Verwendung der Buchungs-ID lediglich an den Web-Service-Port weitergegeben und die Ausnahmen entsprechend umgewandelt.
package de.fhr.vs_iw.webservice;
public final class AccountingEntry implements IAccountingEntry {
private final Integer id;
private final WebAccounting port;
protected AccountingEntry(final Integer id, final WebAccounting port) {
this.id = id;
this.port = port;
}
public Double getAmount() throws IOException {
try {
return port.getAmount(id);
} catch (final de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
}
}
public Integer getId() {
return id;
}
public void setCreditAccount(final Integer creditAccount)
throws IOException, AccountingException {
try {
port.setCreditAccount(id, creditAccount);
} catch (final de.fhr.vs_iw.webservice.axis.IOException e) {
throw new IOException(e.getMessage1());
} catch (final de.fhr.vs_iw.webservice.axis.AccountingException e) {
throw new AccountingException(e.getMessage1());
}
}
...
}
Code 140: Class AccountingEntry (Web-Service)
6.4.6. Fazit
Die Verwendung von Web-Services zur Realisierung eines Verteilten Systems kann den Entwicklungsaufwand durch die vielfachen Möglichkeiten der automatisierten Codegenerierung
einer Anwendung in den unterschiedlichsten Programmiersprachen stark vereinfachen.
Der Nachteil besteht allerdings darin, daß 90% der Datenübertragung Overhead durch die
Verwendung von XML sind, und deshalb die Performance von Web-Services eher kritisch zu
betrachten ist (siehe Anhang D).
- 233 -
Buchhaltung Mobil - Übersicht
7. Buchhaltung Mobil
7.1. Übersicht
Da dieses Tutorial unter der Thematik "Verteilte Systeme" steht, sollte auch die Betrachtung
mobiler Endgeräte nicht fehlen.
7.1.1. Pocket-PC
Konkret soll die mögliche Anbindung eines Pocket-PC an die bestehenden Buchhaltungssysteme veranschaulicht werden. Als Testgerät kommt ein HP iPAQ hx2750 mit Intel ARMProzessor zum Einsatz.
Das Gerät hat die folgenden Hardware-Spezifikationen:






Taktung: 624 MHz
Display: 320x240 Pixel, 256000 Farben
ROM-Speicher: 128MB
RAM-Speicher: 128MB
Wireless LAN und Bluetooth
Kartenslots für Compact-Flash II und SD-Karten
7.1.2. Windows-Mobile
Das Betriebssystem des Pocket-PC ist Windows Mobile 2003 (Version 4.21). Es hält sich
soweit wie möglich und praktikabel an die von Desktop Rechnern her bekannte Windows
API. Normalerweise werden die Programme dafür in C bzw. C++ erstellt, speziell für den
Pocket-PC compiliert, und können dort dann nativ ablaufen. Microsoft stellt zu diesem Zweck
die "eMbedded Visual Tools" [60] und den "Pocket-PC 2003 SDK" [61] zur Verfügung.
Außerdem gibt es eine "eMbedded Visual Basic Runtime" und das "Microsoft .NET Compact
Framework", unter anderem zur Integration in "Visual Studio .NET", um Anwendungen in
diesen beiden Programmiersprachen für den Pocket-PC entwickeln zu können.
Auch von anderen Herstellern gibt es Entwicklungsumgebungen und Tools für Pocket-PC
Anwendungen. [59]
- 234 -
Buchhaltung Mobil - Übersicht
7.1.3. Java Micro Edition
Mit der Java Micro Edition (J2ME) [63] wird eine Java-Umgebung für mobile Geräte im allgemeinen, und damit auch für den Pocket-PC im speziellen, spezifiziert. Diese API ist in
Konfigurationen und Profile unterteilt, wobei im wesentlichen zwei Standardkonfigurationen
unterschieden werden [62]:


CDC - Connected Device Configuration - Die allgemeine Konfiguration bzw. Spezifikation für mobile Geräte mit 32-bit Prozessor und ca. 2.5 MB Speicher für die Java
Umgebung (CVM – Connected Device Configuration Virtual Machine). Die Umgebung zielt auf Kompatibilität mit der API und den Werkzeugen der Java Standard Edition (J2SE) ab.
CLDC - Connected Limited Device Configuration - Eine sehr schlanke Java Konfiguration für Geräte mit nur 128kB bis 512 kB Speicher, die nicht J2SE-konform sein
muß.
CDC-Anwendungen sind durch die angestrebte Kompatibilität mit der J2SE auch auf jedem
Desktop Rechner ablauffähig. Umgekehrt wird der von CDC unterstützte Umfang der
J2SE-API durch das verwendete Profil definiert:



Foundation Profile - Keine Unterstützung für Benutzeroberflächen
Personal Basic Profile - Unterstützung für einfache AWT-Komponenten
Personal Profile - Vollständige Unterstützung von AWT und Applets
Die Funktionalitäten dieser drei Profile bauen aufeinander auf und können durch spezifische
Zusatzprofile erweitert werden:



RMI Optional Package - Remote Method Invocation aus dem java.rmi Paket
JDBC Optional Package - Datenbankzugriff mit der JDBC 3.0 API
Mobile Media Optional Package - Zugriff auf Audio und Video Dienste
Schließlich gibt es für mobile Geräte zwei brauchbare kommerzielle Implementierungen einer
Virtuellen Java Maschine nach diesen Spezifikationen [62]:


IBM J9 JVM
NSICOM CrEme JVM
7.1.4. Mysaifu JVM
Neben den kommerziellen JVMs gibt es aber eine freie Implementierung unter der GPL, die
Mysaifu JVM von "freebeans", die unter
http://www2s.biglobe.ne.jp/~dat/java/project/jvm/index_en.html
heruntergeladen werden kann und für die Buchhaltungsanwendung verwendet wird. Sie hält
sich nicht an die J2ME-Spezifikation, da sie ca. 10MB Speicher zur Ausführung benötigt, hat
aber zum Ziel die API der J2SE (Java 2 Standard Edition) möglichst vollständig auf dem Pocket-PC zu unterstützen [64].
- 235 -
Buchhaltung Mobil - Installation
7.2. Installation
Der erste Schritt für die Buchhaltung auf dem Pocket-PC ist die Installation der JVM.
7.2.1. Laufzeitumgebung
Nach dem Herunterladen und entpacken der Mysaifu JVM (jvm.Release.zip) wird die in
dem Archiv enthaltene cab-Datei (jvm.Release.cab) auf den Pocket-PC kopiert (ActiveSync, WLAN, etc.). Durch das Ausführen der cab-Datei (antippen) wird die JVM im Verzeichnis \Programme\Mysaifu JVM installiert. Deinstalliert werden kann die JVM unter
Einstellungen -> Programme entfernen.
7.2.2. Aufruf der JVM
Aufgerufen wird die JVM selbst direkt im Installationsverzeichnis
\Programme\Mysaifu JVM\bin\jre
durch Ausführen (antippen) von jvm.exe.
In der Hauptansicht wird nun eine Java-Klasse oder
ein jar-Archiv (vs_iw_fibu_pocketpc_3.3.jar)
ausgewählt, das gestartet werden soll.
Außerdem kann man angeben ob die Konsole angezeigt werden soll, und es können einige Optionen für
das Programm eingestellt werden.
Abbildung 94: Haupansicht der Mysaifu JVM
7.2.3. Entwicklungsumgebung
Da sich die Mysaifu JVM an die J2SE-Spezifikationen hält, können zur Entwicklung die selben Werkzeuge (Compiler, usw.) wie auch auf Desktop-PCs verwendet werden, ohne daß
spezielle Maßnahmen zur Vorbereitung der Programme für den Pocket-PC notwendig sind.
Von dem her wurde die Buchhaltungsanwendung für den Pocket-PC "ganz normal" in Eclipse
entwickelt. Wichtig ist nur, daß man keine API's und Klassen der J2SE verwendet, die auf
dem Pocket-PC nicht verfügbar sind.
- 236 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3. Buchhaltungsoberfläche
Für die Buchhaltung auf dem Pocket-PC wurde eine entsprechende Anwendung mit
AWT-Oberfläche erstellt, die unter der Mysaifu-JVM lauffähig ist.
7.3.1. Verfügbare Proxy-Module
Die Oberfläche dient wiederum nur als Client, der auf vorhandene Buchhaltungssysteme
zugreifen soll. Allerdings wird ein fester Satz Proxy-Klassen vorgegeben, die alle direkt im
jar-Archiv der Anwendung enthalten sind, und es wird nicht nach beliebigen, auf dem PocketPC vorhandenen, Proxy-Klassen gesucht.
Die verfügbaren Proxy-Klassen umfassen:





dummy
exception
notimpl
local
socket
Dummy-Klasse, die alle Methoden implementiert
Dummy-Klasse, bei der jede Methode eine Ausnahme wirft
Dummy-Klasse, bei der jede Methode NotImplemented wirft
Lokale Datenhaltung (siehe 4.2) (Standard-Einstellung)
Socket-Verbindung mit Host und Port (siehe 5.2)
Die gewünschte Proxy-Klasse wird mit ihren notwendigen Parametern auf der Kommandozeile angegeben bzw. vor dem Aufruf der JVM im Feld für die
Kommandozeilenargumente eingegeben.
Außerdem kann ein CLASSPATH für weitere nötige
Bibliotheken definiert, und das aktuelle Verzeichnis
eingestellt werden.
Abbildung 95: Allgemeine Optionen der Mysaifu JVM
- 237 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3.2. Aufbau der Oberfläche
Nach dem Starten der Anwendung wird über die
angegebene Proxy-Klasse die Service-Klasse geladen und im Statusbereich die erfolgreiche Ausführung oder eine aufgetretene Ausnahme angezeigt.
Mit den zwei Schaltflächen "Konto" bzw. "Buchung" am oberen Fensterrand kann die entsprechende Ansicht ausgewählt werden, die dann im
freien mittleren Bereich eingeblendet wird.
Windows Mobile typisch wird das Menü der Anwendung am unteren Bildschirmrand angezeigt.
Abbildung 96: Erste Ansicht der Pocket-Buchhaltung
7.3.2.1. Konto und Buchungsliste
Ja nach Auswahl wird eine Liste der vorhandenen Konten bzw. Buchungen angezeigt. Durch
das Selektieren eines Listeneintrags werden die genaueren Daten im Statusbereich eingeblendet. Mit den Schaltflächen neben der Liste können die entsprechenden Aktionen aufgerufen
werden.
Abbildung 97: Ansicht der Konten und Buchungen der Poket-Buchhaltung
- 238 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3.2.2. Konto anlegen oder ändern
Zum Anlegen eines neuen Kontos werden die entsprechenden Eingabefelder für die Daten
angezeigt. Beim Ändern eines Kontos wird dieselbe Ansicht verwendet, nur daß die Daten des
zu ändernden Kontos vorher eingetragen werden.
Abbildung 98: Pocket-Buchhaltung: Konto anlegen oder ändern
7.3.2.3. Buchung anlegen oder ändern
Bei den Buchungen verhält es sich analog zu den Konten. In der Ansicht werden die Eingabefelder entweder leer oder mit den Daten der zu ändernden Buchung angezeigt.
Abbildung 99: Pocket-Buchhaltung: Buchung anlegen oder ändern
- 239 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3.2.4. Konto oder Buchung löschen
Auch für das Löschen von Konten und Buchungen wird eine gemeinsame Ansicht verwendet,
in der nur die jeweiligen Daten eingetragen werden.
Abbildung 100: Pocket-Buchhaltung: Konto oder Buchung löschen
7.3.2.5. Konto-Details
Für Konten gibt es noch eine weitere Ansicht, für die separate Auflistung der Soll- und Haben-Buchungen eines Kontos (getCreditAccounts(), getDebitAccounts()). Außerdem sei
hier das Aussehen der Anwendung auf einem Desktop-PC dargestellt.
Abbildung 101: Pocket-Buchhaltung: Konto-Details
- 240 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3.3. Ausnahmen
Wenn bei einem Aufruf einer Buchhaltungsmethode eine Ausnahme, ein Problem oder ein
anderes Ereignis auftritt, wird dieses im Statusbereich dargestellt und passend farblich hinterlegt (siehe 3.4.2).
Abbildung 102: Pocket-Buchhaltung: Einblendungen im Statusbereich
7.3.4. Menü
Das Menü der Anwendung wird am unteren Bildschirmrand angezeigt. Das Symbol ganz
links gehört jedoch nicht zur Anwendung, sondern dient zum Ausklappen der "Tastatur" von
Windows Mobile.
7.3.4.1. Schnittstellen-Menü
Über das Schnittstellen-Menü können einige Verwaltungsfunktionen aufgerufen werden.
Abbildung 103: Pocket-Buchhaltung: Schnittstellen-Menü

Neu verbinden - Schließt die gerade geladene Service-Klasse (close()) und lädt sie
neu (conect()). Das ist nützlich falls sich der Pocket-PC in den StandBy-Modus
schaltet und dadurch die Verbindung zu Buchhaltungssystem abreißt.

Daten erzeugen - Benutzt die Utilities-Klasse, um Testdaten zu erzeugen
(siehe 3.7.1.1)

Beenden - Schließt die Anwendung
- 241 -
Buchhaltung Mobil - Buchhaltungsoberfläche
7.3.4.2. Hilfe-Menü
Im Hilfe-Menü kann eine Info-Ansicht aufgerufen
werden, die einige Fakten zu der Anwendung wiedergibt, und eine Auflistung der verfügbaren ProxyKlassen.
Abbildung 104: Pocket-Buchhaltung: Hilfe-Menü
Abbildung 105: Pocket-Buchhaltung: Info-Ansicht und Auflistung der Proxy-Klassen
- 242 -
Buchhaltung Mobil - Anbindung an die vorhandenen Systeme
7.4. Anbindung an die vorhandenen Systeme
Wie bereits mehrfach erwähnt soll die Buchhaltungsoberfläche auf dem Pocket-PC über die
entsprechenden Proxy-Klassen an die vorhandenen Module des Buchhaltungssystems angebunden werden.
7.4.1. Lokale und Dummy-Klassen
Die Dummy-Klassen und die lokale Datenhaltung können direkt wie auf einem Desktop-PC
benutzt werden, dienen jedoch mehr zum Testen der Oberfläche.
7.4.2. An das Socket-Modul
Auch das bereits vorhandene Socket-Modul kann direkt verwendet werden, um die Verbindung zu einem Buchhaltungs-Server herzustellen, da die Funktionalitäten und Klassen aus
den dafür notwendigen Paketen java.io und java.net vollständig vorhanden sind.
7.4.3. Über RMI
Die Verwendung von RMI ist im ersten Ansatz nicht möglich, da die Mysaifu JVM diese API
nicht unterstützt und die notwendigen Klassen nicht implementiert wurden.
7.4.4. Andere Technologien
Auch die Verwendung von anderen Technologien ist auf dem Pocket-PC mit der MysaifuJVM nicht möglich, da deren Anforderungen derzeit von der Mysaifu JVM nicht erfüllt werden.
7.5. Erfahrungen und Probleme
Die Programmierung der Oberfläche konnte wirklich direkt so erfolgen, wie für einen Desktop-Rechner auch. Nur bei der Verwendung des List-Steuerelements gab es einen Unterschied während der Programmausführung. Auf dem Pocket-PC ist das programmgesteuerte
Auswählen eines Elements der Liste nicht möglich.
Ansonsten ist die Anwendung auf dem Pocket-PC genauso brauchbar wie auf dem DesktopPC. Die Mysaifu JVM könnte wohl auch im produktiven Einsatz für mobile Anwendungen
verwendet werden.
- 243 -
Schlußwort
Schlußwort
Dieses Tutorial führt schrittweise von eigenen Implementierungen benötigter Funktionalitäten
hin zur Verwendung von umfangreichen Frameworks. Dabei dient das Thema Buchhaltung
als Ausgangspunkt, der den Einstieg erleichtern soll, und als gemeinsame Basis.
Dabei hat sich gezeigt, daß eben der Einstieg durch die Vorgaben aus dem Buchhaltungsmodell mit eigenen Implementierungen sehr gut möglich war, diese Vorgaben allerdings bei der
Verwendung zusammen mit den verschiedenen Technologien zum Teil auch hinderlich waren
und manchmal unnötige Umstände verursacht haben.
Bei der Anwendungsentwicklung muß also im Vorfeld gezielt darauf geachtet werden, welche
Technologien zum Einsatz kommen sollen, und die Anwendung sollte dann entsprechend auf
deren Verwendung hin entwickelt werden, um deren Vorteile vollständig nutzen zu können
und um unnötige Probleme zu vermeiden.
Vor allem der neue Enterprise JavaBeans 3.0 Standard kann die Anwendungsentwicklung aus
der Sicht des Programmierers erheblich vereinfachen, wobei aber die Kompatibilität mit verschiedenen (Versionen von) Applikations-Servern zum Teil nicht einfach herzustellen ist.
- 244 -
Verzeichnisse
Verzeichnisse
Abkürzungsverzeichnis
AAC
API
AJP
AS
AWT
BMP
BPEL
BPM
CDC
CIFS
CLDC
CMP
CORBA
CVM
DAO
DBMS
DD
DII
DOM
DSI
DTO
EJB
EJB3
ERM
HQL
HSQLDB
HTTP
HTTPS
GIOP
IDE
IDL
IDLJ
IIOP
IMAP
IoC
IOR
J2ME
J2SE
J2EE
JAAS
JAF
JAR
JAX-RPC
JAXB
JAXR
JBPM
JCA
Application Client Container
Application Programming Interface
Apache JServ Protocol
Application Server [45]
Abstract Windowing Toolkit
Bean-Managed-Persistence
Business Process Execution Language [43]
Business Process Management [44]
Connected Device Configuration
Common Internet File System
Connected Limited Device Configuration
Container-Managed-Persistence
Common Object Request Broker Architecture [79]
Connected Device Configuration Virtual Machine
Data Access Object (Entity-Bean)
Database Management System
Deployment-Descriptor
Dynamic Invokation Interface
Document Object Model
Dynamic Skeleton Interface
Data Transfer Object
Enterprise JavaBeans [45]
Endung für Enterprise Java Archive, Version 3.0
Entity-Relationship-Model
Hibernate Query Language
Hypersonic-SQL-Database-Engine [14]
Hypertext Transfer Protocol
Hypertext Transfer Protocol Secure
General Inter-ORB Protocol [79]
Integrated Development Environment [30]
Interface Definition Language
IDL-to-Java-Compiler
Internet Inter-ORB Protocol [79]
Internet Message Access Protocol
Inversion of Control
Interoperable Object Reference
Java 2 Platform, Micro Edition [63]
Java 2 Platform, Standard Edition [01]
Java 2 Platform, Enterprise Edition [01]
Java Authentication and Authorization Service [07]
Java Beans Activation Framework [49]
Java Archive
Java-API for XML-Based RPC [55]
Java Architecture for XML Binding [69]
Java-API for XML-Registries [54]
JBoss Business Process Management
J2EE Connector Architecture [56]
- 245 -
Verzeichnisse
JCP
JDBC
JDK
JEMS
JMS
JMX
JNDI
JPA
JRMP
JSF
JSP
JSR
JTA
JTS
JVM
JWSDP
MDA
MIME
MOM
MVC
NFS
NNTP
OID
OMG
OTS
PAR
PCMCIA
PDL
POA
POJI
POJO
POP3
RMI
RPC
SAX
SDK
SEI
SMB
SMTP
SOA
SOAP
StAX
UIL2
URL
VM
WAR
W3C
WS
WSE
WSDK
WSDL
Java Community Process
Java Database Connectivity
Java Development Kit [01]
JBoss Enterprise Middleware System
Java Messaging Service [15]
Java Management Extensions [16]
Java Naming and Directory Interface [17]
Java Persistence API
Java Remote Method Protocol
Java Server Faces
Java Server Page
Java Specification Request
Java Transaction API [51]
Java Transaction Service [52]
Java Virtual Machine [01]
Java Web-Services Developer Pack
Model Driven Architecture
Multipurpose Internet Mail Extensions
Message Oriented Middleware [15]
Model-View-Controller
Network File System
Network News Transfer Protocol
Object Identifier
Object Management Group [79]
Object Transaction Service
Persistence Archive
People Can't Memorize Computer Industry Acronyms
Process Definiton Language
Portable Object Adapter
Plain Old Java Interface
Plain Old Java Object
Post Office Protocol, Version 3
Remote Method Invocation [34]
Remote Procedure Call
Simple API for XML
Software Development Kit
Service Endpoint Interface
Server Message Block
Simple Mail Transfer Protocol
Service-oriented Architecture
Simple Object Access Protocol [26]
Streaming API for XML
Unified Invocation Layer Version 2
Uniform Resource Locator
Virtual Machine
Web (Application) Archive
World Wide Web Consortium
Web-Services [24]
Web-Services Enhancements
Web-Services Development Kit
Web-Service Description Language
- 246 -
Verzeichnisse
WSTools
XML
XML-RPC
XOP
JBoss-Web-Service-Tools [23]
Extensible Markup Language
Extensible Markup Language - Remote Procedure Call
XML-binary Optimized Packaging
- 247 -
Verzeichnisse
Abbildungsverzeichnis
Abbildung 1: Objektmodell der Buchhaltung ............................................................................ 4
Abbildung 2: Schnittstellenmodell der Buchhaltung ................................................................. 5
Abbildung 3: Vekettung der Module des Buchhaltungssystems ............................................. 15
Abbildung 4: Mögliche Szenarien der Verkettung von Implementierungen ........................... 16
Abbildung 5: Hauptansicht der Buchhaltungsoberfläche......................................................... 17
Abbildung 6: Schnittstellen-Menü ........................................................................................... 18
Abbildung 7: Service-Klasse laden .......................................................................................... 19
Abbildung 8: Hinweisanzeige bei der Auswahl der Proxy-Klasse .......................................... 19
Abbildung 9: Kontenliste ......................................................................................................... 21
Abbildung 10: Neues Konto anlegen ....................................................................................... 21
Abbildung 11: Konto bearbeiten .............................................................................................. 22
Abbildung 12: Konto löschen .................................................................................................. 22
Abbildung 13: Buchungsliste ................................................................................................... 23
Abbildung 14: Neue Buchung erfassen.................................................................................... 23
Abbildung 15: Buchung bearbeiten.......................................................................................... 24
Abbildung 16: Buchung löschen .............................................................................................. 24
Abbildung 17: Liste der Haben- und Soll-Buchungen eines Kontos ....................................... 25
Abbildung 18: Der Saldo eines Kontos.................................................................................... 25
Abbildung 19: Nachrichtenfenster bei Ausnahmen ................................................................. 26
Abbildung 20: Statusleiste grau ............................................................................................... 26
Abbildung 21: Statusleiste grün ............................................................................................... 27
Abbildung 22: Statusleiste gelb................................................................................................ 27
Abbildung 23: Statusleiste rot .................................................................................................. 27
Abbildung 24: Einblendung bei nicht implementierten Methoden (einzelne Werte) .............. 27
Abbildung 25: Einblendung bei nicht implementierten Methoden (ganze Auflistung)........... 27
Abbildung 26: Liste der laufenden Vorgänge .......................................................................... 33
Abbildung 27: Ansicht-Menü................................................................................................... 33
Abbildung 28: Threading-Menü............................................................................................... 35
Abbildung 29: Ablauf im Modus Singlethreaded .................................................................... 36
Abbildung 30: Ablauf im Modus Synchron einreihen ............................................................. 37
Abbildung 31: Ablauf im Modus Asynchron und Multithreaded ............................................ 38
Abbildung 32: Ablauf beim Abbruch eines Vorgangs............................................................. 39
Abbildung 33: Test-Menü ........................................................................................................ 41
Abbildung 34: Funktions-Test-Dialog ..................................................................................... 41
Abbildung 35: Geschwindigkeits-Test-Dialog......................................................................... 42
Abbildung 36: Threadsicherheits-Testdialog ........................................................................... 42
Abbildung 37: Logging-Menü ................................................................................................. 43
Abbildung 38: Look & Feel Menü........................................................................................... 46
Abbildung 39: Benutzeroberfläche im "Metal" Look & Feel .................................................. 46
Abbildung 40: Hilfe-Menü....................................................................................................... 47
Abbildung 41: Verzeichnis-Auswahl-Dialog........................................................................... 48
Abbildung 42: Eclipse-Dialog "New Project".......................................................................... 52
Abbildung 43: Eclipse-Dialog "New Java Project" ................................................................. 53
Abbildung 44: Eclipse Package Explorer................................................................................. 53
Abbildung 45: Eclipse-Dialog "New Folder" .......................................................................... 54
Abbildung 46: Eclipse Package Explorer................................................................................. 54
Abbildung 47: Eclipse-Dialog "Import" - Select ..................................................................... 55
Abbildung 48: Eclipse-Dialog "Import" - File system............................................................. 56
Abbildung 49: Eclipse Package Explorer................................................................................. 56
- 248 -
Verzeichnisse
Abbildung 50: Eclipse-Dialog "Properties for VS_IW_FiBu" ................................................ 57
Abbildung 51: Eclipse-Dialog "JAR Selection" ...................................................................... 57
Abbildung 52: Eclipse Package Explorer................................................................................. 58
Abbildung 53: Eclipse-Dialog "Run" - Empty......................................................................... 58
Abbildung 54: Eclipse-Dialog "Run" - Java Application ........................................................ 59
Abbildung 55: Eclipse-Dialog "New Java Package" ............................................................... 60
Abbildung 56: Eclipse Package Explorer................................................................................. 60
Abbildung 57: Eclipse-Dialog "Implemented Interfaces Selection"........................................ 61
Abbildung 58: Eclipse-Dialog "New Java Class" .................................................................... 61
Abbildung 59: Eclipse Package Explorer................................................................................. 62
Abbildung 60: Eclipse-Gesamtansicht des neu erstellten Projekts .......................................... 62
Abbildung 61: ERM-Modell 1 für die Buchhaltung mit Hibernate ....................................... 116
Abbildung 62: ERM-Modell 2 für die Buchhaltung mit Hibernate ....................................... 120
Abbildung 63: Datenpakte ..................................................................................................... 124
Abbildung 64: Anfrage-Antwort............................................................................................ 124
Abbildung 65: Entfernter Methodenaufruf ............................................................................ 124
Abbildung 66: Eclipse Package Explorer............................................................................... 125
Abbildung 67: Socket-Anfrage-Antwort-Klassen.................................................................. 129
Abbildung 68: Schematischer Ablauf von RMI..................................................................... 143
Abbildung 69: RMI-Kommunikations-Architektur ............................................................... 143
Abbildung 70: Nachladen von Java-Klassen ......................................................................... 151
Abbildung 71: Java 2 Enterprise Komponenten-Model......................................................... 170
Abbildung 72: Windows Umgebungsvariablen ..................................................................... 174
Abbildung 73: Paket-Auswahl bei der JBoss-Installation...................................................... 175
Abbildung 74: JBoss-Verzeichnisstruktur ............................................................................. 176
Abbildung 75: Konsolenfenster eines laufenden JBoss ......................................................... 181
Abbildung 76: Hypersonic Datenbank-Manager ................................................................... 185
Abbildung 77: Eclipse-Dialog "New Project"........................................................................ 187
Abbildung 78: Eclipse-Dialog "Select a JBoss configuration" .............................................. 188
Abbildung 79: Eclipse-Dialog "New Server" - Define .......................................................... 188
Abbildung 80: Eclipse-Dialog "New Server" - Create........................................................... 189
Abbildung 81: Eclipse Package Explorer - Servers ............................................................... 189
Abbildung 82: Eclipse Servers-Ansicht ................................................................................. 190
Abbildung 83: Eclipse-Console mit laufendem JBoss ........................................................... 190
Abbildung 84: Lebenszyklus einer zustandslosen Session-Bean........................................... 193
Abbildung 85: Lebenszyklus einer zustandsbehafteten Session-Bean .................................. 193
Abbildung 86: Interceptor-Aufruf.......................................................................................... 194
Abbildung 87: Lebenszyklus einer Entity-Bean .................................................................... 198
Abbildung 88: Ablauf des Entity-Bean-Prinzips bei einem Kontenobjekt ............................ 199
Abbildung 89: Beziehung der Entity-Beans........................................................................... 200
Abbildung 90: Lebenszyklus einer Message-Driven-Bean.................................................... 203
Abbildung 91: Nachrichtenschlange ...................................................................................... 204
Abbildung 92: Anmelde-Versende-System ........................................................................... 204
Abbildung 93: MBean-Lebenszyklus..................................................................................... 206
Abbildung 94: Haupansicht der Mysaifu JVM ...................................................................... 236
Abbildung 95: Allgemeine Optionen der Mysaifu JVM........................................................ 237
Abbildung 96: Erste Ansicht der Pocket-Buchhaltung .......................................................... 238
Abbildung 97: Ansicht der Konten und Buchungen der Poket-Buchhaltung ........................ 238
Abbildung 98: Pocket-Buchhaltung: Konto anlegen oder ändern ......................................... 239
Abbildung 99: Pocket-Buchhaltung: Buchung anlegen oder ändern ..................................... 239
Abbildung 100: Pocket-Buchhaltung: Konto oder Buchung löschen .................................... 240
- 249 -
Verzeichnisse
Abbildung 101: Pocket-Buchhaltung: Konto-Details ............................................................ 240
Abbildung 102: Pocket-Buchhaltung: Einblendungen im Statusbereich............................... 241
Abbildung 103: Pocket-Buchhaltung: Schnittstellen-Menü .................................................. 241
Abbildung 104: Pocket-Buchhaltung: Hilfe-Menü ................................................................ 242
Abbildung 105: Pocket-Buchhaltung: Info-Ansicht und Auflistung der Proxy-Klassen....... 242
- 250 -
Verzeichnisse
Codeverzeichnis
Code 1: Interface IAccount ........................................................................................................ 6
Code 2: Enumeration AccountType........................................................................................... 7
Code 3: Interface IAccountingEntry .......................................................................................... 7
Code 4: Interface IAccountingService ....................................................................................... 8
Code 5: Interface IAccountingProxy.......................................................................................... 9
Code 6: Interface IExtendedAccountingService ........................................................................ 9
Code 7: Class AccountingException........................................................................................ 10
Code 8: "Verpacken" einer beliebigen Exception als IOException ......................................... 11
Code 9: Class NotImplemented (Exception)............................................................................ 11
Code 10: Methoden loadProxy(…) der Utilities-Klasse .......................................................... 12
Code 11: Class NoInterfaceException ..................................................................................... 13
Code 12: Klassenvariable ARGS ............................................................................................. 20
Code 13: Ausgabe des Stacktrace mit Hilfe von StringWriter und PrintWriter ...................... 26
Code 14: Class AbstractRowTableModel<T> ......................................................................... 28
Code 15: Class AccountTableModel........................................................................................ 29
Code 16: Class AccountingEntryTableModel.......................................................................... 29
Code 17: Class RunnableServiceTask...................................................................................... 30
Code 18: Class MethodCallTask (Adapterschicht).................................................................. 30
Code 19: Class AbstractSwingTask ......................................................................................... 31
Code 20: Class Caller (GUI-Vorgang)..................................................................................... 31
Code 21: Interface SwingTaskListener .................................................................................... 32
Code 22: Class ThreadingService ............................................................................................ 34
Code 23: Verwendung von ExecutorService im ThreadingService......................................... 35
Code 24: Class Logger ............................................................................................................. 43
Code 25: Ermittlung des Klassennamens und des Threadnamens aus dem Stacktrace ........... 44
Code 26: In Spalten formatierte Log-Ausgabe ........................................................................ 44
Code 27: Ermittlung des Methodennamens aus dem Stacktrace ............................................. 44
Code 28: Logger-Ausgabe der aufgerufenen Methoden .......................................................... 45
Code 29: Look & Feel Initialisierung ...................................................................................... 45
Code 30: Utilities-Methode setLookAndFeel()........................................................................ 47
Code 31: Class ClassFileLoader .............................................................................................. 49
Code 32. Interface ClassFileLoaderListener............................................................................ 49
Code 33: Interface ClassFileReflection.................................................................................... 50
Code 34: Class ClassFileDecoder ............................................................................................ 50
Code 35: Method classFound() in ServiceSelectDialog .......................................................... 51
Code 36: Class ClassFileEntry................................................................................................. 51
Code 37: HashMaps für Konten- und Buchungs-Objekte ....................................................... 64
Code 38: HashMaps für Soll- und Haben-Buchungen............................................................. 64
Code 39: Class AccountingService (Lokal) ............................................................................. 67
Code 40: Class Account (Lokal) .............................................................................................. 68
Code 41: Class AccountingEntry (Lokal) ................................................................................ 70
Code 42: Class LocalProxy ...................................................................................................... 70
Code 43: Class Account (Serialize) ......................................................................................... 71
Code 44: Class AccountingEntry (Serialize) ........................................................................... 72
Code 45: Method loadData() (Serialize) .................................................................................. 72
Code 46: Method storeData() (Serialize) ................................................................................. 73
Code 47: Class SerializeProxy ................................................................................................. 73
Code 48: Class AccountingService (JAXB - Variante 1) ........................................................ 80
Code 49: Class AccountingEntry (JAXB - Variante 1) ........................................................... 80
- 251 -
Verzeichnisse
Code 50: Methode afterUnmarshal()........................................................................................ 81
Code 51: Class Account (JAXB - Variante 1) ......................................................................... 82
Code 52: Class AccountingService (JAXB - Variante 2) ........................................................ 85
Code 53: Class Account (JAXB - Variante 2) ......................................................................... 86
Code 54: Class AccountingEntry (JAXB - Variante 2) ........................................................... 87
Code 55: Class JAXBProxy1 ................................................................................................... 89
Code 56: Class JAXBUtil ........................................................................................................ 90
Code 57: Method writeXML() (JAXBUtil) ............................................................................. 90
Code 58: Method readXML() (JAXBUtil)............................................................................... 91
Code 59: Method getSchema() (JAXBUtil)............................................................................. 91
Code 60: Class MySQLProxy .................................................................................................. 93
Code 61: Method query() (MySQL) ........................................................................................ 93
Code 62: Method querySet() (MySQL) ................................................................................... 94
Code 63: Method execute() (MySQL) ..................................................................................... 94
Code 64: Method prepare() (MySQL) ..................................................................................... 95
Code 65: Verwaltung der Konten- und Buchungsobjekte (MySQL)....................................... 96
Code 66: Class Account (Hibernate-Beispiel) ....................................................................... 101
Code 67: Beispiel für 1:1-Beziehung mit Primär-Schlüssel .................................................. 103
Code 68: Beispiel für 1:1-Beziehung mit Fremd-Schlüssel................................................... 104
Code 69: Beispiel für 1:n-Beziehung ..................................................................................... 105
Code 70: Beispiel für n:n-Beziehung ..................................................................................... 105
Code 71: Manuelle Transaktions-Demarkation (1)................................................................ 109
Code 72: Manuelle Transaktions-Demarkation (2)................................................................ 109
Code 73: Selbst implementierte Versionskontrolle................................................................ 111
Code 74: Klasse mit automatischer Versionskontrolle durch Hibernate ............................... 111
Code 75: Class AccountingService (Hibernate - Variante 1) ................................................ 117
Code 76: Class Account (Hibernate - Variante 1).................................................................. 118
Code 77: Class AccountingEntry (Hibernate - Variante 1).................................................... 119
Code 78: Class AccountingService (Hibernate - Variante 2) ................................................ 120
Code 79: Class Account (Hibernate - Variante 2).................................................................. 121
Code 80: Class AccountingEntry (Hibernate - Variante 2).................................................... 121
Code 81: Class HibernateProxy2 (Hibernate - Variante 2).................................................... 122
Code 82: Class SocketServer ................................................................................................. 126
Code 83: Class SocketProxy .................................................................................................. 128
Code 84: run()-Methode von SocketServer ........................................................................... 129
Code 85: Class Request.......................................................................................................... 130
Code 86: Class ClientSideRequest......................................................................................... 131
Code 87: Class ServerSideResponse...................................................................................... 132
Code 88: Class Response ....................................................................................................... 133
Code 89: Class ServerSideResponse...................................................................................... 133
Code 90: Class ClientSideResponse ...................................................................................... 134
Code 91: Class StringUnMarshaller (Teil 1).......................................................................... 135
Code 92: Class StringUnMarshaller (Teil 2).......................................................................... 135
Code 93: Class StringUnMarshaller (Teil 3).......................................................................... 136
Code 94: Method mashalSet()................................................................................................ 137
Code 95: Method unmarshalSet() .......................................................................................... 137
Code 96: Methoden marshalThrowable() und unmarshalThrowable().................................. 137
Code 97: Methode remoteObjectResponse() ......................................................................... 139
Code 98: Methoden getOid() und genOid() ........................................................................... 139
Code 99: Implementierung einiger Buchhaltungsmethoden (Socket - AccountingService) . 140
Code 100: Method request1() (SocketProxy)......................................................................... 141
- 252 -
Verzeichnisse
Code 101: Method request2() (SocketProxy)......................................................................... 141
Code 102: Method request() (SocketProxy)........................................................................... 141
Code 103: Method requestObject() (SocketProxy)................................................................ 142
Code 104: Method calculateBalance() bei RMI..................................................................... 145
Code 105: Class LocateRegistry ............................................................................................ 145
Code 106: Interface Registry.................................................................................................. 146
Code 107: Class RMIServer................................................................................................... 146
Code 108: Class AccountingService (RMI)........................................................................... 148
Code 109: Class Account (RMI)............................................................................................ 148
Code 110: Class AccountingEntry (RMI).............................................................................. 149
Code 111: Class RMIProxy.................................................................................................... 150
Code 112: Class CorbaServer (Methoden)............................................................................. 159
Code 113: Class CorbaServer (main)..................................................................................... 160
Code 114: Class CorbaServerAccount................................................................................... 161
Code 115: Class CorbaServerEntry........................................................................................ 162
Code 116: Class AccountingService (CORBA)..................................................................... 164
Code 117: Class Account (CORBA)...................................................................................... 165
Code 118: Class AccountingEntry (CORBA)........................................................................ 165
Code 119: Class CorbaProxy ................................................................................................. 166
Code 120: Interceptor-Methode ............................................................................................. 195
Code 121: Class EJBAccountingService ............................................................................... 197
Code 122: Class Account (EJB)............................................................................................. 201
Code 123: Class AccountingEntry (EJB)............................................................................... 203
Code 124: Interface MessageListener .................................................................................... 204
Code 125: Class MessageBean .............................................................................................. 205
Code 126: Interface MyManagementMBean ......................................................................... 206
Code 127: Interface TimerService ......................................................................................... 207
Code 128: Interface IBackupTimerMBean ............................................................................ 208
Code 129: Class BackupTimer............................................................................................... 208
Code 130: Class EJBProxy .................................................................................................... 214
Code 131: Class MessageClient............................................................................................. 215
Code 132: InitialContext mit Hashtable................................................................................. 216
Code 133: Interface IWebAccounting.................................................................................... 220
Code 134: Konto-Methoden für Interface IWebAccounting ................................................. 221
Code 135: Buchungs-Methoden für Interface IWebAccounting ........................................... 221
Code 136: Class WebAccountingImpl................................................................................... 222
Code 137: Class WebServiceProxy........................................................................................ 230
Code 138: Class AccountingService (Web-Services)............................................................ 231
Code 139: Class Account (Web-Service)............................................................................... 232
Code 140: Class AccountingEntry (Web-Service)................................................................. 233
- 253 -
Verzeichnisse
Resourcenverzeichnis
Resource 1: Ant-Build-Datei jaxb-generate-schema-v1.xml ................................................... 82
Resource 2: schema-v1.xsd ...................................................................................................... 84
Resource 3: fibu-v1.xml ........................................................................................................... 84
Resource 4: schema-v2.xsd ...................................................................................................... 88
Resource 5: fibu-v2.xml ........................................................................................................... 88
Resource 6: SQL-Befehle zum Anlegen der Konten- und Buchungs-Tabelle......................... 95
Resource 7: SQL-Befehle der Service-Klasse (MySQL)......................................................... 96
Resource 8: SQL-Befehle der Konto-Klasse (MySQL)........................................................... 97
Resource 9: SQL-Befehle der Buchungs-Klasse (MySQL)..................................................... 98
Resource 10: Hibernate Datenbankeinstellungen .................................................................. 107
Resource 11: Socket-Protokoll - Anfrage .............................................................................. 130
Resource 12: Socket-Protokoll - Antwort .............................................................................. 132
Resource 13: CORBA Accounting.idl ................................................................................... 155
Resource 14: Ant-Build-Datei corba-idlj.xml ........................................................................ 157
Resource 15: Bedeutung der CORBA-Suffixe....................................................................... 158
Resource 16: jacorbsettings.cmd............................................................................................ 168
Resource 17: jacorbnaming.cmd ............................................................................................ 168
Resource 18: jacorbserver.cmd .............................................................................................. 168
Resource 19: jacorbclient.cmd ............................................................................................... 169
Resource 20: Hypersonic-Datenquelle hsqldb-ds.xml ........................................................... 177
Resource 21: MySQL-Datenquelle mysql-ds.xml ................................................................. 178
Resource 22: PostegreSQL-Datenquelle postgres-ds.xml ..................................................... 179
Resource 23: Oracle-Datenquelle oracle-ds.xml.................................................................... 180
Resource 24: JNDI-Ansicht des allgemeinen Java-Namensraumes ...................................... 184
Resource 25: jmx-queue-service.xml ..................................................................................... 205
Resource 26: jms-topic-service.xml ....................................................................................... 205
Resource 27: application.xml (EJB)....................................................................................... 209
Resource 28: Ant-Vorgang für EJB3-Archiv......................................................................... 210
Resource 29: Ant-Vorgang für Persistence-Archiv ............................................................... 211
Resource 30: Deployment-Descriptor persistence.xml .......................................................... 211
Resource 31: Ant-Vorgang für JAR-Archiv .......................................................................... 211
Resource 32: Ant-Build-Datei enterprise-deploy-jboss4.xml ................................................ 212
Resource 33: build.properties................................................................................................. 213
Resource 34: jndi.properties................................................................................................... 216
Resource 35: log4j.properties................................................................................................. 217
Resource 36: Web-Service-Descriptor web.xml .................................................................... 223
Resource 37: Ant-Build-Datei webservice-deploy.xml ......................................................... 224
Resource 38: application.xml (Web-Service) ........................................................................ 224
Resource 39: Ant-Build-Datei webservice-generate.xml....................................................... 225
Resource 40: WSTools-Konfiguration wstools-java2wsdl.xml............................................. 226
Resource 41: WSTools-Konfiguration wstools-wsdl2java.xml............................................. 227
Resource 42: Ant-Build-Datei axis-generate-client.xml ........................................................ 228
- 254 -
Verzeichnisse
Tabellenverzeichnis
Tabelle 1: Beispiel-Buchungsatz................................................................................................ 4
Tabelle 2: Verwendete Begriffe ................................................................................................. 5
Tabelle 3: Modulares Konzept der Programmteile .................................................................. 12
Tabelle 4: JAXB Paket-Annotations ........................................................................................ 76
Tabelle 5: JAXB Klassen-Annotations .................................................................................... 76
Tabelle 6: JAXB Enum-Annotations ....................................................................................... 77
Tabelle 7: JAXB Feld-Annotations.......................................................................................... 78
Tabelle 8: JAXB Objekt-Fabrik-Annotations .......................................................................... 79
Tabelle 9: JAXB Adapter-Annotations .................................................................................... 79
Tabelle 10: Einbindung von Hibernate als zusätzliche Schicht ............................................... 99
Tabelle 11: Schematisher Aufbau der Hibernate-Komponenten ............................................. 99
Tabelle 12: Hibernate-Beziehungs-Semantik......................................................................... 104
Tabelle 13: Zuordnung der Basisdatentypen.......................................................................... 156
Tabelle 14: Zuordnung der komplexen IDL-Angaben........................................................... 156
Tabelle 15: Erzeugte CORBA-Artefakte ............................................................................... 158
Tabelle 16: Bean-Callback-Methoden und Annotations........................................................ 192
Tabelle 17: Annotation für Dependency-Injection................................................................. 194
Tabelle 18: Vergleich von SOAP-Nachrichten für RPC-Style und Document-Style ............ 219
Tabelle 19: Vergleich von SOAP-Nachrichten für literal und encoded................................. 219
Tabelle 20: Von AXIS erzeugte Client-Klassen .................................................................... 229
- 255 -
Verzeichnisse
Quellenverzeichnis
[01] Homepage der Firma Sun-Microsystems
http://java.sun.com/
[02] Homepage des JBoss-Application-Server
http://www.jboss.com/
[03] Deutsche Version der JBoss-Homepage
http://de.jboss.com/
[04] Wikipedia-Artikel über den JBoss
http://de.wikipedia.org/wiki/JBoss_Application_Server
[05] Homepage der Apache-Foundation
http://www.apache.de/
[06] Hompage des Tomcat Web-Containers
http://tomcat.apache.org/
[07] Homepage des Java Authentication and Authorization Service (JAAS)
http://java.sun.com/products/jaas/
[08] Wikipedia-Artikel über die Java 2 Enterprise Edition (J2EE)
http://de.wikipedia.org/wiki/J2EE
[09] Homepage des Java-basierten Installer-Generators IzPack
http://www.izforge.com/izpack/
[10] Wikipedia-Artikel über MySQL
http://de.wikipedia.org/wiki/MySQL
[11] Homepage der MySQL-Datenbank
http://www.mysql.com/
[12] Wikipedia-Artikel über Finanzbuchhaltung
http://de.wikipedia.org/wiki/Finanzbuchhaltung
[13] Wikipedia-Artikel über Buchhaltung
http://de.wikipedia.org/wiki/Buchhaltung
[14] Homepage der Hypersonic-Datenbank (HSQLDB)
http://www.hsqldb.org/
[15] Homepage des Java Messaging Service (JMS)
http://java.sun.com/products/jms/
[16] Homepage der Java Management Extensions (JMX)
http://java.sun.com/javase/technologies/core/mntr-mgmt/javamanagement/
- 256 -
Verzeichnisse
[17] Homepage des Java Naming and Directory Interface (JNDI)
http://java.sun.com/javase/technologies/core/jndi/index.jsp
[18] Heiko W. Rupp, JBoss - Server-Handbuch für J2EE-Entwickler und Administratoren,
dpunkt-Verlag, 1. Auflage 2005, ISBN 3-89864-318-2
[19] Homepage der Open-Source-Datenbank PostgreSQL
http://www.postgresql.org/
[20] Violet, ein einfacher schlanker Java-UML-Editor
http://horstmann.com/violet/
[21] FAQ zu den Web-Services beim JBoss
http://wiki.jboss.org/wiki/Wiki.jsp?page=JBWSFAQ
[22] Homepage der JBoss-Web-Services
http://labs.jboss.com/portal/jbossws/
[23] JBoss-Web-Services User-Guide
http://labs.jboss.com/portal/jbossws/user-guide/en/html/index.html
[24] Homepage der Web-Services Interoperability Organization, Basic Profile Version 1.0
http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html
[25] IBM-Homepage: Welche Art von Web-Service sollte man benutzen?
http://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/
[26] SOAP-Spezifikation vom W3C
http://www.w3.org/TR/soap/
[27] Homepage der Hibernate-Projekts
http://www.hibernate.org/
[28] Java-Web-Services Homepage
http://java.sun.com/webservices/jwsdp/index.jsp
[29] Artikel "Threads and Swing" bei Sun
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
[30] Homepage des Eclipse-Projects
http://www.eclipse.org/
[31] Sun-Dokument über die abgelehnten Methoden bei Threads
http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html
[32] Homepage des AXIS-Framework bei der Apache-Foundation
http://ws.apache.org/axis/
[33] Homepage zu Remote Method Invocation (RMI)
http://java.sun.com/javase/technologies/core/basic/rmi/index.jsp
- 257 -
Verzeichnisse
[34] Wikipedia-Artikel zu RMI
http://de.wikipedia.org/wiki/Remote_Method_Invocation
[35] RMI-Tutorial von Sun
http://java.sun.com/docs/books/tutorial/rmi/index.html
[36] Ziele und Funktionsweisen von RMI
http://www.sbgl.de/rmi/
[37] Homepage der Java Database Connectivity (JDBC)
http://java.sun.com/javase/technologies/database/index.jsp
[38] Homepage der Eclipse Enterprise Java Variante
http://www.eclipse.org/home/categories/index.php?category=enterprise
[39] Homepage des JBoss Cache Projekts
http://www.jboss.org/products/jbosscache
[40] Homepage des JBoss Framework zur aspektorientierten Programmierung
http://labs.jboss.com/portal/jbossaop
[41] Homepage von JBoss jBPM
http://www.jboss.com/products/jbpm
[42] Homepage der JBoss Rules
http://www.jboss.com/products/rules
[43] Wikipedia-Artikel zu BPEL
http://de.wikipedia.org/wiki/Business_Process_Execution_Language
[44] Wikipedia-Artikel zu BPM
http://de.wikipedia.org/wiki/Prozessmanagement
[45] Java 2 Enterprise Edition Homepage
http://java.sun.com/javaee/
[46] Wikipedia-Artikel über EJB-Container
http://de.wikipedia.org/wiki/EJB-Container
[47] Verwendung des Application-Client-Container (AAC)
http://docs.sun.com/source/817-2173-10/dcacc.html
[48] Wikipedia-Artikel über die Java Management Extensions (JMX)
http://de.wikipedia.org/wiki/JMX
[49] Homepage des Java Beans Activation Framework (JAF)
http://java.sun.com/products/javabeans/jaf/downloads/index.html
[50] Wikipedia-Artikel über den Java Authentication and Authorization Service
http://de.wikipedia.org/wiki/JAAS
- 258 -
Verzeichnisse
[51] Homepage der Java Transaction API (JTA)
http://java.sun.com/products/jta/
[52] Homepage des Java Transaction Service (JTS)
http://java.sun.com/products/jts/
[53] Homepage der JavaMail-API bei Sun
http://java.sun.com/products/javamail/
[54] Homepage der Java-API for XML Registries (JAXR)
http://java.sun.com/webservices/jaxr/index.jsp
[55] Homepage der Java-API for XML-Based RPC (JAX-RPC)
http://java.sun.com/webservices/jaxrpc/
[56] Homepage der Java Connector Architecture (JCA)
http://java.sun.com/j2ee/connector/
[57] Wikipedia-Artikel über die Java Connector Architecture (JCA)
http://de.wikipedia.org/wiki/J2EE_Connector_Architecture
[58] Java on Pocket-PC (Unofficial FAQ)
http://blog.vikdavid.com/2004/12/java_on_pocketp.html
[59] Introduction to Pocket-PC Development
http://www.codeproject.com/ce/Pocket_PC_Development.asp
[60] Windows Mobile Developer Center
http://msdn.microsoft.com/windowsmobile/default.aspx
[61] Windows Mobile Developer Resources
http://www.microsoft.com/windowsmobile/developers/default.mspx
[62] Sun CDC FAQ
http://java.sun.com/products/cdc/faq.html
[63] Homepage der Java Micro Edition (J2ME)
http://java.sun.com/javame/index.jsp
[64] Homepage der Mysaifu JVM
http://www2s.biglobe.ne.jp/~dat/java/project/jvm/index_en.html
[65] JSR-250: Common Annotations for the Java Platform
http://jcp.org/en/jsr/detail?id=250
[66] JSR-220: Enterprise Java Beans 3.0
http://jcp.org/en/jsr/detail?id=220
[67] Hibernate Reference Doumentation
http://www.hibernate.org/hib_docs/v3/reference/en/html_single/
- 259 -
Verzeichnisse
[68] Hibernate Annotations Reference Guide
http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/
[69] Home der Java Architecture for XML Bindung (JAXB)
http://java.sun.com/webservices/jaxb/
[70] Wikipedia-Artikel über die Java Architecture for XML Binding (JAXB)
http://de.wikipedia.org/wiki/JAXB
[71] The Java EE 5 Tutorial, JAXB, Page 5
http://java.sun.com/javaee/5/docs/tutorial/doc/JAXB5.html
[72] JAXB: Issue 268 - Map handling broken
https://jaxb.dev.java.net/issues/show_bug.cgi?id=268
[73] Wikipedia-Artikel über Enterprise JavaBeans
http://de.wikipedia.org/wiki/Enterprise_Java_Beans
[74] EJB Glossar
http://www.objects-at-work.de/pmwiki/EJBGlossar/HomePage
[75] Wikipedia-Artikel zum Java Messaging Service (JMS)
http://de.wikipedia.org/wiki/Java_Message_Service
[76] Homepage des Apache Logging Services Project Log4j
http://logging.apache.org/log4j/docs/
[77] Dynamic code downloading using Java RMI
http://java.sun.com/j2se/1.5.0/docs/guide/rmi/codebase.html
[78] JacORB Homepage
http://www.jacorb.org/
[79] Homepage der Object Management Group (OMG)
http://www.omg.org/
[80] Wikipedia-Artikel über CORBA
http://de.wikipedia.org/wiki/CORBA
[81] CORBA-Dienste - Ausarbeitung zum Hauptseminar Middleware
http://www.marclayer.de/stud/proj/cos/CORBAservices.php
[82] Eclipse Europa Projektseite
http://www.eclipse.org/europa/
[83] Spezifikation des Formats von class-Dateien
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
[84] Werner Eberling, Jan Leßner, Enterprise JavaBeans 3 - Das EJB3-Praxisbuch für Einund Umsteiger, Hanser-Verlag, 1. Auflage 2007, ISBN 978-3-446-41085-5
- 260 -
Anhänge
Anhänge
Anhang A: System-Eigenschaften des JBoss
awt.toolkit
sun.awt.windows.WToolkit
catalina.base
D:\daemon\jboss-4.2.2.GA\server\default
catalina.ext.dirs
D:\daemon\jboss-4.2.2.GA\server\default\lib
catalina.home
D:\daemon\jboss-4.2.2.GA\server\default
catalina.useNaming
false
com.arjuna.ats.arjuna.objectstore.objectStoreDir
D:\daemon\jboss-4.2.2.GA\server\default\data/tx-object-store
com.arjuna.ats.jta.lastResourceOptimisationInterface
org.jboss.tm.LastResource
com.arjuna.ats.tsmx.agentimpl
com.arjuna.ats.internal.jbossatx.agent.LocalJBossAgentImpl
com.arjuna.common.util.logger
log4j_releveler
com.arjuna.common.util.logging.DebugLevel
0x00000000
com.arjuna.common.util.logging.FacilityLevel
0xffffffff
com.arjuna.common.util.logging.VisibilityLevel 0xffffffff
common.loader
${catalina.home}/lib,${catalina.home}/lib/*.jar
file.encoding
Cp1252
file.encoding.pkg
sun.io
file.separator
\
hibernate.bytecode.provider
javassist
java.awt.graphicsenv
sun.awt.Win32GraphicsEnvironment
java.awt.printerjob
sun.awt.windows.WPrinterJob
java.class.path
D:\daemon\jboss-4.2.2.GA\bin\\run.jar
java.class.version
50.0
java.endorsed.dirs
D:\daemon\jboss-4.2.2.GA\bin\..\lib\endorsed
java.ext.dirs
C:\Program Files\Java\jre6\lib\ext
java.home
C:\Program Files\Java\jre6
java.io.tmpdir
C:\DOCUME~1\luf39862\LOCALS~1\Temp\
java.library.path
.;
C:\WINDOWS;
C:\WINDOWS\system32;
C:\WINDOWS\Sun\Java\bin;
C:\Program Files\Java\jre6\bin;
java.naming.factory.initial
org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs
org.jboss.naming:org.jnp.interfaces
java.protocol.handler.pkgs
org.jboss.net.protocol
java.rmi.server.RMIClassLoaderSpi org.jboss.system.JBossRMIClassLoader
java.rmi.server.codebase
http://127.0.0.1:8083/
java.rmi.server.hostname
127.0.0.1
java.runtime.name
Java(TM) SE Runtime Environment
java.runtime.version
1.6.0_11-b03
java.specification.name
Java Platform API Specification
java.specification.vendor Sun Microsystems Inc.
java.specification.version 1.6
java.vendor
Sun Microsystems Inc.
java.vendor.url
http://java.sun.com/
java.vendor.url.bug
http://java.sun.com/cgi-bin/bugreport.cgi
java.version
1.6.0_11
java.vm.info
mixed mode
java.vm.name
Java HotSpot(TM) Client VM
java.vm.specification.name
Java Virtual Machine Specification
java.vm.specification.vendor
Sun Microsystems Inc.
java.vm.specification.version 1.0
java.vm.vendor
Sun Microsystems Inc.
java.vm.version
11.0-b16
jboss.bind.address
127.0.0.1
jboss.home.dir
D:\daemon\jboss-4.2.2.GA
- 261 -
Anhänge
jboss.home.url
file:/D:/daemon/jboss-4.2.2.GA/
jboss.identity
bf3152b003d17e24x33389935x11b0ccf6736x-8000461
jboss.lib.url
file:/D:/daemon/jboss-4.2.2.GA/lib/
jboss.remoting.domain
JBOSS
jboss.remoting.instanceid bf3152b003d17e24x33389935x11b0ccf6736x-8000461
jboss.remoting.jmxid
rfhinf176_1258974783595
jboss.remoting.version
22
jboss.server.base.dir
D:\daemon\jboss-4.2.2.GA\server
jboss.server.base.url
file:/D:/daemon/jboss-4.2.2.GA/server/
jboss.server.config.url
file:/D:/daemon/jboss-4.2.2.GA/server/default/conf/
jboss.server.data.dir
D:\daemon\jboss-4.2.2.GA\server\default\data
jboss.server.home.dir
D:\daemon\jboss-4.2.2.GA\server\default
jboss.server.home.url
file:/D:/daemon/jboss-4.2.2.GA/server/default/
jboss.server.lib.url
file:/D:/daemon/jboss-4.2.2.GA/server/default/lib/
jboss.server.log.dir
D:\daemon\jboss-4.2.2.GA\server\default\log
jboss.server.name
default
jboss.server.temp.dir
D:\daemon\jboss-4.2.2.GA\server\default\tmp
jbossmx.loader.repository.class
org.jboss.mx.loading.UnifiedLoaderRepository3
line.separator
org.apache.commons.logging.Log
org.apache.commons.logging.impl.Log4JLogger
org.w3c.dom.DOMImplementationSourceList
org.apache.xerces.dom.DOMXSImplementationSourceImpl
os.arch
x86
os.name
Windows XP
os.version
5.1
path.separator
;
program.name
run.bat
server.loader
shared.loader
sun.arch.data.model
32
sun.boot.class.path
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jaxb-api.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxrpc.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxws-ext.j
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxws.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-saaj.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\serializer.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\xalan.jar;
D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\xercesImpl.jar;
C:\Programme\Java\jre6\lib\resources.jar;
C:\Programme\Java\jre6\lib\rt.jar;
C:\Programme\Java\jre6\lib\sunrsasign.jar;
C:\Programme\Java\jre6\lib\jsse.jar;
C:\Programme\Java\jre6\lib\jce.jar;
C:\Programme\Java\jre6\lib\charsets.jar;C:\Programme\Java\jre6\classes
sun.boot.library.path
C:\Programme\Java\jre6\bin
sun.cpu.endian
little
sun.desktop
windows
sun.io.unicode.encoding
UnicodeLittle
sun.jnu.encoding
Cp1252
sun.management.compiler
HotSpot Client Compiler
sun.os.patch.level
Service Pack 3
user.country
DE
user.dir
D:\ daemon\jboss-4.2.2.GA\bin
user.home
C:\Documents and Settings\luf39862
user.language
de
user.name
luf39862
user.timezone
Europe/Berlin
- 262 -
Anhänge
Anhang B: Erweiterte Darstellung des asynchronen Threading
- 263 -
Anhänge
Anhang C: Notwendige Bibliotheken
Java Architecture for XML Binding (JAXB)
jaxb1-impl.jar
jaxb-api.jar
jaxb-impl.jar
jaxb-xjc.jar
jsr173_api.jar
sjsxp.jar
Hibernate-Core
ant-1.6.5.jar
ant-antlr-1.6.5.jar
ant-junit-1.6.5.jar
ant-launcher-1.6.5.jar
antlr-2.7.6.jar
ant-swing-1.6.5.jar
asm.jar
asm-attrs.jar
c3p0-0.9.0.jar
cglib-2.1.3.jar
checkstyle-all.jar
cleanimports.jar
commons-collections-3.2.jar
commons-logging-1.0.4.jar
concurrent-1.3.2.jar
connector.jar
dom4j-1.6.1.jar
ehcache-1.2.3.jar
jaas.jar
jacc-1_0-fr.jar
javassist.jar
jaxen-1.1-beta-7.jar
jboss-cache.jar
jboss-common.jar
jboss-jmx.jar
jboss-system.jar
jdbc2_0-stdext.jar
jgroups-2.2.8.jar
jta.jar
junit-3.8.1.jar
log4j-1.2.11.jar
oscache-2.1.jar
proxool-0.8.3.jar
swarmcache-1.0rc2.jar
syndiag2.jar
versioncheck.jar
xerces-2.6.2.jar
xml-apis.jar
Hibernate-Annotations
ejb3-persistence.jar
lucene-core-2.0.0.jar
Web-Services
jaxws-api.jar
jaxws-rt.jar
jaxws-tools.jar
jsr181-api.jar
jsr250-api.jar
JBoss-Web-Services-Tools
jaxb-api.jar
jaxb-impl.jar
jboss-jaxws.jar
jbossws-core.jar
jbossws-jboss-integration.jar
policy-1.0.jar
stax-api-1.0.jar
wsdl4j.jar
xmlsec.jar
- 264 -
Anhänge
AXIS
axis.jar
axis-ant.jar
commons-discovery-0.2.jar
commons-logging-1.0.4.jar
jaxrpc.jar
log4j-1.2.8.jar
saaj.jar
wsdl4j-1.5.1.jar
JBoss-Client
jbossha-client.jar
jboss-iiop-client.jar
jboss-jaxrpc.jar
jbossjmx-ant.jar
jboss-jsr77-client.jar
jbossmq-client.jar
jboss-remoting.jar
jboss-saaj.jar
jboss-serialization.jar
jboss-srp-client.jar
jboss-system-client.jar
jbossws-client.jar
jboss-xml-binding.jar
jmx-client.jar
jmx-invoker-adaptor-client.jar
juddisaaj.jar
log4j.jar
logkit.jar
mail.jar
scout.jar
trove.jar
wsdl4j.jar
xmlsec.jar
activation.jar
antlr-2.7.6.jar
avalon-framework.jar
commons-httpclient.jar
commons-logging.jar
concurrent.jar
ejb3-persistence.jar
getopt.jar
hibernate-annotations.jar
hibernate-client.jar
jacorb.jar
javassist.jar
javax.servlet.jar
jbossall-client.jar
jboss-annotations-ejb3.jar
jboss-aop-jdk50-client.jar
jboss-aspect-jdk50-client.jar
jboss-client.jar
jboss-common-client.jar
jbosscx-client.jar
jboss-deployment.jar
jboss-ejb3-client.jar
jboss-ejb3x.jar
JacORB
antlr-2.7.2.jar
avalon-framework-4.1.5.jar
backport-util-concurrent.jar
idl.jar
jacorb.jar
logkit-1.2.jar
picocontainer-1.2.jar
wrapper.dll
wrapper-3.1.0.jar
- 265 -
Anhänge
Anhang D: Performance-Messung
Durch die Möglichkeit der Geschwindigkeitsmessung in der Buchhaltungsoberfläche, die
1000 Buchungen anlegt und wieder löscht, ist eine grobe Aussage über die Performance der
verschiedenen Technologien möglich.
Datenhaltung
Da bei der Datenhaltung die Zugriffe auf die Konten- und Buchungsobjekte fast immer direkt
im Speicher stattfinden, ist die Dauer der Geschwindigkeitsmessung meist konstant. Die Abweichungen ergeben sich hier bei der Zeit die nötig ist, um die Daten zu Laden bzw. zu Speichern, wozu jeweils die von der Utility-Klasse erzeugten Testdatensätze verwendet werden.
Technologie
Local
Serialize
JAXB (V1)
JAXB (V2)
MySQL
Hibernate (V1)
Hibernate (V2)
Laden
00:00:010
00:00:060
00:00:600
00:00:700
00:00:030
00:13:130
00:00:240
Speichern
00:00:010
00:00:010
00:00:030
00:00:030
00:00:010
00:00:010
00:00:020
Geschwindigkeitstest
00:00:020
00:00:020
00:00:020
00:00:020
00:05:400
02:40:750 (mit commit())
00:00:060
Die hohen Werte bei Hibernate (V1) ergeben sich dadurch, daß nach jeden Aufruf manuell ein
commit() ausgeführt wird. Bei der zweiten Variante erfolgen die Zugriffe nur im Speicher.
Transport
Die Transport-Technologien werden alle mit einer Instanz der lokalen Datenhaltung auf der
Serverseite getestet. Server und Client laufen dabei beide auf demselben Rechner.
Technologie
Socket
RMI
CORBA
JacORB
Verbinden
00:00:020
00:00:300
00:00:400
00:30:410
Schließen
00:00:010
00:00:010
00:00:010
00:00:010
Geschwindigkeitstest
00:03:525
00:03:060
00:05:870
00:02:460
Auffallend ist die deutlich längere Ladezeit beim JacORB, der dafür später im laufenden Betrieb eine bessere Performance erzielt.
Enterprise JavaBeans
Hier kommen die Enterprise JavaBeans auf dem JBoss-Application-Server mit Hibernate und
einer MySQL-Datenbank zum Einsatz.
Technologie
JBoss - EJB
JBoss - Web-Services
Verbinden
00:00:930
00:00:810
Schließen
00:00:010
00:00:010
- 266 -
Geschwindigkeitstest
02:37:497
04:24:410
Anhänge
Anhang E: WSDL-Datei für den Buchhaltungsservice
<?xml version="1.0" encoding="UTF-8"?>
<definitions name='WebAccountigService'
targetNamespace='http://www.fh-regensburg.de/vs_iw/webservice'
xmlns='http://schemas.xmlsoap.org/wsdl/'
xmlns:ns1='http://www.fh-regensburg.de/vs_iw/webservice/types'
xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
xmlns:tns='http://www.fh-regensburg.de/vs_iw/webservice'
xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<types>
<schema targetNamespace='http://www.fh-regensburg.de/vs_iw/webservice/types'
xmlns='http://www.w3.org/2001/XMLSchema'
xmlns:soap11-enc='http://schemas.xmlsoap.org/soap/encoding/'
xmlns:tns='http://www.fh-regensburg.de/vs_iw/webservice/types'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<complexType name='AccountingException'>
<sequence>
<element name='message' nillable='true' type='string'/>
</sequence>
</complexType>
<complexType name='IOException'>
<sequence>
<element name='message' nillable='true' type='string'/>
</sequence>
</complexType>
<complexType name='Integer.Array'>
<sequence>
<element maxOccurs='unbounded' minOccurs='0' name='value' nillable='true' type='int'/>
</sequence>
</complexType>
<element name='AccountingException' type='tns:AccountingException'/>
<element name='IOException' type='tns:IOException'/>
</schema>
</types>
<message name='IWebAccountingService_calculateBalance'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_calculateBalanceResponse'>
<part name='result' type='xsd:double'/>
</message>
<message name='IOException'>
<part element='ns1:IOException' name='IOException'/>
</message>
<message name='AccountingException'>
<part element='ns1:AccountingException' name='AccountingException'/>
</message>
<message name='IWebAccountingService_createAccount'>
<part name='Integer_1' type='xsd:int'/>
<part name='int_2' type='xsd:int'/>
<part name='String_3' type='xsd:string'/>
</message>
<message name='IWebAccountingService_createAccountResponse'/>
<message name='IWebAccountingService_createAccountingEntry'>
<part name='Integer_1' type='xsd:int'/>
<part name='Integer_2' type='xsd:int'/>
<part name='Double_3' type='xsd:double'/>
<part name='String_4' type='xsd:string'/>
</message>
<message name='IWebAccountingService_createAccountingEntryResponse'>
<part name='result' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getAccountingEntries'/>
<message name='IWebAccountingService_getAccountingEntriesResponse'>
<part name='result' type='ns1:Integer.Array'/>
</message>
<message name='IWebAccountingService_getAccounts'/>
<message name='IWebAccountingService_getAccountsResponse'>
<part name='result' type='ns1:Integer.Array'/>
</message>
<message name='IWebAccountingService_getAmount'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getAmountResponse'>
<part name='result' type='xsd:double'/>
</message>
<message name='IWebAccountingService_getCreditAccount'>
- 267 -
Anhänge
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getCreditAccountResponse'>
<part name='result' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getCreditEntries'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getCreditEntriesResponse'>
<part name='result' type='ns1:Integer.Array'/>
</message>
<message name='IWebAccountingService_getDebitAccount'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getDebitAccountResponse'>
<part name='result' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getDebitEntries'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getDebitEntriesResponse'>
<part name='result' type='ns1:Integer.Array'/>
</message>
<message name='IWebAccountingService_getDescription'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getDescriptionResponse'>
<part name='result' type='xsd:string'/>
</message>
<message name='IWebAccountingService_getText'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getTextResponse'>
<part name='result' type='xsd:string'/>
</message>
<message name='IWebAccountingService_getType'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_getTypeResponse'>
<part name='result' type='xsd:int'/>
</message>
<message name='IWebAccountingService_removeAccount'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_removeAccountResponse'/>
<message name='IWebAccountingService_removeAccountingEntry'>
<part name='Integer_1' type='xsd:int'/>
</message>
<message name='IWebAccountingService_removeAccountingEntryResponse'/>
<message name='IWebAccountingService_setAmount'>
<part name='Integer_1' type='xsd:int'/>
<part name='Double_2' type='xsd:double'/>
</message>
<message name='IWebAccountingService_setAmountResponse'/>
<message name='IWebAccountingService_setCreditAccount'>
<part name='Integer_1' type='xsd:int'/>
<part name='Integer_2' type='xsd:int'/>
</message>
<message name='IWebAccountingService_setCreditAccountResponse'/>
<message name='IWebAccountingService_setDebitAccount'>
<part name='Integer_1' type='xsd:int'/>
<part name='Integer_2' type='xsd:int'/>
</message>
<message name='IWebAccountingService_setDebitAccountResponse'/>
<message name='IWebAccountingService_setDescription'>
<part name='Integer_1' type='xsd:int'/>
<part name='String_2' type='xsd:string'/>
</message>
<message name='IWebAccountingService_setDescriptionResponse'/>
<message name='IWebAccountingService_setText'>
<part name='Integer_1' type='xsd:int'/>
<part name='String_2' type='xsd:string'/>
</message>
<message name='IWebAccountingService_setTextResponse'/>
<message name='IWebAccountingService_setType'>
<part name='Integer_1' type='xsd:int'/>
<part name='int_2' type='xsd:int'/>
</message>
- 268 -
Anhänge
<message name='IWebAccountingService_setTypeResponse'/>
<portType name='IWebAccountingService'>
<operation name='calculateBalance' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_calculateBalance'/>
<output message='tns:IWebAccountingService_calculateBalanceResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='createAccount' parameterOrder='Integer_1 int_2 String_3'>
<input message='tns:IWebAccountingService_createAccount'/>
<output message='tns:IWebAccountingService_createAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='createAccountingEntry'
parameterOrder='Integer_1 Integer_2 Double_3 String_4'>
<input message='tns:IWebAccountingService_createAccountingEntry'/>
<output message='tns:IWebAccountingService_createAccountingEntryResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='getAccountingEntries'>
<input message='tns:IWebAccountingService_getAccountingEntries'/>
<output message='tns:IWebAccountingService_getAccountingEntriesResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getAccounts'>
<input message='tns:IWebAccountingService_getAccounts'/>
<output message='tns:IWebAccountingService_getAccountsResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getAmount' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getAmount'/>
<output message='tns:IWebAccountingService_getAmountResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getCreditAccount' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getCreditAccount'/>
<output message='tns:IWebAccountingService_getCreditAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getCreditEntries' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getCreditEntries'/>
<output message='tns:IWebAccountingService_getCreditEntriesResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getDebitAccount' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getDebitAccount'/>
<output message='tns:IWebAccountingService_getDebitAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getDebitEntries' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getDebitEntries'/>
<output message='tns:IWebAccountingService_getDebitEntriesResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getDescription' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getDescription'/>
<output message='tns:IWebAccountingService_getDescriptionResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getText' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getText'/>
<output message='tns:IWebAccountingService_getTextResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='getType' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_getType'/>
<output message='tns:IWebAccountingService_getTypeResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='removeAccount' parameterOrder='Integer_1'>
<input message='tns:IWebAccountingService_removeAccount'/>
<output message='tns:IWebAccountingService_removeAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='removeAccountingEntry' parameterOrder='Integer_1'>
- 269 -
Anhänge
<input message='tns:IWebAccountingService_removeAccountingEntry'/>
<output message='tns:IWebAccountingService_removeAccountingEntryResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='setAmount' parameterOrder='Integer_1 Double_2'>
<input message='tns:IWebAccountingService_setAmount'/>
<output message='tns:IWebAccountingService_setAmountResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='setCreditAccount' parameterOrder='Integer_1 Integer_2'>
<input message='tns:IWebAccountingService_setCreditAccount'/>
<output message='tns:IWebAccountingService_setCreditAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='setDebitAccount' parameterOrder='Integer_1 Integer_2'>
<input message='tns:IWebAccountingService_setDebitAccount'/>
<output message='tns:IWebAccountingService_setDebitAccountResponse'/>
<fault message='tns:IOException' name='IOException'/>
<fault message='tns:AccountingException' name='AccountingException'/>
</operation>
<operation name='setDescription' parameterOrder='Integer_1 String_2'>
<input message='tns:IWebAccountingService_setDescription'/>
<output message='tns:IWebAccountingService_setDescriptionResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='setText' parameterOrder='Integer_1 String_2'>
<input message='tns:IWebAccountingService_setText'/>
<output message='tns:IWebAccountingService_setTextResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
<operation name='setType' parameterOrder='Integer_1 int_2'>
<input message='tns:IWebAccountingService_setType'/>
<output message='tns:IWebAccountingService_setTypeResponse'/>
<fault message='tns:IOException' name='IOException'/>
</operation>
</portType>
<binding name='IWebAccountingServiceBinding' type='tns:IWebAccountingService'>
<soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>
<operation name='calculateBalance'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='createAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='createAccountingEntry'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/>
</output>
- 270 -
Anhänge
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='getAccountingEntries'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getAccounts'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getAmount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getCreditAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getCreditEntries'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getDebitAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
- 271 -
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
Anhänge
</operation>
<operation name='getDebitEntries'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getDescription'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getText'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='getType'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='removeAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='removeAccountingEntry'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
- 272 -
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
Anhänge
</operation>
<operation name='setAmount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='setCreditAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='setDebitAccount'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
<fault name='AccountingException'>
<soap:fault name='AccountingException' use='literal'/>
</fault>
</operation>
<operation name='setDescription'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='setText'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
</operation>
<operation name='setType'>
<soap:operation soapAction=''/>
<input>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</input>
<output>
<soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice'
</output>
<fault name='IOException'>
<soap:fault name='IOException' use='literal'/>
</fault>
- 273 -
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
use='literal'/>
Anhänge
</operation>
</binding>
<service name='WebAccountigService'>
<port binding='tns:IWebAccountingServiceBinding' name='IWebAccountingServicePort'>
<soap:address location='http://localhost:8080/WebAccountingService'/>
</port>
</service>
</definitions>
- 274 -
Anhänge
Anhang F: Changelog der Anwendung
Version 3.3:
- Anpassungen für JBoss 5.1.0
- Geänderte Anzeige der benötigten Argumente einer Proxy-Klasse
- Bugfix im Socket2-Modul: null wurde als OID übergeben
- Bugfix im Account-Task: setDescription() wurde als setAccountType()
angezeigt
Version 3.2:
- Anpassungen für JBoss 5.0.0
- Unterstützung des neuen Nimbus Look & Feel
- Bugfix beim Laden des MacOS Look & Feel
Version 3.1:
- Alternative Socket-Implementierung mit Data-Streams
- Anpassung an JBoss 4.2.2, JBoss-WS 3.0.3 und Axis 1.4
- Gleichzeitige Kompatibilität mit Java 1.5 und 1.6 sichergestellt
- Bugfixes in etlichen Klassen
Version 3.0:
- Interner Umbau der Benutzeroberfläche
- Erweitertes Menü mit mehr Funktionen
- Icons im Menü und auf Schaltflächen
- besser kontrollierbare Vorgänge im Vorgangsfenster
- mehrere Performance-Modi
- schnellere Verarbeitung bei vielen Buchungen
- zusätzliche Look & Feel Integration
- Jar-Integration von Beschreibung und JavaDoc
Version 2.3:
- Performance verbessert, langsame Codeteile überarbeitet
- Bugfixes bei den Test-Funktionen
- Kompatibilität mit Java 1.6
Version 2.2:
- Verbesserung und Vereinfachung der Oberfläche
- Fortschrittsanzeige
- Threading bei vielen Vorgängen verbessert
- Locking in der Adapterschicht berichtigt
- Test-Funktionen für erweiterte Schnittstelle berichtigt
Version 2.1:
- Reduzierte Anzahl separater Vorgänge zur Performance-Steigerung
- Bugfix: Oberfläche friert unter Linux nicht mehr ein
- Verbesserte Speicherung der eingestellten Menü-Optionen in einer
Properties-Datei
- Eingestelltes Look & Feel wird gespeichert
- Unnütze Bedienelemente werden je nach Anwendungszustand deaktiviert
- Eingegebene Argumente werden je Proxy-Klasse gespeichert
Version 2.0:
- Generische Adapterschicht zur Reduzierung von redundantem Code
- Generische Vorgänge mit Callback-Schnittstellen
- FiBu-Schnittstellen und Geschäftslogik für J2EE-Anwendungen angepasst
- EJB-Implementierung angepasst
- Schnittstelle zur Entkopplung der Geschäftslogik
- Vollständiger eigener Klassenlader für den Proxy-Suchvorgang
- 275 -
Anhänge
Version 1.6:
- Zusammengefaßte Methodenaufrufe getrennt
- Eigener Vorgang mit separatem Thread für jeden Aufruf
- Bessere Entkopplung der Oberfläche durch eine Adapter-Schicht
- Adapter-Schicht in eigenes Paket ausgelagert
Version 1.5:
- Absolutes Layout durch Komponenten mit Layout-Manager ersetzt
- Dynamische Unterstützung von mehreren Look & Feel Auswahlmöglichkeiten
- Mehr Einstell-Möglichkeiten im Menu
- Threading in eigenes Paket ausgelagert
- Verbesserter Code durch Eclipse-Plugins Checkstyle und Findbugs
Version 1.2:
- Fängt Ausnahmen besser ab
- Komplett überarbeitetes Laden von Proxy-Klassen
- Eigener Klassenlader mit Listener-Schnittstelle
- Decoder für class-Dateien mit eigener Reflection-Schnittstelle
- Menu-Einstellungen werden automatisch in Properties-Datei gespeichert
Version 1.1.2:
- Erste Version zur Herausgabe in der Vorlesung
- Fängt mehr Ausnahmen ab
- Verbesserung des Ladens von Proxy-Klassen
- Beim Laden von Buchungen und Konten werden mehrere Methodenaufrufe
zusammengefasst
- Verbesserte Testfunktionen
Version 1.1:
- Entkoppelt die Schnittstellenaufrufe in separaten Threads
- Kann Proxy-Klassen laden, die in einem Unterverzeichnis und
gleichzeitig im CLASSPATH vorhanden sind
- Eine Modul-Implementierung für jede Technologie
- Testfunktionen für die Implementierungen
Version 1.0:
- Erste vollständig lauffähige Version
- Oberfläche kann alle FiBu-Methoden ansprechen
- Feste Bindung an die Schnittstellen
- 276 -
Herunterladen