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