256 59 5MB
German Pages 454
Verteilte Anwendungen mit Java
Verteilte Anwendungen mit Java Enterprise-Architekturen im Web mit CORBA, XML/SOAP, XML/SOAP,JSP, JSP, (E)JB und JDBC Torsten Langner
new technology eBook Die nicht autorisierte Weitergabe dieses eBooks ist eine Verletzung des Urheberrechts!
Markt+Technik Verlag
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich.
Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 1 05 04 03 02
ISBN 3-8272-6296-8
© 2002 by Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D 81829 München/Germany Alle Rechte vorbehalten Fachlektorat: Wolfgang Dehnhardt, Stuttgart Lektorat: Melanie Kasberger, [email protected] Orthografische Korrektur: Christel Metke, Köln Herstellung: Ulrike Hempel, [email protected] Einbandgestaltung: adesso 21, Thomas Arlt, München Satz: reemers publishing services gmbh, Krefeld (www.reemers.de) Druck und Verarbeitung: Kösel, Kempten (www.KoeselBuch.de) Printed in Germany
Inhaltsverzeichnis Kapitel 1
Prolog 1.1 1.2
Kapitel 2
Kapitel 3
9 Allgemeines An wen richtet sich dieses Buch?
10 11
Auffrischen einiger Vorkenntnisse
13
2.1 2.2 2.3 2.4 2.4.1 2.4.2 2.5 2.5.1 2.5.2 2.5.3
14 15 17 18 18 20 22 23 26 28
Grundlagen verteilter Systeme Der Aufbau verteilter, webbasierter Anwendungen Entwicklungstechnologien Vorkenntnisse zur Objektorientierung Beispiel 1: Identitätskonstruktion in grauer Vorzeit Beispiel 2: WindowsDrucker Objektorientierte Programmierung mit Java – Wiederholung Schnittstellen und Vererbungsmechanismen Quasi-parallele Ausführung mittels Threads Ausführung der beiden Varianten
Socket-Kommunikation zwischen verteilten Anwendungen
31
3.1 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.3 3.3.1 3.3.2 3.4 3.4.1 3.4.2 3.5
32 32 33 35 39 40 41 41 44 45 46 48
3.5.1
Die Kommunikation über TCP/IP Beispiel 1: Ein einfacher Server Realisierung des Clients Realisierung des Servers Klassendokumentationen Der Server läuft Ein wiederverwendbares Framework Erstellung der Elternklasse für einen unbegrenzten Server Die Auslagerung der Kommunikation in einer eigenen Klasse Beispiel 2: Verwendung des Frameworks Der »Portier« Einbettung der Kommunikationsstruktur Beispiel 3: Eine komplexere Kommunikationsstruktur implementieren Realisierung des Clients
52 52
6
Inhaltsverzeichnis
3.5.2 3.6 3.6.1 3.6.2 3.6.3 3.7 3.7.1 3.7.2 3.7.3 Kapitel 4
Realisierung des Servers Erweiterung des Frameworks Variante 1 Variante 2 Beispiel 4: Einen skalierbaren Server implementieren Klassen dynamisch über das Netz laden Allgemeine Vorgehensweise Wozu das Ganze? Ein Socket-basierter Klassenserver
Middleware als die Kommunikationsalternative 4.1 4.1.1 4.1.2 4.2 4.2.1 4.2.2 4.2.3 4.2.4 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4 4.4.1 4.4.2 4.4.3 4.5 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.5.6 4.6
Eine Vorüberlegung Ein kurzer Exkurs: Methodenaufrufe einmal anders Aufbau einer geeigneten Client/Server-Architektur Middleware heißt die Alternative! Ein erster Vergleich zwischen RPC und Middleware Ein Broker übernimmt die Kommunikation! Die Interface Definition Language (IDL) Verwirrung pur: Proxies, Stubs und Skeletons Remote Method Invocation (RMI) Hintergründe Erzeugen einer RMI-Architektur in nur fünf Schritten Ausführen des RMI-Beispiels Klassenreferenzen Code-Mobilität CORBA Was ist CORBA? IDL: Eine Sprache zur Definition von Interfaces Erzeugen einer CORBA-Architektur in fünf Schritten Interoperabilität zwischen CORBA- und RMI-Systemen JRMP IIOP JRMP oder IIOP? Ein praktisches Anwendungsbeispiel Die Programmierung der Beispielsysteme Die Ausführung der Beispielsysteme CORBA: Ansichten und Meinungen
55 60 61 64 66 69 69 74 75 85 86 87 90 101 102 103 105 107 107 108 109 116 117 119 120 120 122 131 146 146 147 147 148 149 165 167
Inhaltsverzeichnis
4.7 4.7.1 4.7.2 4.8 4.8.1 4.9 4.9.1 4.9.2 4.9.3 4.10 Kapitel 5
Verwendete Tools Broker Kompilierungs- und Übersetzungstools JNDI: Namens- und Verzeichnisdienste Namenskontexte Sicherheitsberechtigungen in Java: Java Security Berechtigungen (Permissions) Berechtigungstypen Policy-Dateien erzeugen Zusammenfassung
169 169 170 171 172 175 176 176 181 187
Datenbehandlung mit Java
189
5.1 5.1.1 5.1.2 5.1.3 5.2 5.2.1 5.2.2 5.2.3 5.2.4 5.3
190 190 195 235 249 250 251 260 296
5.3.1 5.3.2 5.4 Kapitel 6
7
JDBC Grundlagen Eine Datenbankanbindung mit Java erstellen Objekte und Relationen – Widerspruch oder Symbiose? Datenbehandlung mit XML Die Geschichte von XML Grundlagen von XML Parsen von XML-Dokumenten mit Java Transformation von XML-Dokumenten mit XSLT Ein Brückenschlag: Von der Datenbank zum XML-Dokument und weiter zu anderen Medien buch.kapitel5.xml.vorlagen.DB2XML_Konverter buch.kapitel5.xslt.vorlagen.Transformer Zusammenfassung
304 304 310 318
Basistechnologien für webbasierte Anwendungen
319
6.1 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.3 6.3.1 6.3.2 6.3.3
322 325 326 327 342 350 355 356 357 360
Informationsübermittlung an die CGI-Komponente Servlets Die Entwickung eines Servlets Praktische Anwendungsbeispiele Weitere Servlet-Techniken Klassen-Dokumentationen JSP Das Prinzip der Entwicklung von JavaServer Pages Erweiterte Sprachkonstrukte einer JSP Praktische Anwendungsbeispiele
8
Inhaltsverzeichnis
6.4 6.5 6.5.1 6.5.2 6.5.3 6.6 Kapitel 7
Kapitel 8
JSP oder Servlet? Session-Management Die Idee der »Session« Alternative Ansätze zum Ablegen von Session-Informationen Klassen-Dokumentationen Zusammenfassung
367 367 367 368 378 380
Weiterführende Konzepte webbasierter Anwendungen
381
7.1 7.1.1 7.1.2 7.1.3 7.2 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.1 7.3.2 7.3.3
382 382 384 387 389 389 390 391 392 404 405 405 408 419
Exkurs: JavaBeans Was sind JavaBeans? Eine Beispiel-Bean Eine Umgebung für die Beispiel-Bean SOAP Was ist SOAP? Wissenswertes über SOAP Webserverkonfiguration Praktische Beispiele Zusammenfassung Enterprise JavaBeans Was sind Enterprise JavaBeans? Vorgehen bei der EJB-Entwicklung am Beispiel Zusammenfassung
Endanwendung: Der Directbroker »comsofort«
421
8.1 8.2 8.2.1 8.2.2 8.3
422 423 423 426 448
Allgemeiner Aufbau des Systems Die Entwicklung der einzelnen Teilsysteme Das »XEVA-Börsensystem« Das »comsofort«-Kundensystem Zusammenfassung
Stichwortverzeichnis
449
Kapitel 1 Prolog 1.1 1.2
Allgemeines An wen richtet sich dieses Buch?
10 11
10
1.1
Allgemeines
Allgemeines Statistisch gesehen ist dieser Abschnitt das am häufigsten überschlagene Kapitel vieler IT-Bücher. Viele Leser (zu denen ich mich auch zähle) wollen »direkt ans Eingemachte« vordringen, also zu den Kapiteln, in denen irgendwelche Bildchen oder Quellcode vorkommen. Aber genau diese Leser verpassen nun den Hintergrund für die Entstehung dieses Buches.
Abbildung 1.1:
Das Buch soll einen »roten Faden« durch die unterschiedlichen Technologien bieten
Als Dozent habe ich schon unzählige Vorträge, Seminare und Schulungen abgehalten, die sich um die in Abbildung 1.1 gezeigten Themen drehten. Dabei habe ich stets festgestellt, dass die Teilnehmer sich zwar auf ein oder zwei Themengebiete spezialisiert haben, ihnen aber meist die »globale« Sichtweise auf das zur Verfügung stehende Technologiespektrum fehlte. Bei der realen Projektarbeit in größeren Unternehmen lassen sich nur selten Allround-Genies finden, die sich 100-prozentig mit allen Technologien auskennen. Wenn aber Projekte anstehen, die z.B. in den B2B1-Bereich einzuordnen sind, dann muss zur Lösung der gestellten Aufgabe ein Mix verschiedener Technologien angewendet werden. Wenn das System der Firma A plötzlich mit dem System der Firma B zusammen-
1 Business-to-Business
Kapitel 1 • Prolog
11
arbeiten soll, muss das Entwicklungsteam wissen, wie es eine zukunftssichere und vor allem effiziente Kommunikation zwischen den beiden Systemen auf die Beine stellen kann. Die Technologien, die einem Entwicklungsteam heutzutage zur Verfügung stehen, sind so mächtig und komplex, dass ein Überblick immer schwerer fällt. Und genau an diesem Punkt soll dieses Buch ansetzen. Es soll die in Abbildung 1.1 dargestellten Technologien grundlegend erklären und sie im weiteren Verlauf des Buches mit anderen Technologien zu mischen versuchen. Am Ende dieses Buches wird eine umfangreichere Anwendung vorgestellt, bei der ein Kunde sich in ein Portal eines Pseudo-Directbrokers einloggt und von hier aus Aktien kaufen bzw. verkaufen kann. Die von dem Directbroker angenommenen Aufträge werden an ein Pseudo-Börsensystem weitergeleitet und dort ausgeführt. Leider fällt es mir in meinen Seminaren immer recht schwer, den Teilnehmern klarzumachen, dass die Entwicklung verteilter, webbasierter Anwendungen interdisziplinär ist, sich also aus verschiedenen Themengebieten zusammensetzt. Kurz gesagt: Wenn jemand z.B. beabsichtigt, ein interaktives Webportal zu entwickeln, wird er nicht allein nur Java anwenden müssen, sondern zumindest auch HTML. Für jedes in diesem Buch behandelte Themengebiet gibt es bereits eine Vielzahl von ausgezeichneten Büchern. Oft ist es so, dass in diesen Büchern das Thema sehr gut erklärt und auseinander genommen, aber die Integration in andere Technologien unterschlagen wird. Weil dieses Buch so etwas wie einen »roten Faden« durch die hier vorgestellten Technologien darstellt, ist es offensichtlich, dass nicht jedes Themengebiet bis in kleinste Detail dargestellt werden kann. Es soll Ihnen aber helfen, die Kernpunkte jeder einzelnen Technologie kennen zu lernen, zu verstehen und mit anderen Themen mischen und kombinieren zu können.
1.2
An wen richtet sich dieses Buch? Nun, diese Frage zu beantworten ist sehr schwierig. Es ist schließlich kein außergewöhnliches Phänomen, dass eine Person über mehr Kenntnisse in einer Sache verfügt als eine andere. Da sich der Inhalt dieses Buches aus einer Vielzahl verschiedener Technologien zusammensetzt, ist es schwierig, eine präzise Zielgruppe zu definieren. Aus diesem Grund wird ein »worst case« an Kenntnisstand angenommen und festgelegt, dass der Leser dieses Buches eine Person ist, die bereits über fundamentale Kenntnisse der Programmiersprache Java verfügt, d.h., es muss ihm bzw. ihr bekannt sein, wie Java-Programme geschrieben werden, was z.B. Streams sind und was Java von anderen Programmiersprachen abhebt. Das Finden einer Zielgruppe, die die Inhalte dieses Buches in der Praxis einsetzen kann, fällt da schon erheblich leichter. Es soll für alle diejenigen eine Unterstützung darstellen, die einer interdisziplinären Aufgabe gegenüberstehen. Z.B. sollen Sie eine Anwendung entwickeln, die einen Browser als grafisches Front-End einsetzt oder eine XML-Schnittstelle für andere Anwendungen zur Verfügung stellt.
12
An wen richtet sich dieses Buch?
Um Ihnen Bücher bestmöglicher Qualität zur Verfügung stellen zu können, bitten der Verlag und ich Sie um Ihr Feedback: Sollten Sie beim Studieren dieser Lektüre einen Fehler (egal welchen) entdecken, etwas für besonders gut gelungen halten, Anmerkungen zu Inhalten dieses Buches haben oder eine Frage, so senden Sie bitte eine E-Mail an [email protected]. Vielen Dank vorab und viel Spaß mit diesem Buch! Torsten Langner, Aachen, April 2002
Kapitel 2 Auffrischen einiger Vorkenntnisse 2.1 2.2 2.3 2.4 2.5
Grundlagen verteilter Systeme Der Aufbau verteilter, webbasierter Anwendungen Entwicklungstechnologien Vorkenntnisse zur Objektorientierung Objektorientierte Programmierung mit Java – Wiederholung
14 15 17 18 22
14
Grundlagen verteilter Systeme
In diesem Kapitel werden bestimmte Themengebiete angesprochen, die zum Verständnis des nachfolgenden Stoffes dienen. Das allgemeine Problem (egal, ob es sich dabei um eine Schulung handelt oder um ein Lehrmittel) ist der unterschiedliche Wissensstand der Leser. So kommt es dazu, dass der eine mehr Erfahrung auf einem bestimmten Gebiet hat als der andere, wobei dieser wiederum in vielen anderen Dingen Kenntnisse aufweist. Aus diesem Grund soll dieses Kapitel in einer Art »Quick-and-dirty«-Manier dem Leser bestimmte Themengebiete erläutern oder ggf. auch zur Wiederauffrischung nahe bringen.
2.1
Grundlagen verteilter Systeme Der grundlegende Unterschied zwischen einem verteilten und einem nicht-verteilten System ist ganz einfach der, dass bei einem verteilten System die zu lösende Aufgabe auf mehrere Rechner aufgeteilt wird. Die Lösung des Problems erfolgt also nicht mehr an einem einzigen Ort, sondern wird von mehreren so genannten Komponenten durchgeführt. Sobald mehrere Komponenten die Lösung eines Problems übernehmen, entsteht ein erhöhter Kommunikationsbedarf. Physisch gesehen existiert eine bestimmte Anzahl an Rechnern, die über ein leistungsfähiges Kommunikationsnetzwerk miteinander verbunden sind. Versinnbildlicht ist ein verteiltes System so etwas wie eine Firma, in der für die verschiedenen Aufgaben entsprechende Abteilungen existieren. So gibt es eine Abteilung, die sich nur um die Buchhaltung kümmert, oder eine andere Abteilung, die den Kundenservice übernimmt. Genauso wie sich eine größere Firma aus verschiedenen Abteilungen zusammensetzt, setzt sich auch ein größeres System aus verschiedenen Komponenten zusammen. In der Firma kommunizieren die Mitarbeiter (Programmteile einer Komponente) direkt oder über das Telefon miteinander. In dem System kommunizieren die einzelnen Komponenten über ein Netzwerk. Dieses Netzwerk ist in den meisten Fällen das Internet oder ein firmeninternes Intranet. Damit die Komponenten untereinander kommunizieren können, muss die Programmiersprache, mit der die Komponenten entwickelt werden, Fähigkeiten aufweisen, die auf möglichst schnelle und einfache Art und Weise eine derartige Kommunikation möglichst plattformübergreifend und programmiersprachenunabhängig auf die Beine stellen können. Sofern die Kommunikation auch auf ein externes System ausgeweitet wird (z.B. wenn das Rechnersystemen eines Autoherstellers das System des Zulieferers kontaktiert und dort eine Bestellung von 2.000 Scheibenwischern aufgibt), muss für das eigene System eine übergreifende Kommunikation entworfen werden. In der IT-Welt gibt eine Vielzahl von Standards und jeder einzelne Urheber glaubt, das Nonplusultra erfunden zu haben. Glücklicherweise kommt es im Zuge erhöhter Kommunikation zwischen Rechnersystemen zu immer mehr Standards, die so kompatibel sind, dass Daten von A nach B über C zu übertragen. Dabei müssen die eigenen Systeme gegenüber unerlaubten Zugriffen abgesichert werden.
Kapitel 2 • Auffrischen einiger Vorkenntnisse
15
Damit sich – als einfachster Fall einer Client/Server-Kommunikation – Herr Müller im Internet die Webseite des Herrn Meier ansehen kann, braucht Herr Müller einen Browser und einen Zugang zum Internet. Herr Meier hatte seine Internetseiten in Form von HTML-Dokumenten und Bildern auf einem Webserver gespeichert, der über eine URL vom Browser des Herrn Müller aufrufbar ist. Dabei kommuniziert der Browser mit dem Webserver über das Protokoll HTTP. Der Client (Browser des Herrn Meier) erfragt dabei Dienstleistungen, die der Server (Webserver des Herrn Müller) zur Verfügung stellt. Die Dienstleistung des Webservers von Herrn Meier ist in diesem Fall lediglich die Übertragung der Dateiinhalte, die vom Client (Browser des Herrn Müller) erfragt werden. Die vom Browser empfangenen Daten werden von diesem interpretiert und visualisiert. Das Kommunikationsprotokoll HTTP nutzt zur eigentlichen Dateiübertragung die Dienste von TCP/IP; Herr Müller und Herr Meier bemerken dies jedoch nicht mehr.
2.2
Der Aufbau verteilter, webbasierter Anwendungen Im Folgenden werden die verteilten Systeme, die über das Internet (bzw. Intranet) kommunizieren und sich dabei überwiegend des Protokolls HTTP bedienen, als »webbasiert« bezeichnet, da der Bezug zu der Kommunikationsplattform hierdurch eindeutiger ist. Erfreulicherweise bietet das Internet nicht nur Herrn Meier die Gelegenheit, sein Haus, sein Auto und seine Frau der Öffentlichkeit zu präsentieren, sondern es ermöglicht auch Anwendungen, die in den B2C1- oder B2B-Bereich einzuordnen sind. Beim B2C-Bereich stellt eine Firma ihren Kunden die Möglichkeit zur Verfügung, Geschäfte über das Internet abzuwickeln. Herr Müller kann z.B. Bücher kaufen oder Aktien ordern. Im B2B-Bereich kommunizieren Systeme zweier oder mehrerer Firmen miteinander, wie bei dem anfänglich erwähnten Autohersteller, dessen System automatisch eine Bestellung bei einem Zulieferer auslöst. Grundlegende Elemente für ein derartiges System – egal, ob es nun im B2B- oder B2CBereich angesiedelt werden kann – sind Ressourcenzugriffe auf Datenbanken oder die Visualisierung von Informationen. Beim Entwurf einer Architektur für ein webbasiertes System lassen sich die Komponenten in verschiedene funktionale Ebenen2 einordnen. Diese unter dem Oberbegriff »N-Tier-Architektur« bekannte Aufteilung gibt an, wie viele funktionale Ebenen in einem gegebenen System existieren. Eine 2-Tier-Architektur beispielsweise kann aus einer Präsentationsebene bestehen, die Informationen einer zweiten Ebene, in der die Anwendungen und Daten existieren, visualisiert. In einer 3-Tier-Architektur kann die zweite Ebene des letzten Beispiels (Anwendungen und Daten) in zwei separate
1 Business-to-Consumer 2 engl.: tiers
16
Der Aufbau verteilter, webbasierter Anwendungen
Ebenen aufgeteilt werden, indem die Anwendungen des Systems auf einem Rechner laufen und die Datenbanken getrennt von diesen auf einem dritten Rechner zur Verfügung stehen3.
Abbildung 2.1:
Architektur und Eigenschaften webbasierter Anwendungen
Bei der Entwicklung webbasierter Anwendungen wird meistens eine 4-TierArchitektur entworfen (siehe Abbildung 2.2), bei der die Anwendung gemäß ihrer Funktionalitäten auf verschiedene Ebenen verteilt wird. Auf Ebene I befinden sich die Clients, also Browser oder irgendwelche andere Präsentationsprogramme. Diese kommunizieren nur mit der Ebene II, in der ein Webserver die Anfragen entgegennimmt. Die Geschäftslogik, z.B. das Berechnen von Tarifen in Versicherungssystemen, wird auf separate Rechner ausgelagert, die sich auf Ebene III befinden. Die Datenbanken, aus denen die Anwendungsserver ihre Informationen beziehen, befinden sich auf Ebene IV. Durch diese Aufteilung ist das System funktional übersichtlich, leichter erweiterbar und einfacher zu warten. Wenn das System in den B2B-Bereich einzuordnen ist und die zugehörige Firma (z.B. eine Bank) über eine Vielzahl von Kunden verfügt, so kann es vorkommen, dass der Webserver in Tier 2 gleichzeitig mehrere tausend Anfragen bearbeiten muss. Die dabei beteiligte Geschäftslogik von Tier 3 kann dann so derart stark in Anspruch genommen werden, dass die Anfragen 3 Dabei muss die Anwendungsebene (2. Ebene) oder Datenbankebene nicht nur jeweils aus einem Rechner bestehen, sondern kann aus mehreren (physischen) Rechnern zusammengesetzt werden.
Kapitel 2 • Auffrischen einiger Vorkenntnisse
17
nicht schnell genug beantwortet werden können. Durch die 4-Tier-Aufteilung ist es dem Entwicklungsteam nun möglich, einen dritten Rechner in Tier 3 einzufügen, sodass die Last auf Ebene III gedrittelt werden kann.4
Abbildung 2.2:
2.3
Klassische 4-Tier-Aufsplittung einer webbasierten Anwendungsarchitektur
Entwicklungstechnologien Zwischen den in Abbildung 2.2 dargestellten Ebenen werden Informationen ausgetauscht. Damit diese Informationen z.B. auf Ebene I von einem Browser visualisiert werden können, werden die Daten, die der Webserver an ihn sendet, mittels HTML formatiert. HTML ist lediglich ein Format zur Beschreibung von Informationen, welches vom Browser erkannt, interpretiert und visualisiert wird. Damit die in dem Browser angezeigte Internetseite einen scheinbar dynamischen Charakter erhält, indem z.B. Eingabeformulare überprüft oder Buttons animiert werden, wird der Code einer vom Browser ausführbaren Skriptsprache mit in das vom Webserver gesendete HTML-Dokument integriert. Diese Skriptsprache ist in
4 Ein weiteres Belastungsbeispiel für Tier 3 ist der Einsatz genetischer Algorithmen. Wenn auf einem Rechner eine Auswertung mithilfe genetischer Algorithmen durchgeführt wird, kann diese schon mal 15 Minuten laufen, wobei alle Kapazitäten des Servers ausgenutzt werden.
18
Vorkenntnisse zur Objektorientierung
der Regel JavaScript, die jedoch nur auf den ersten Blick Ähnlichkeiten mit der Programmiersprache Java aufweist. Damit der Webserver mit den Anwendungsservern oder die Anwendungsserver untereinander kommunizieren können, wird die so genannte Middleware-Technologie eingesetzt, mit deren Hilfe eine sichere und vergleichsweise einfache Entwicklung möglich gemacht wird. Eine bekannte Middleware-Technologie im B2B-Bereich ist CORBA, bei deren Einsatz es sogar möglich wird, Plattformgrenzen und unterschiedliche Programmiersprachen zu überwinden. Der Trend geht immer mehr dazu über, die Informationen zwischen einzelnen Anwendungsservern mit XML zu formatieren und die Dienstleistungen über so genannte »Webservices« zur Verfügung zu stellen. Der Aufbau dieses Buches wurde so gewählt, dass nach einer grundlegenden Einführung in die Client/Server-Technologie die Kommunikation zwischen Rechnern unter Einsatz von Middleware erläutert wird. Anschließend wird die Behandlung und Handhabung von Daten (also die Kommunikation zwischen Ebene III und Ebene IV) aufgegriffen und in bestehende Anwendungen eingebaut. Danach wird die Kommunikation zwischen Ebene I und Ebene II thematisiert und schließlich eine Verbindung zu Ebene III hergestellt.
2.4
Vorkenntnisse zur Objektorientierung In den vielen Seminaren, die ich bisher gegeben habe, hat sich eines besonders herauskristallisiert: Auch vermeintlich »alte Hasen«, die sich mit objektorientierter Programmierung »auskennen«, haben erstaunliche Wissenslücken, was Objektorientierung angeht. Deshalb folgt nun zuerst eine kleine, aber feine Wiederholung dieser Grundlagen.
2.4.1
Beispiel 1: Identitätskonstruktion in grauer Vorzeit Die Grafik in Abbildung 2.3 wurde (wie alle anderen Klassen-Diagramme, die in diesem Buch vorkommen) mithilfe der UML5, einer Design-Vorschrift für objektorientierte Modellierung, entworfen. Sie zeigt die Klasse Object im Paket java.lang, von der alle Klassen, die in Java geschrieben werden, erben. Alle anderen Klassen, die in diesem Diagramm dargestellt sind, befinden sich im Paket buch.beispiel.
5 Unified Modeling Language
Kapitel 2 • Auffrischen einiger Vorkenntnisse
Abbildung 2.3:
19
Das Klassendiagramm für das Beispiel »Personen«
Eine Person besitzt in diesem Beispiel drei öffentliche »Attribute«, die im zugehörigen Quellcode als Variablen mit dem Schlüsselwort public gekennzeichnet sind und im Diagramm durch ein »+« gekennzeichnet sind.6 Eine Person erbt alle öffentlichen Eigenschaften von Object und vererbt alle öffentlichen Eigenschaften an die Kindklassen Mann und Frau. Man spricht auch von Spezialisierungen der Elternklasse. Eine Frau besitzt hier keine lokalen öffentlichen Attribute (außer den von Person geerbten) und verfügt nur über ein privates, also nur für sich selbst erreichbares, zugängliches Attribut alter (weil eine Frau erwartungsgemäß niemals ihr Alter preisgegeben hat). Im Diagramm werden private Attribute mit einem »-« gekennzeichnet. Weiterhin besitzt eine Frau ein geschütztes (protected) Attribut geburtsname, das nur allen von Frau abgeleiteten (»erbenden«) Klassen zur Verfügung steht. Geschützte Attribute werden im Diagramm mit einem »#« versehen. 6 Manche LeserInnen erinnern sich vielleicht noch (wahrscheinlich mit Grausen) an diese seinerzeit kaum zu umgehende bzw. zwingende gesellschaftliche/staatliche/rechtliche Konstruktion oder »Programmierung« der Eigenschaft »Geschlecht« – eine eigentlich biologische Kategorie.
20
Vorkenntnisse zur Objektorientierung
Weiterhin kann man festhalten, dass
2.4.2
•
... ein Sohn ein Mann ist,
•
... eine Tochter eine Frau ist,
•
... eine Tochter eine Person ist,
•
... ein Sohn eine Person ist,
•
... eine Tochter einen Geburtsnamen hat, aber kein Alter,
•
... ein Sohn ein Autokennzeichen besitzt sowie (ebenso wie die Tochter oder die Frau bzw. der Mann) über Vorname, Name und Adresse verfügt,
•
... eine Person ein Objekt ist
•
... und somit ein Mann, eine Frau, ein Sohn und eine Tochter auch Objekte sind.
Beispiel 2: WindowsDrucker
Abbildung 2.4: Klassendiagramm für das Beispiel »WindowsDrucker«
Kapitel 2 • Auffrischen einiger Vorkenntnisse
21
Die Klasse DruckerManager im Paket microsoft.windows2000 besitzt eine öffentliche Methode namens melde_an, die einen Übergabeparameter des Typs WindowsDrucker erwartet und true oder false zurückgibt. Des Weiteren verfügt ein WindowsDrucker über ein – für Außenstehende nicht zugängliches – Attribut stationsID des Typs String und über drei öffentliche Methoden namens drucke(...), getBezeichnung() und setBezeichnung(...). Ein WindowsDrucker ist eine abstrakte Klasse, was bedeutet, dass keine direkte Instanz (= Objekt) eines WindowsDrucker erzeugt werden kann. Um einen WindowsDrucker instanziieren zu können, kann nur eine Kindklasse in ein Objekt verwandelt werden. Eine direkte Kindklasse ist Stylus_Nadeldrucker im Paket epson sowie deren Spezialisierung Stylus_Laserdrucker. Stylus_Laserdrucker verfügt über vier Methoden, die alle die Bezeichnung drucke haben, sich aber durch die Kombination und den Typ der Übergabeparameter unterscheiden. Über Stylus_Nadeldrucker wird zwar die Methode drucke(text: String) geerbt, aber in Stylus_Laserdrucker überschrieben und auf Quellcodeebene mit neuem Inhalt gefüllt. Sei nun ferner folgendes Programm gegeben: Listing 2.1:
buch\beispiel\Druckertest.java
1: import ...; 2: 3: public class Druckertest extends Object { 4: 5: private static void dummy_1 ( Stylus_Nadeldrucker drucker ) { 6: drucker.drucke ( "Testausgabe 1" ); 7: } 8: 9: private static void dummy_2 ( WindowsDrucker y ) { 10: drucker.drucke ( "Testausgabe 2" ); 11: } 12: 13: private static void dummy_3 ( Stylus_Nadeldrucker drucker ) { 14: drucker.drucke ( "Testausgabe 3" ); 15: drucker.drucke ( 17 ); 16: drucker.drucke ( 12.32 ); 17: drucker.zerstoere_Dich_selbst (); dummy_2 ( drucker ); 18: 19: } 20: 21: 22: public static void main (String args[]) { // neuen Druckermanager initialisieren: 23: 24: DruckerManager dm = new DruckerManager(); 25: // neue Drucker initialisieren: 26: 27: Stylus_Nadeldrucker d_1 = new Stylus_Nadeldrucker(); 28: Stylus_Laserdrucker d_2 = new Stylus_Laserdrucker();
22
OOP-Programmierung mit Java – Wiederholung
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: } 45: }
Deskjet_690C d_3 = new Deskjet_690C(); // Anmeldungen am Druckertreiber: dm.melde_an ( d_1 ); dm.melde_an ( d_2 ); dm.melde_an ( d_3 ); dummy_1 dummy_1 dummy_1 dummy_2 dummy_2 dummy_2 dummy_3 dummy_3 dummy_3
( ( ( ( ( ( ( ( (
d_1 d_2 d_3 d_1 d_2 d_3 d_1 d_2 d_3
); ); ); ); ); ); ); ); );
So lässt sich anhand dieses Beispiels feststellen, dass •
die Befehle in den Zeilen 15 und 16 scheitern, da die drucke-Methoden mit den Übergabeparametern des Typs double und int nicht in der Klasse Stylus_Nadeldrucker definiert sind.
•
der Befehl in Zeile 33 scheitert, da d_3 des Typs Deskjet_690C ist, die melde_an-Methode von DruckerManager jedoch eine Spezialisierung von WindowsDrucker erwartet. Deskjet_690C hätte von WindowsDrucker erben müssen, damit dieser Funktionsaufruf funktioniert.
2.5
•
der Befehl in Zeile 37 scheitert, da die dummy_1-Methode einen Stylus_Nadeldrucker oder einen von ihm abgeleiteten Typ erwartet und Deskjet_690C kein Stylus_Nadeldrucker ist. Analog verhält es sich mit der Zeile 43 und der dummy_3-Methode.
•
der Befehl in Zeile 40 scheitert, da die dummy_2-Methode einen WindowsDrucker oder einen von ihm abgeleiteten Typ erwartet, und Deskjet_690C kein WindowsDrucker ist.
•
bei der Übergabe von d_2 an dummy_1, dummy_2 und dummy_3 jeweils in deren Zeilen 6, 10 und 14 der Code der drucke-Methode ausgeführt wird, der in der Klasse Stylus_Laserdrucker neu definiert wurde.
Objektorientierte Programmierung mit Java – Wiederholung In diesem Abschnitt werden nun ausgewählte Themen aufgegriffen, die noch einmal kurz die Kenntnisse auffrischen sollen, wie mit Java objektorientiert programmiert werden kann.
Kapitel 2 • Auffrischen einiger Vorkenntnisse
2.5.1
23
Schnittstellen und Vererbungsmechanismen Wenn man das Druckerbeispiel aus Abschnitt 2.4.2 in die Realität umsetzt, so erhält man die im Folgenden beschriebenen Codefragmente. Ein WindowsDrucker (Listing 2.2) wird durch das Schlüsselwort abstract als abstrakte Klasse gekennzeichnet, wodurch eine Instanziierung von WindowsDrucker nicht erlaubt wird. Damit ein WindowsDrucker in ein Objekt verwandelt werden kann, muss erst eine von ihm abgeleitete Kindklasse instanziiert werden. WindowsDrucker erbt automatisch (ohne weitere Angaben) von der Klasse java.lang.Object. Listing 2.2:
microsoft\windows2000\WindowsDrucker.java
public abstract class WindowsDrucker { private String stationsID; public void drucke ( String text ) { System.out.println ( stationsID + ": " + text ); } ... }
Der Stylus_Nadeldrucker in Listing 2.3 hingegen kann sehr wohl instanziiert werden. Durch die explizite Angabe des Schlüsselworts extends wird dem Compiler angegeben, dass Stylus_Nadeldrucker von WindowsDrucker erbt, also eine Spezialisierung eines WindowsDruckers darstellt. Listing 2.3:
Epson\Stylus_Nadeldrucker.java
public class Stylus_Nadeldrucker extends WindowsDrucker { private String seriennummer; private void zerstoere_dich_selbst () { //bumm! } ... }
In Java ist es nur möglich, direkt von einer Elternklasse zu erben. Anders als z.B. bei C++ wird somit eine übersichtliche Vererbungslinie geschaffen. Jedoch hat sich gezeigt, dass eine »Ein-Eltern«-Hierarchie in bestimmten Lebenssituationen Nachteile mit sich bringt. Daher hat man rein abstrakte Klassen, so genannte Interfaces oder zu Deutsch auch Schnittstellen, eingeführt, die nur über Konstanten und Methodendefinitionen verfügen, nicht aber über Implementierungen (Methodenkörper). Ein Beispiel für ein derartiges Interface ist die Schnittstelle Runnable aus Listing 2.4.
24
OOP-Programmierung mit Java – Wiederholung
Listing 2.4:
java\lang\Runnable.java
public interface Runnable { public void run (); }
Wie in Listing 2.4 zu erkennen ist, definiert das Interface eine Methode namens run, die lediglich definiert wird und damit keinen Methodenkörper besitzt. Wenn eine Klasse alle Eigenschaften von Runnable besitzen soll, muss sie dieses Interface »implementieren«, d.h., mit einem Körper versehen (Listing 2.5). Listing 2.5:
A.java
public class A implements Runnable { public void run () { System.out.println ( "Ich wurde implementiert!" ); } }
Wie in Listing 2.5 zu sehen, implementiert A alle Methoden von Runnable (eigentlich ja nur run) und ist somit auch Runnable. Die Antwort auf die Frage: »Ist ‚A’ denn ‚Runnable’?« lautet: »Ja!«. Durch den implements-Befehl wird dem Entwickler gewissermaßen vorgeschrieben, dass er allen im implementierten Interface deklarierten Methoden seiner Klasse einen Methodenkörper geben muss. Vergisst er einen, beanstandet dies der Compiler direkt bei der Kompilierung. Es ist aber auch möglich, von einer Klasse zu erben und ein oder mehrere Interfaces zu implementieren. Als Beispiel hierzu sei die Klasse B in Listing 2.6 angeführt, die von der Klasse WindowsDrucker erbt und alle Eigenschaften der Schnittstelle Runnable aufweist. Listing 2.6:
B.java
public class B extends WindowsDrucker implements Runnable { public void run () { System.out.println ( "Ich wurde implementiert!" ); } }
Ein letzter wichtiger Punkt bezüglich objektorientierter Programmiertechniken ist das Zurückverwandeln (Casten) von Objekten in abgeleitete Datentypen. Ein beliebtes Hilfsmittel in der Java-Entwicklung ist die Benutzung einer linearen Liste, die in Form eines java.util.Vector zur Verfügung steht. Diese Klasse
Kapitel 2 • Auffrischen einiger Vorkenntnisse
25
stellt eine Methode addElement zur Verfügung, die als Übergabeparameter etwas des Typs java.lang.Object erwartet und ans Ende der Liste anfügt. Um die Werte auslesen zu können, kann z.B. die elementAt-Methode benutzt werden, der ein Integer-Wert übergeben werden muss (die Position des auszulesenden Objekts) und die ein java.lang.Object zurückliefert. Gegeben sei nun das folgende Codefragment (Listing 2.7), das in einem beliebigen Programm stehen könnte: Listing 2.7:
XYZ.java
public void eins_zwei_drei () { // einen Vector erzeugen: Vector v = new Vector (); // einen String hineinlegen: v.addElement ( "Ich bin ein String" ); // einen Drucker erzeugen und hineinlegen: Stylus_Laserdrucker drucker = new Stylus_Laserdrucker (); v.addElement ( drucker );
//-----------------> Auslesen aus dem Vector: Object o = v.elementAt ( 0 ); if ( o instanceof String ) { // Casten ist möglich, da o eine Instanz der Klasse String: String s = (String) o; } Object p = v.elementAt ( 1 ); if ( p instanceof WindowsDrucker ) { // p ist ein WindowsDrucker! WindowsDrucker win_drucker = (WindowsDrucker) p; win_drucker.drucke ( "... Hallo!" ); } if ( p instanceof Stylus_Nadeldrucker ) { // p ist aber auch ein Nadeldrucker! Stylus_Nadeldrucker nadel_drucker = (Stylus_Nadeldrucker) p; nadel_drucker.drucke ( "... Hallo!" ); nadel_drucker.zerstoere_dich_selbst (); } if ( p instanceof Stylus_Laserdrucker ) { // p ist aber auch ein Laserdrucker!
26
OOP-Programmierung mit Java – Wiederholung
Stylus_Laserdrucker laser_drucker = (Stylus_Laserdrucker) p; laser_drucker.drucke ( "... Hallo!" ); laser_drucker.drucke ( 23.23 ); laser_drucker.zerstoere_dich_selbst (); } }
Dabei werden zunächst zwei Objekte (ein String und ein Stylus_Laserdrucker) in einen Vector eingefügt und anschließend wieder ausgelesen. Durch das instanceofSchlüsselwort wird geprüft, ob das von elementAt zurückgelieferte Object eine Instanz z.B. der Klasse String ist. Wenn dies der Fall ist, kann gecastet werden. Da das in p abgelegte Object in Wirklichkeit ein Stylus_Laserdrucker ist, liefert die instanceof-Abfrage in den if-Anweisungen immer ein true zurück, da ein Laserdrucker von einem Nadeldrucker und dieser wiederum von einem WindowsDrucker erbt. Nach dem Casten, z.B. auf einen Stylus_Nadeldrucker, können nur Methoden bei der Variable nadel_drucker aufgerufen werden, die in der Klasse von Stylus_Nadeldrucker definiert sind. Obwohl es sich bei dem Objekt tatsächlich um einen Laserdrucker handelt, können trotzdem nur die Methoden des gecasteten Typs aufgerufen werden.
2.5.2
Quasi-parallele Ausführung mittels Threads Mit der Programmiersprache Java ist es auf wunderbar einfache Art und Weise möglich, nebenläufige Prozesse, so genannte Threads, quasi-parallel ausführen zu können. Möglich macht dies ein in die Virtual Machine von Java eingebauter Thread-Mechanismus, der alle Threads Ressourcen schonend ausführt. Durch diesen Mechanismus ist es z.B. in Word möglich, während des Druckvorgangs ein Dokument zu bearbeiten. Um einen solchen Thread zu erzeugen, können zwei verschiedene Wege gewählt werden, die nun erläutert werden. Direkte Vererbung durch die Klasse Thread Damit eine Klasse Thread-fähig gemacht wird, muss sie – wie ein Laserdrucker von einem Nadeldrucker – von der Elternklasse java.lang.Thread erben: Listing 2.8:
Thread_Nummer_1.java
public class Thread_Nummer_1 extends Thread { public void run () { while ( true ) { // Endlosschleife: try { System.out.println ( "Nummer 1" ); // schlafen für drei Sekunden:
Kapitel 2 • Auffrischen einiger Vorkenntnisse
27
sleep ( 3000 ); } catch (Exception e) { // es ist ein Fehler aufgetreten, egal... } } } }
Innerhalb der run-Methode in Listing 2.8 wird der Mechanismus implementiert, der während der Thread-Laufzeit ausgeführt werden soll. In diesem Beispiel wird der Thread Thread_Nummer_1 so lange ausgeführt, bis das komplette, zugehörige Java-Programm beendet wird und zur Konsole zurückkehrt. Obwohl es sich hierbei um eine Endlosschleife handelt, wird diese vom Thread-Mechanismus der Virtual Machine zu gegebener Zeit unterbrochen, um einem anderen Thread die (Weiter-)Ausführung zu ermöglichen. Durch den sleep-Aufruf wird ein Ressourcen schonendes Ruhen des Threads veranlasst. Implementierung des Interfaces Runnable Ein anderer Weg zur Gestaltung eines Threads ist die Implementierung des Interfaces Runnable, das bereits in Listing 2.4 aufgetaucht ist. In Listing 2.9 wird das Interface implementiert und die run-Methode aus Listing 2.8 übernommen. Zusätzlich wurde in die Klasse Thread_Nummer_2 ein privates Attribut runner des Typs Thread übernommen, das über die Methoden fang_an () bzw. hoer_auf () verändert werden kann. Listing 2.9:
Thread_Nummer_2.java
public class Thread_Nummer_2 implements Runnable { private Thread runner; public void fang_an () { if ( runner == null ) { runner = new Thread ( this ); runner.start (); } } public void hoer_auf () { runner = null; } public void run () { while ( true ) { // Endlosschleife: try {
28
OOP-Programmierung mit Java – Wiederholung
System.out.println ( "Nummer 2" ); // schlafen für drei Sekunden: runner.sleep ( 3000 ); } catch (Exception e) { // es ist ein Fehler aufgetreten, egal... } } } }
2.5.3
Ausführung der beiden Varianten Das in Listing 2.9 abgedruckte Programm erzeugt Objekte beider Varianten und startet deren jeweilige Threads. Listing 2.10: Thread_Ausfuehrung.java public class Thread_Ausfuehrung { public static void main (...) { // Erzeugung der ersten Variante: Thread_Nummer_1 t1 = new Thread_Nummer_1 (); // Erzeugung der zweiten Variante: Thread_Nummer_2 t2 = new Thread_Nummer_2 (); // Start beider Varianten: t1.start (); t2.fang_an (); } }
Die von Thread_Ausfuehrung produzierte Konsolenausgabe sieht wie folgt aus: c:\> java Thread_Ausfuehrung Nummer Nummer Nummer Nummer ...
1 2 1 2
Kapitel 2 • Auffrischen einiger Vorkenntnisse
29
Wie der Output zeigt, laufen beide Threads quasi-parallel ab. Eine wichtige Anmerkung, die in diesem Zusammenhang gemacht werden muss, ist das Thema »Monitore«. Angenommen, zwei Threads versuchen zur selben Zeit auf einem Konto 1.000 € zu buchen bzw. den Gesamtbetrag auszulesen. Um diese quasiparallelen Zugriffe in eine Warteschlange einzufügen, muss das Schlüsselwort synchronized vor die entsprechende Methode geschrieben werden. Beispiel: public synchronized void buche ( double betrag ) { ... }
Der Thread-Mechanismus ist insofern wichtig, als er im nachfolgenden Kapitel über Client/Server-Technologien mehrfach Anwendung finden wird.
Kapitel 3 Socket-Kommunikation zwischen verteilten Anwendungen 3.1 3.2 3.3 3.4 3.5 3.6 3.7
Die Kommunikation über TCP/IP Beispiel 1: Ein einfacher Server Ein wiederverwendbares Framework Beispiel 2: Verwendung des Frameworks Beispiel 3: Eine komplexere Kommunikationsstruktur implementieren Erweiterung des Frameworks Klassen dynamisch über das Netz laden
32 32 41 45 52 60 69
32
3.1
Die Kommunikation über TCP/IP
Die Kommunikation über TCP/IP Die Kommunikation über Sockets bietet dem Entwickler eine einfache und schnell zu implementierende Möglichkeit, eine Client-/Server-Struktur mittels Java auf die Beine zu stellen. Ein Socket ist die Kombination aus einer IP-Adresse und einer Portnummer. Eine IP-Adresse ist 32 Bits lang und wird durch vier Dezimalzahlen (A.B.C.D) repräsentiert. Dabei können A, B, C und D Werte zwischen 0 und 255 annehmen und müssen durch einen Punkt voneinander abgegrenzt werden. Z.B. ist 204.71.177.66 die IP-Adresse, die im Volksmund auch mit dem Rechnernamen www.yahoo.com bezeichnet wird. Rechnernamen werden im Allgemeinen auch als »Host-Namen« bezeichnet. Die Schreibweise für einen Socket ist die IP-Adresse, gefolgt von einem Doppelpunkt und der Portnummer, also A.B.C.D:PortNum. Beispielsweise wäre 204.71.177.66:80 die Adresse des Yahoo-Webservers. Ein Port repräsentiert eine logische Nummer eines Netzwerk-Service, der auf dem jeweiligen Server-Rechner läuft. Das bedeutetet, dass jede Server-Anwendung, die auf einem Netzwerkrechner ausgeführt wird, einen Service für andere im Netz existente Rechner darstellt.
3.2
Beispiel 1: Ein einfacher Server Das erste Beispiel zum Thema Socket-Kommunikation soll die Grundlagen des Austauschs von Daten über ein Netzwerk verdeutlichen. Dabei ist es zunächst egal, ob diese Minimal-Implementierung auf zwei Rechnern läuft oder nur auf einem. Eine Client/Server-Architektur ist zwar eigentlich dafür gedacht, dass der Client auf einem Rechner läuft und der Server auf einem anderen, aber zu Testund Entwicklungszwecken ist es durchaus üblich, das Projekt auf einem Rechner zum Laufen zu bringen. Ein typisches Kommunikationsszenario ist in Abbildung 3.1 dargestellt. Dabei nimmt der Client zunächst Kontakt mit dem Server auf, indem er eine Verbindung zu diesem herstellt [1]. Sobald die Verbindung aufgebaut worden ist, kann die eigentliche Kommunikation, also der gegenseitige Austausch von Daten [2], durchgeführt werden. Der Datenaustausch in diesem Ausgangsbeispiel soll so gestaltet sein, dass der Client zwei Anfragen in Form von Strings an den Server sendet und jeweils eine Antwort erhält. Der Kontakt zwischen beiden Parteien soll für die Dauer des Kommunikationsablaufs aufrechterhalten bleiben.
Kapitel 3 • Socket-Kommunikation zwischen verteilten Anwendungen
Abbildung 3.1:
3.2.1
33
Typisches Kommunikationsszenario
Realisierung des Clients Um eine Verbindung zum Server mittels Sockets in Listing 3.1 herzustellen, muss ein neues Objekt der Klasse java.net.Socket erzeugt werden, wobei dem Konstruktor zum einen die IP-Adresse und zum anderen die Portnummer übergeben wird: Socket server = new Socket ( "123.14.36.7", 9999 );
Bei der Konstruktion dieses Objekts des Typs Socket können primär zwei mögliche Fehler auftreten: Wird die übergebene IP-Adresse nicht gefunden, wirft der Konstruktor eine java.net.UnknownHostException aus. Ist es unmöglich mit dem Server zu kommunizieren, wird eine allgemeine java.io.IOException erzeugt. Nachdem die Verbindung erfolgreich aufgebaut wurde, müssen Streams eingesetzt werden, um die Kommunikation abzuarbeiten. Dies funktioniert nach dem gleichen Muster wie beim Zugriff auf eine Datei. Um in eine Textdatei schreiben zu können, ist es notwendig, erst das Wie mittels eines Streams festzulegen und anschließend diesen Stream mit dem Wo-Stream zu initialisieren. Das Wo ist in diesem Fall kein FileOutputStream, sondern ein java.io.OutputStream, der von der Variable server bereitgestellt wird: 29: PrintWriter out = 30: new PrintWriter ( server.getOutputStream() );
34
Beispiel 1: Ein einfacher Server
Analog verhält es sich mit dem lesenden Stream: 31: BufferedReader in = new BufferedReader ( 32: new InputStreamReader ( 33: server.getInputStream() 34: ) 35: );
Diese beiden Streams können nun dazu benutzt werden, um das Kommunikationsszenario aus Abbildung 3.1 abzuarbeiten. Zuerst ist es der Client, der eine Anfrage an den Server richtet (»Hallo Server«): 38: String befehl_1 = "Hallo Server"; 41: out.println ( befehl_1 ); 42: out.flush();
Wichtig ist hierbei, dass beim OutputStream out die Methode flush() aufgerufen wird, damit der auf der Gegenseite lesende Stream weiß, dass der Output damit fürs Erste fertig ist. Wurden die Daten versandt, sendet der Server einen String als Antwort, der nun eingelesen wird (»Hallo Client«): 45: String antwort_1 = in.readLine();
Die zwei letzten Senden/Empfangen-Szenarien verlaufen analog zu diesen zwei. Insgesamt ergibt sich folgender Code: Listing 3.1: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
buch\kapitel3\tcp_ip\beispiel1\DemoClient_1.java
/* * DemoClient_1.java */ package buch.kapitel3.tcp_ip.beispiel1; // -----> Import der benötigten Klassen: import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; // Import der benötigten Klassen: import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.IOException; import java.net.Socket; import java.net.ServerSocket; import java.util.Date; import java.text.SimpleDateFormat; // "Hallo Server" String hallo = in.readLine(); wln ("Client sagt: " + hallo); String antwort_1 = "Hallo Client!"; wln ("sende: " + antwort_1); // ==> "Hallo Client" out.println ( antwort_1 ); out.flush(); // ==> "Wieviel Uhr ist es?" String frage = in.readLine(); wln ("Client fragt: " + frage); String antwort_2 = aktuelle_Uhrzeit(); wln ("sende: " + antwort_2); // ==> "13:32:22" out.println ( antwort_2 ); out.flush();
38
Beispiel 1: Ein einfacher Server
70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: }
// Kommunikationskanal schließen: in.close(); out.close(); anfrage.close(); }
/** * @args: Kommandozeilen-Parameter */ public static void main (String args[]) { // Server starten: try { new DemoServer_1 ( 9999 ); } catch (IOException e) { // Ein I/O-Fehler ist aufgetreten: System.out.println ("Fehler: " + e); } } //----------------------------------> Hilfsroutinen: private void wln (String s) { System.out.println ( s ); } private String aktuelle_Uhrzeit () { Date jetzt = new Date(); SimpleDateFormat formatter = new SimpleDateFormat ("HH:mm:ss"); // verlangsamt die Antwort künstlich um 10 Sek. try { Thread.sleep( 10000 ); } catch (InterruptedException e) {} // Rückgabe: return formatter.format( jetzt ); }
Kapitel 3 • Socket-Kommunikation zwischen verteilten Anwendungen
3.2.3
39
Klassendokumentationen java.net.Socket Konstruktor
Beschreibung
Socket (InetAddress ia, int port)
Erzeugt einen Stream-basierten Socket und verbindet ihn mit der spezifizierten IPAdresse und Port-Nummer.
Socket (String host, int port)
Erzeugt einen Stream-basierten Socket und verbindet ihn mit der spezifizierten IPAdresse und Port-Nummer.
Tabelle 3.1:
Konstruktoren der Klasse java.net.Socket (Auszug)
Rückgabetyp
Methoden
void
close()
Schließt diesen Socket.
InetAddress
getInetAddress()
Gibt die Adresse zurück, mit der der Socket in Verbindung steht.
InputStream
getInputStream()
Gibt den Input-Stream dieses Sockets zurück.
InetAddress
getLocalAddress()
Gibt die lokale Adresse zurück, an die der Socket gebunden ist.
int
getLocalPort()
Gibt den lokalen Port zurück, an den der Socket gebunden ist.
OutputStream
getOutputStream()
Gibt den Output-Stream dieses Sockets zurück.
Tabelle 3.2:
Beschreibung
Methoden der Klasse java.net.Socket (Auszug)
java.net.ServerSocket Konstruktor
Beschreibung
ServerSocket (int port)
Erzeugt einen serverseitigen Socket, der an einen übergebenen Port gebunden ist.
Tabelle 3.3:
Konstruktoren der Klasse java.net.ServerSocket (Auszug)
Rückgabetyp
Methoden
Beschreibung
Socket
accept()
Wartet so lange, bis eine Verbindung zu diesem Server-Socket hergestellt wurde und akzeptiert diese.
Tabelle 3.4:
Methoden der Klasse java.net.ServerSocket (Auszug)
40
Beispiel 1: Ein einfacher Server
Rückgabetyp
Methoden
Beschreibung
void
close()
Schließt diesen Socket.
InetAddress
getInetAddress()
Gibt die lokale Adresse dieses ServerSockets zurück.
int
getLocalPort()
Gibt den lokalen Port zurück, an den der Server-Socket gebunden ist.
Tabelle 3.4:
3.2.4
Methoden der Klasse java.net.ServerSocket (Auszug) (Forts.)
Der Server läuft
Abbildung 3.2: Visualisierter Kommunikationsverlauf zwischen Client und Server
Das Starten von Client_1 und Server_1 führt zu dem in Abbildung 3.3 angegebenen Output, der durch den in Abbildung 3.2 dargestellten Kommunikationsverlauf zwischen Client und Server zustande kommt. Dieser Server kann jedoch nur einen Client zu einem Zeitpunkt bedienen. Für den Fall, dass ein weiterer Client Kontakt zum Server aufnimmt, wird dessen Anfrage durch die ServerSocket-Methode accept() in eine Warteschlange eingereiht. Ein
Kapitel 3 • Socket-Kommunikation zwischen verteilten Anwendungen
41
solcher Server ist durchaus annehmbar, aber nicht »state of the art«. Es muss also ein Server entwickelt werden, der quasi-parallel mehrere Clients gleichzeitig bedienen kann. Unter Zuhilfenahme der Thread-Technologie ist dies auf eine Ressourcen schonende Art und Weise realisierbar. Hier muss lediglich für jeden kommunikationswilligen Client ein Thread erzeugt werden, der das Kommunikationsschema abarbeitet.
Abbildung 3.3:
Kommunikationsverlauf bei einem 1-Client-Server (ein Server, der nur einen einzigen Client zu einen Zeitpunkt bedienen kann)
Zu diesem Zweck wird im Folgenden ein Framework erarbeitet, welches das Erstellen eines Servers enorm vereinfachen soll. Dabei muss darauf geachtet werden, dass ein Modulkonzept (= Komponentenkonzept) eingehalten wird, wodurch die erstellten Klassen wiederverwendbar und leicht modifizierbar werden.
3.3
Ein wiederverwendbares Framework
3.3.1
Erstellung der Elternklasse für einen unbegrenzten Server Wie bereits erwähnt, muss ein guter Server mehrere Clients gleichzeitig bedienen können. Die Kommunikation soll dabei jeweils in einem Thread ausgelagert werden. Diese Tatsache liefert den Grundstein für eine Vorlage zu einem unbegrenzten Server, also einem Server der unbeschränkt viele Anfragen (quasi-parallel) bedienen kann.
42
Ein wiederverwendbares Framework
Abbildung 3.4 stellt den Zusammenhang und den Aufbau der im Folgenden besprochenen Klassen dar. Ausgehend von den aus Beispiel 1 erworbenen Erkenntnissen bezüglich des Aufbaus eines Servers soll die Klasse Unbegrenzter_Server (Listing 3.3) analog zu diesem Beispiel zusammengesetzt werden. Diese Klasse ist abstrakt, d.h., es sollen keine Instanzen des Typs Unbegrenzter_Server erzeugt werden können. Sie soll dazu dienen, dass nur Objekte von selbstgeschriebenen, abgeleiteten Klassen wie z.B. DemoServer_2 erzeugt werden können. Die Klasse Unbegrenzter_Server erbt direkt von der Klasse Thread und ist somit auch ein Thread. Die Klasse erhält ein privates Attribut des Typs ServerSocket und eine ableitbare Methode namens bearbeite_Anfrage, die als Übergabeparameter einen Socket erhält. Die ebenfalls ableitbare und abstrakte Methode neue_Anfrage soll gültige Instanzen einer Kindklasse des Typs Anfragebearbeitung zurückzugeben, die mit dem übergebenen Socket initialisiert wurden. Insgesamt ergibt sich folgender Quellcode: Listing 3.3: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
buch\kapitel3\tcp_ip\beispiel1\vorlagen\Unbegrenzter_Server.java
/* * Unbegrenzter_Server.java */ package buch.kapitel3.tcp_ip.vorlagen; //----> Imports: import java.io.IOException; import java.net.Socket; import java.net.ServerSocket; // DemoServer_4 starten:" 25: ); 26: 27: // DemoServer_4 starten: 28: Thread t = (Thread) demoServer_4.newInstance(); 29: t.start();
Kapitel 3 • Socket-Kommunikation zwischen verteilten Anwendungen
30: 31: 32: 33: }
83
}
Abbildung 3.16: Ausführung beider Programme
Bei der Ausführung der Programme empfiehlt es sich, auf Clientseite die Klassen Netzwerk_ClassLoader und Netzwerk_Tester in ein jar-Archiv zu packen und die
Programme aus diesem Archiv heraus zu starten, wie in Abbildung 3.16 angedeutet.
Kapitel 4 Middleware als die Kommunikationsalternative 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10
Eine Vorüberlegung Middleware heißt die Alternative! Remote Method Invocation (RMI) CORBA Interoperabilität zwischen CORBA- und RMI-Systemen CORBA: Ansichten und Meinungen Verwendete Tools JNDI: Namens- und Verzeichnisdienste Sicherheitsberechtigungen in Java: Java Security Zusammenfassung
86 101 107 120 146 167 169 171 175 187
86
4.1
Eine Vorüberlegung
Eine Vorüberlegung In Kapitel 3 wurde in mehreren Varianten beschrieben, wie zwei oder mehrere Rechner miteinander kommunizieren können. Alle vorgestellten Architekturen hatten Eines gemeinsam: Bevor ein Server und ein Client auf die Beine gestellt werden konnten, musste ein Kommunikationsschema kreiert werden.1 Bei der Erstellung eines solchen Schemas muss als Erstes festgelegt werden, was der Server »können« soll, d.h., welche Dienste er dem Client zur Verfügung stellen soll. Danach werden die Dienste implementiert. DemoServer_3 wurde so implementiert, dass ein Client genau zwei Dienste in Anspruch nehmen konnte. Zum einen konnte der Server die für ihn aktuelle Uhrzeit zurückgeben und zum anderen den Inhalt eines vom Client angefragten Verzeichnisses senden. Aber das alleinige Schreiben dieser zwei Funktionalitäten hat nicht ausgereicht, um die Kommunikation zu realisieren. In der kommuniziere-Methode der Klasse Bearbeitung_3 musste durch das Verschachteln von if-Abfragen sozusagen »erkannt« werden, was der Client überhaupt wollte. Leider reichte das allein auch nicht aus, um von einer echten Kommunikation sprechen zu können, da auch noch programmtechnisch abgebildet werden musste, in welcher Reihenfolge welche Daten welchen Typs vom Client gesendet und in welcher Reihenfolge welche Daten welchen Typs vom Server an den Client wieder zurückgesendet wurden. Also alles in allem eine komplexe Struktur. Im Bereich der Qualitätssicherung sind verschachtelte if-Abfragen der Albtraum vieler Softwaretester und im Allgemeinen eine beliebte Fehlerquelle. Aus diesem Grund soll in dieser Vorüberlegung eine »Vereinheitlichung« des Datenaustauschs angestrebt werden. In der Praxis wird diese Vereinheitlichung auch als »Protokoll« bezeichnet und später in Kapitel 7 im Zusammenhang mit SOAP wieder aufgegriffen. Die Idee, die hinter dem Ganzen steckt, ist in Abbildung 4.1 abgebildet. Der Client soll in die Lage versetzt werden, Methoden auf dem Server aufzurufen und ggf. Rückgabewerte vom Server zu empfangen. Das hierbei angewendete Know-how basiert zu 96% auf den Inhalten des dritten Kapitels und wird nur durch kleinere Add-ons erweitert. Dies soll bedeuteten, dass die in diesem Teilabschnitt angewendeten Vorgehensweisen beispielsweise denen eines DemoServer_3 gleichen und es keine neuen Vorlagen wie die Server-Bearbeitung mehr gibt. Die restlichen 4% zusätzlich benötigten Wissens werden in dem nachfolgenden Exkurs zum Thema »Methodenaufrufe einmal anders« abgehandelt.
1 In der Fachsprache bedeutet das, dass ein Protokoll vereinbart werden musste.
Kapitel 4 • Middleware als die Kommunikationsalternative
Abbildung 4.1:
4.1.1
87
Entfernte Methoden auf dem Server aufrufen
Ein kurzer Exkurs: Methodenaufrufe einmal anders Java ist durch so genannte »Reflection Classes« in der Lage, Methoden eines Objekts gewissermaßen in Textform aufzurufen. Jede in Java geschriebene Klasse erbt direkt oder indirekt von der Klasse java.lang.Object. Diese Klasse besitzt die Methode getClass(), mit der der Zugriff auf die Klasse des Objekts ermöglicht wird. Wird die getClass-Methode z.B. bei einem Objekt des Typs String aufgerufen, so wird als Referenz die String-Klasse zurückgegeben. Rückgabetyp
Methodensyntax
Beschreibung
Class
forName (String className)
Gibt eine Referenz auf eine Klasse zurück, die in Textform an diese statische Methode übergeben wurde. Beispiel: com.sun.jdbc.odbc.JdbcOdbcDriver
Constructor
Tabelle 4.1:
getConstructor ( Class[] paramTypes )
Durch Aufruf dieser Methode bei einem Class-Objekt wird versucht innerhalb der Klasse einen Konstruktor zu finden, der die übergebenen Parameterkombinationen (paramTypes) besitzt.
Methoden der Klasse java.lang.Class (Auszug)
88
Eine Vorüberlegung
Rückgabetyp
Methodensyntax
Beschreibung
Constructor[]
getConstructors ()
Wie zuvor liefert diese Methode Referenzen auf alle Konstruktoren der Klasse, jedoch ohne Vorauswahl.
Method
getDeclaredMethod ( String methodName, Class[] paramTypes )
Genau wie bei getConstructor (paramTypes), jedoch mit dem Unter-
getDeclaredMethods ()
Analog zu getConstructors()
Method[]
Tabelle 4.1:
schied, dass hier Methoden referenziert werden.
Methoden der Klasse java.lang.Class (Auszug) (Forts.)
Zur Demonstration dient folgendes Beispiel: Listing 4.1: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
Demoklasse.java
//-------> Imports import java.lang.reflect.Method; // Import der benötigten Klassen: import java.io.BufferedReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URL; import java.net.URLConnection; import java.net.Socket; // casten!: String[] real_wert_2 = (String[]) wert_2; // das Array ausgeben: for (int i=0; i Imports: import buch.kapitel3.tcp_ip.vorlagen.Unbegrenzter_Server;
Kapitel 4 • Middleware als die Kommunikationsalternative
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
95
import buch.kapitel3.tcp_ip.vorlagen.Anfragebearbeitung; import java.net.Socket; // richtiges Login */ public String login ( String benutzername, String password ) throws RemoteException;
428
Die Entwicklung der einzelnen Teilsysteme
21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: }
public void plaziere_Order ( String sessionID, String wkn, int anzahl, double kurs ) throws RemoteException; public Order[] gib_Orderbuch ( String sessionID ) throws RemoteException; public Order[] gib_Depotbestand ( String sessionID ) throws RemoteException; /* Listet alle handelbaren Aktien auf */ public Wertpapier[] gib_Aktienliste ( String sessionID ) throws RemoteException; public Realtimekurs gib_letzten_Kurs ( String sessionID, String wkn ) throws RemoteException; public Realtimekurs[] gib_alle_Kurse ( String sessionID, String wkn ) throws RemoteException; public Wertpapier gib_Wertpapier ( String sessionID, String wkn ) throws RemoteException;
Der gesamte Code der dieses Interface implementierenden Klasse Kundenserver umfasst ganze 637 Zeilen. Daher werden an dieser Stelle lediglich einige ausgewählte Punkte herausgegriffen und erläutert. Die Klasse Kundenserver (siehe Listing 8.2) verfügt über die vier nachfolgenden privaten Attribute: 42: 43: 44: 45:
private private private private
Boersensystem XEVA = null; SessionManager_Interface sessionManager; DatenbankManager db; Connection verbindung;
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
429
Dabei repräsentiert die Variable XEVA des Typs Boersensystem den Zugang zum XEVA-Börsensystem, das in der verbinde_zu_XEVA-Methode über CORBA kontaktiert wird. Wie zuvor erwähnt, wurde die Session-Verwaltung des comsofortSystems ausgelagert, sodass ein RMI-Zugriff auf die Session-Verwaltung aus Kapitel 6 im Konstruktor vom Kundenserver hergestellt wird und mittels der Variablen sessionManager referenziert wird. Für sämtliche datentechnischen Operationen, wie das Abfragen von Kundendaten, werden SQL-Befehle auf die Datenbank des comsofort-Systems verwendet, die über die Variable db des Typs DatenbankManager zugänglich sind. Die über diese Variable hergestellte verbindung zur Datenbank wird während der gesamten Lebensdauer des KundenserverObjekts verwendet. Die SQL-Befehle und Datenbankverbindungen werden wie beim XEVA-Boersensystem in einer XML-Datei (buch\endanwendung\comsofort\hauptapplikation\SQL_Informationen.xml) ausgelagert, in der u.a. folgende Informationen enthalten sind: ...
jdbc:odbc:comsofort sun.jdbc.odbc.JdbcOdbcDriver ali baba
SELECT Anrede, Vorname, Name FROM Benutzer WHERE ((Benutzername=?) AND (Passwort=?)) UPDATE Orders SET Status=2 WHERE Status=1 ...
Als Beispiel für die Arbeitsweise des Systems wird die login-Methode der Klasse Kundenserver herausgegriffen und nun erläutert. Über das im Konstruktor von Kundenserver (Listing 8.2) instanziierte Objekt db des Typs DatenbankManager wird der SQL-Befehl mit der Bezeichnung Benutzerdaten eingelesen und seine WHERE-Klausel mit den der login-Methode übergebenen Daten (benutzername und passwort) gefüllt. Anschließend wird der SQLBefehl ausgeführt: 98: 99: 100: 101: 102: 104: 105:
String sql = db.gib_SQL_Befehl ( "Benutzerdaten" ); PreparedStatement pstmt = verbindung.prepareStatement( sql ); pstmt.setString( 1, benutzername ); pstmt.setString( 2, passwort ); ResultSet ergebnis = pstmt.executeQuery();
430
Die Entwicklung der einzelnen Teilsysteme
Falls die durch die Ausführung des SQL-Befehls entstandene Ergebnismenge einen Eintrag besitzt, ist dies die Zutrittsberechtigung für den Kunden. Um später persönliche Daten wie Name oder Vorname wiederverwenden zu können, werden diese Informationen aus der Ergebnismenge herausgelesen und in den Variablen anrede, vorname und nachname abgelegt. Schließlich wird noch das Flag erlaubnis auf true gesetzt: 108: 109: 110: 111: 112: 113: 114:
if ( ergebnis.next() ) { // Benutzer besitzt Legitimation: anrede = ergebnis.getString( 1 ); vorname = ergebnis.getString( 2 ); nachname = ergebnis.getString( 3 ); erlaubnis = true; }
Sofern das erlaubnis-Flag in Zeile 128 den Wert true besitzt, wird über den sessionManager in Zeile 129ff. eine neue Session für diesen soeben eingeloggten Benutzer erzeugt: 128: 130: 131:
if ( erlaubnis ) { String sessionID = sessionManager.erzeuge_neue_Session );
Nach der Erzeugung eines Session-Objekts auf dem vom Kundenserver-System getrennten Session-Verwaltungssystem (siehe Abbildung 8.1) wird das entfernte Session-Objekt über RMI referenziert. Dabei zeigt der Suchpfad in Zeile 136 auf ein lokales Objekt, also ein RMI-Objekt, das sich auf rmi://localhost/ befindet. Wenn das System in der Praxis tatsächlich auf verschiedene physische Rechner verteilt wird, muss dieser Pfad entsprechend um die URL (z.B. rmi:// 192.168.2.23/) erweitert werden: 134: 135: 136: 137:
Session_Interface session = (Session_Interface) Naming.lookup( "sessions/" + sessionID );
Nach einer erfolgreichen Referenz auf die Session des Benutzers, werden die im vorhergehenden SQL-Befehl aus der Datenbank ausgelesenen Informationen in dem Session-Objekt gespeichert: 138: 139: 140: 141:
session.putValue session.putValue session.putValue session.putValue
( ( ( (
"user.UserName", benutzername ); "user.Anrede", anrede ); "user.Vorname", vorname ); "user.Nachname", nachname );
Wenn das erlaubnis-Flag allerdings false ist, also der angegebene Benutzername oder das Passwort nicht in dieser Kombination in der Datenbank auftreten, wird in den Zeilen 153 bis 156 eine RemoteException erzeugt und an den Aufrufer dieser login-Methode zurückgeworfen:
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
153: 154: 155: 156:
431
throw new RemoteException ( "Der Benutzername oder das Passwort ist falsch!" + "Bitte versuchen Sie es erneut..." );
Insgesamt entsteht folgendes (gekürztes) Listing: Listing 8.2:
buch\endanwendung\comsofort\hauptapplikation\Kundenserver.java
1: /* 2: * Kundenserver.java 3: */ 4: 5: package buch.endanwendung.comsofort.hauptapplikation; 6: ... 36: public class Kundenserver 37: extends UnicastRemoteObject 38: implements Kundenserver_Interface 39: 40: { 41: 42: private Boersensystem XEVA = null; 43: private SessionManager_Interface sessionManager; 44: private DatenbankManager db; 45: private Connection verbindung; 46: 83: //-----------------------------> Interface-Methoden: 84: 85: public String login(String benutzername, String passwort) 86: throws RemoteException 87: { 88: w ( 89: "... login ('" + benutzername + 90: "', '" + passwort + "'): " 91: ); 92: 93: String anrede = null, vorname = null, nachname = null; 94: boolean erlaubnis = false; 95: 96: try { 97: // Datenbank abfragen: 98: String sql = db.gib_SQL_Befehl ( "Benutzerdaten" ); 99: PreparedStatement pstmt = 100: verbindung.prepareStatement( sql ); 101: pstmt.setString( 1, benutzername ); 102: pstmt.setString( 2, passwort ); 103:
432
Die Entwicklung der einzelnen Teilsysteme
104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149:
ResultSet ergebnis = pstmt.executeQuery();
if ( ergebnis.next() ) { // Benutzer besitzt Legitimation: anrede = ergebnis.getString( 1 ); vorname = ergebnis.getString( 2 ); nachname = ergebnis.getString( 3 ); erlaubnis = true; } // Ressourcen freigeben: ergebnis.close(); pstmt.close(); } catch (Exception e) { String err = "#0001" + e.getMessage(); wln ( err ); throw new RemoteException ( err ); } /* Datenbankabfrage hat funktioniert: * => Session anlegen oder nicht? */ if ( erlaubnis ) { try { String sessionID = sessionManager.erzeuge_neue_Session ); // Daten in die Session eintragen: Session_Interface session = (Session_Interface) Naming.lookup( "sessions/" + sessionID ); session.putValue ( "user.UserName", benutzername ); session.putValue ( "user.Anrede", anrede ); session.putValue ( "user.Vorname", vorname ); session.putValue ( "user.Nachname", nachname ); wln ( sessionID ); // Rückgabe: return sessionID; } catch ( Exception e ) { String err = "#0004" + e.getMessage(); wln ( err ); throw new RemoteException ( err );
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
150: 151: 152: 153: 154: 155: 156: 157: 158: ...
433
} } else { wln ( "Erlaubnis verweigert!" ); throw new RemoteException ( "Der Benutzername oder das Passwort ist falsch!" + "Bitte versuchen Sie es erneut..." ); } }
Zum Start des Kundenservers sollte wie beim Börsensystem zunächst im Datenbankmanager der Eintrag »comsofort« mit Verweis auf die Testdatenbank durchgeführt werden. Da alle Teilsysteme von comsofort über RMI kommunizieren, muss zunächst der Broker gestartet werden und anschließend der Kundenserver: c:\> start RMIregistry c:\> start java buch.endanwendung.comsofort.hauptapplikation.Starter
Der Chart-Zeichner Eine weitere Komponente des »comsofort«-Systems ist der Chart-Zeichner. Dieser zeichnet Kursverläufe in GIF-Grafiken ein und stellt diese bei Bedarf über RMI als Byte-Array zur Verfügung. Da dieses Thema bisher in diesem Buch (und auch in anderen Büchern) noch nicht aufgegriffen wurde, wird dieser Punkt etwas ausführlicher behandelt. Wie in Abbildung 8.4 dargestellt, publiziert der Chart-Zeichner seinen Dienst über das RMI-Interface ChartMaker_Interface. Andere Teilsysteme des Gesamtsystems »comsofort« können lediglich die gib_ChartDaten-Methode dieses Remote-Objekts aufrufen. Auch an dieser Stelle muss eine gültige sessionID übergeben werden. Innerhalb der implementierten gib_ChartDaten-Methode wird eine Verbindung zum Kundenserver hergestellt und bei diesem die gib_alle_ Kurse-Methode mit den der gib_ChartDaten-Methode übergebenen Parameter (sessionID und wkn) aufgerufen. Eine Prüfung, ob die übergebene sessionID überhaupt gültig ist, wird bereits im Kundenserver durchgeführt und muss daher nicht noch einmal im Chart-Zeichner gemacht werden. Der Kundenserver liest bei der Ausführung der gib_alle_Kurse-Methode sämtliche Kursdaten für die Aktie mit der angegebenen wkn aus und liefert sie dem Aufrufer als Realtimekurs-Array zurück. Beim Aufruf der gib_ChartDaten-Methode verwendet ChartMaker die Klasse ChartDraw, um ein Fenster (siehe Abbildung 8.5) anzuzeigen und hierin den Kursverlauf einzuzeichnen.
434
Die Entwicklung der einzelnen Teilsysteme
Abbildung 8.4:
Die Zusammenarbeit der einzelnen Teilsysteme über RMI
Abbildung 8.5:
Das Fenster, in dem die aktuellen Börsenkurse gezeichnet werden
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
435
Dabei ist die Klasse ChartDraw (Listing 8.3) eine Ableitung der Klasse java.awt.Frame, die für die Fenstergestaltung zuständig ist. Die allgemeine Funktionsweise ist recht einfach. Im Fensterbereich wird der Chart gezeichnet und anschließend über einen GifEncoder von Acme2 in die Binärdaten eines GIFBildes umgewandelt. Um nicht alles von Hand zu zeichnen, wird in Zeile 30 die in Abbildung 8.5 sichtbare Chartvorlage aus einer Datei eingelesen: 30: vorlage = createImage ( (ImageProducer) getFile ("chart.gif") );
Um ChartDraw zum Zeichnen eines Kursverlaufes zu veranlassen, muss die in Zeile 66 beginnende zeichne_Chart-Methode aufgerufen werden, der ein Realtimekurs-Array übergeben wird. Diese Methode verfügt über das synchronizedSchlüsselwort, damit während des Zeichnens eines Kursverlaufes niemand diese Durchführung stören kann. Um letztendlich den Chart vom Bildschirm in eine Datei oder ein Byte-Array zu bekommen, wird in Zeile 139 ein neuer GifEncoder von Acme angelegt und ihm der chart des Typs java.awt.Image und ein ByteArrayOutputstream übergeben: 137: ByteArrayOutputStream byte_out = new ByteArrayOutputStream (); 139: GifEncoder enc = new GifEncoder ( chart, byte_out );
Durch Aufruf von encode() beim GifEncoder wird der Chart vom Bildschirm abgetragen und in das »virtuelle« Medium byte_out geschrieben. Um der aufrufenden Methode das fertige Byte-Array zurückzugeben, wird beim ByteArrayOutputstream die toByteArray-Methode aufgerufen: 140: enc.encode(); 144: return byte_out.toByteArray();
Auszugsweise ergibt sich für die Klasse ChartDraw folgender Code: Listing 8.3: 1: 2: 3: 4: 5: 6: 7: ...
buch\endanwendung\chart_maker\server\ChartDraw.java
/* * ChartDraw.java */ package buch.endanwendung.chart_maker.server; import Acme.JPM.Encoders.GifEncoder;
2 Kostenloser Download über http://www.acme.com
436
Die Entwicklung der einzelnen Teilsysteme
22: public class ChartDraw extends java.awt.Frame { 23: 24: private Image vorlage; 25: private Image chart; 26: private Graphics chart_Grafik; 27: 28: public ChartDraw() throws Exception { 29: super ( "ChartMaker 1.0" ); 30: vorlage = createImage ( (ImageProducer) getFile ("chart.gif") ); 31: initComponents (); 32: 33: this.setSize( 420, 255 ); 34: show(); 35: } 36: ... 41: 42: public void paint ( Graphics g ) { 43: g.drawImage ( vorlage, 5,25, this ); 44: if (chart != null) g.drawImage( chart, 5, 25 , this ); 45: } 46: ... 65: 66: public synchronized byte[] zeichne_Chart (Realtimekurs[] kurse) { 67: // Grafik vorbereiten: 68: chart = this.createImage( 410, 230 ); 69: chart_Grafik = chart.getGraphics(); 70: 71: long start_zeit = kurse[0].zeit; 72: 73: 74: // Grafik zeichnen: 75: chart_Grafik.setColor( Color.white ); 76: chart_Grafik.fillRect( 0,0,600,600 ); 77: chart_Grafik.setColor (Color.black); 78: chart_Grafik.drawImage( vorlage, 0, 5, this ); 79: 80: chart_Grafik.setColor( Color.red ); 81: ... 132: } 133: 134: repaint (); 135: 136: try {
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
137: 138: 139: 140: 141: 142: 143: 144: ... 226: }
437
ByteArrayOutputStream byte_out = new ByteArrayOutputStream (); GifEncoder enc = new GifEncoder ( chart, byte_out ); enc.encode(); // Rückgabe: return byte_out.toByteArray();
Starten der Teilsysteme Beim Starten der Teilsysteme müssen zunächst die benötigten Broker gestartet werden. Wenn das System auf einem Rechner zum Laufen gebracht wird, werden RMIregistry.exe und tNameServ.exe lokal gestartet. Wird das System jedoch auf mehrere Rechner verteilt, muss tNameServ.exe auf dem Rechner gestartet werden, auf dem das Börsensystem gestartet werden soll. Und überall dort, wo sich eine der anderen Teilkomponenten befindet, muss der RMI-Broker RMIregistry. exe gestartet werden. Da die Teilkomponenten untereinander zusammenarbeiten und zum Teil aufeinander angewiesen sind, muss beim Start die in Abbildung 8.6 dargestellte Startreihenfolge eingehalten werden. Da alle »comsofort«-Systeme über den SessionManager die Sessions der Benutzer verwalten, wird dieses System zuerst gestartet. Als Nächstes kann das Boersensystem gestartet werden. Die Programmierung des Börsensystems wurde so gestaltet, dass in zufälligen Intervallen zwischen zwei und sieben Sekunden für die aus der Datenbank ausgelesenen Aktien ein Kurs (beginnend bei 50) generiert wird. Nach erfolgreichem Start dieser beiden Systeme wird der Kundenserver gestartet. Dieser stellt eine Verbindung zum SessionManager und zum Boersensystem her, um bei Anfragen direkt mit dem jeweiligen Kooperationspartner zusammenarbeiten zu können. Als letztes System kann der ChartMaker gestartet werden, da dieser den Kundenserver benötigt, um die für die Zeichnung des Charts benötigten Realtimekurse über den Kundenserver abfragen zu können.
438
Die Entwicklung der einzelnen Teilsysteme
Abbildung 8.6:
Bildschirmausgaben beim Start der Teilsysteme
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
439
Die CGI-Komponenten für den Webserver Allgemeiner Aufbau
Abbildung 8.7:
Der interne Ablauf beim Login eines Benutzers
Wenn sich ein Kunde in das System einloggen möchte, muss er dies zunächst über ein Formular in einer »starren« Website machen, die die Formulardaten an eine JSP sendet. Generell wurden alle CGI-Komponenten des Webserver-Teilsystems als JSPs entwickelt. Nur die CGI-Komponente, die sich um die Anfragen des Realtimekurs-Applets kümmert, wurde in Form eines Servlets realisiert. Wichtig ist, dass von jedem Link aus auf eine Komponente des Systems immer die sessionID übergeben wird. Die Login-Seite Nach dem Ausfüllen des Zugangsdatenformulars wird die Anfrage an die LoginJSP (buch.endanwendung.comsofort.login.jsp) gesendet. Dort werden zunächst die ankommenden Daten des Formulars ausgelesen und über RMI an die loginFunktion des Kundenservers gesendet: Kunde legitimiert // => Weiterleitung ins System: response.sendRedirect( "../cgi/buch.endanwendung.comsofort.main_Page.jsp?" + sessionID=" + sessionID );
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
441
} catch (Exception e) { // Kunde nicht legitimiert: Fehlermeldung... %>
Es ist ein Fehler aufgetreten:
Bitte loggen Sie sich ggf. erneut ein...
Die Depotübersicht Nach erfolgreicher Eingabe der Zugangsdaten wird der Benutzer durch einen redirect-Befehl aus von der Login-JSP auf die main_Page.jsp geleitet, die eine Frame-Darstellung produziert. In der linken Frame-Seite wird das Menü angezeigt, dass von der menue.jsp produziert wird. In der rechten Frame-Seite wird der Inhalt des zum Benutzer gehörigen Depots durch Aufruf der depot.jsp angezeigt.
Abbildung 8.9:
Die Startseite für den persönlichen Desktop eines Benutzers
Durch Aufruf der gib_Depotbestand-Methode beim Kundenserver mit Übergabe der sessionID wird ein Order-Array zurückgeliefert, wobei jede Order einen ausgeführten Auftrag darstellt. Order[] depot = kundenserver.gib_Depotbestand ( sessionID );
442
Die Entwicklung der einzelnen Teilsysteme
Die Aktienorder Der Kauf einer Aktie wird durch Aufruf der aktienorder.jsp ermöglicht. nach Anklicken des WEITER-Buttons im angezeigten Formular werden die Formulardaten an die aktienorder_auswertung.jsp gesendet, dort auf ihre Korrektheit überprüft und über den Kundenserver als Auftrag an das Börsensystem übergeben.
Abbildung 8.10: Das Eingabeformular für eine Aktienorder
Das Orderbuch Nach der Orderaufgabe wird – wenn alle Eingaben korrekt sind – der Benutzer zum Orderbuch weitergeleitet, in dem alle an diesem Börsentag georderten Aktien aufgelistet sind. Bei der Erzeugung des Inhalts schreibt die gleichnamige JSP den nachfolgenden JavaScript-Code in die HTML-Antwort, durch den sich die Seite alle 20 Sekunden selbst erneuert. Dadurch wird sozusagen alle 20 Sekunden der aktuelle Orderstatus angezeigt.
Der Einbau dieses Code-Fragments hat lediglich pädagogischen Charakter und soll keine Empfehlung für Eigenentwicklungen darstellen. Durch diesen Effekt wird zwar auf einfache Art und Weise ein Update der darzustellenden Informationen durchgeführt, jedoch wird bei jedem reload() der Inhalt der Seite gelöscht und neu aufgebaut. Bei langsamen Verbindungen kann es dann – abgesehen vom
Kapitel 8 • Endanwendung: Der Directbroker »comsofort«
443
einem lästigen »Flackereffekt« – beim Kunden zu sekundenlangen weißen Seiteninhalten kommen. Alternativ wird gleich ein automatischer Update durchgeführt, der diese Nachteile nicht aufweist.
Abbildung 8.11: Der Überblick über die Kauf- und Verkaufaufträge
Die Anzeige von Echtzeitkursen Für die Anzeige der Echtzeitkurse werden in die HTML-Antwort der realtimekurs.jsp zwei verschiedene Techniken eingesetzt. Zum einen zeigt ein Applet (also ein Java-Programm) den aktuellen Kurswert (alle fünf Sekunden) mit der entsprechenden Kurszeit an und zum anderen sorgt ein JavaScript-Befehl dafür, dass der Chart alle 30 Sekunden erneuert wird. Das Realtime_Applet (Listing 8.4) wird ähnlich wie das Applet aus Listing 6.4 entwickelt. Das dortige Applet hatte die Aufgabe, ein Servlet zu kontaktieren und die Zeit einzulesen. Dieses Applet macht das Gleiche, nur dass es diesmal ein Objekt des Typs Realtimekurs einliest und darstellt. Auszugsweise ergibt sich folgender Code: Listing 8.4:
buch\endanwendung\comsofort\oeffentlich\Realtime_Applet.java
1: /* 2: * Realtime_Applet.java 3: */ 4:
444
Die Entwicklung der einzelnen Teilsysteme
5: package buch.endanwendung.comsofort.oeffentlich; 6: ... 24: public class Realtime_Applet extends Applet implements Runnable { 25: ... 60: 61: //-------> Methoden, die vom Browser aufgerufen werden: 62: public void start () { 63: if ( runner == null ) { 64: runner = new Thread ( this ); 65: runner.start (); 66: } 67: } 68: 69: public void stop () { 70: if ( runner != null ) { 71: runner.stop (); 72: runner = null; 73: } 74: } 75: 76: public void setWKN ( String WKN ) { 77: this.WKN = WKN; 78: } 79: //