Nachdem die grundlegende Funktion entfernter Objekte und Methoden sowie die Architektur von RMI erläutert wurde, werden im Folgenden wichtige Eigenschaften von RMI zusammengefasst. Callback-Operationen Bisher wurden vor allem Client-Server-Anwendungen betrachtet, die über einen Server Dienste für einen Client anbieten. Der Client kann hierbei die notwendigen Funktionen auf eine transparente Art und Weise aufrufen. Eine derartige Kommunikation ist allerdings immer auf den Server hin ausgerichtet. RMI bietet auch die Möglichkeit, eine Zwei-Wege-Kommunikation zwischen Objekten zu realisieren. Zur Verwendung einer Objektreferenz, die von einem Rechner an einen anderen geschickt wird, muss hierzu lediglich die Objektklasse das Interface Remote implementieren. Das Objekt muss anschließend exportiert werden, um entfernte Methodenaufrufe empfangen zu können. Ein Objekt wird dann automatisch exportiert, wenn es das Objekt UnicastObject erweitert. Wenn diese zwei Bedingungen erfüllt sind, kann in RMI ein entferntes Objekt von einem Client an einen Server geschickt werden, so dass der Server die entfernten Methoden des Client-Objekts aufrufen kann. Es sei deutlich darauf hingewiesen, dass diese Aufrufrichtung entgegen der regulären Richtung erfolgt. Durch ein derartiges Vorgehen können aber vollständige verteilte Anwendungen entwickelt werden, in denen keine Unterscheidung zwischen Server und Client (und somit zwischen der Richtung einer Dienstanforderung) erfolgt. Jede Anwendung verwendet dann den Dienst einer anderen Anwendung oder stellt diesen zur Verfügung. Ein Beispiel für eine derartige Anwendung bzw. für die Erweiterung, die hierdurch möglich wird, ist eine typische Banktransaktion. Im Client-Server-Modell sendet der Client eine Anfrage bezüglich des Kontostandes an den Server, der diese Anfrage beantwortet. In einem erweiterten Modell könnte der Client eine Anfrage an den Server stellen, der zunächst mit einer Gegenfrage, bspw. nach einem Passwort, antwortet. Die korrekte Angabe des Kontostandes wird in diesem Beispiel nur dann gegeben, wenn das Passwort eingegeben wird. Das Anwendungsbeispiel zum Schluss dieses Kapitels demonstriert die Anwendung derartiger Callback-Operationen. Dynamisches Laden von Klassen RMI wurde von Anfang an als System verteilter Java-zu-Java-Anwendungen entwickelt. Aufgrund dieser Eigenschaft unterscheidet sich RMI auch deutlich von anderen verteilten Systemen (bspw. von CORBA), da es möglich ist, vollständige Objekte von einem Adressraum in einen anderen zu übertragen. Durch diese Möglichkeit können nicht nur (statische) Daten, sondern auch (dynamische) Verhaltensweisen übertragen werden. Mittels der Serialisierung und des RMI-ClassLoaders können vollständige Klassendefinitionen übertragen werden. In RMI ist es deshalb ohne weiteres möglich, vollständige Objektgraphen zu schicken, indem einfach ein Objekt als Argument eines Methodenaufrufs verwendet wird. Hierzu ist es allerdings erforderlich, dass das Objekt das Interface Serializable implementiert. Indem Objekte von einem Adressraum in einen anderen verschoben werden, kann das Objekt Ressourcen eines anderen Rechners nutzen, bspw. die CPU, Dateien oder Datenbanken. Ist bspw. eine aufwendige Berechnung erforderlich, so kann es durchaus sinnvoll sein, wenn ein Objekt, das diese Berechnung durchführt, von einer langsamen auf eine schnellere Maschine verschoben wird. Hierbei wird der Aufwand für das Versenden eines Objekts vernachlässigt. Entfernte Interfaces, Objekte und Methoden Eine verteilte Anwendung, die mit RMI erstellt wird, besteht wie jede andere Java-Anwendung aus Interfaces und Klassen. Im Unterschied zu den bisher betrachteten Funktionen von Klassen und Interfaces kann es aber vorkommen, dass einige Implementierungen von Interfaces und Klassen sich in entfernten VMs befinden. Objekte, die Methoden beinhalten, die über VMs hinweg aufgerufen werden können, werden als entfernte Objekte bezeichnet. Ein Objekt wird dann zu einem entfernten Objekt, wenn es ein entferntes Interface implementiert, das die folgenden Eigenschaften aufweist: Das entfernte Interface erweitert das Interface java.rmi.Remote. Jede Methode des Interfaces beinhaltet zusätzlich zu anwendungsspezifischen Ausnahmen die Exception java.rmi.RemoteException in der throws-Klausel. RMI verarbeitet entfernte Objekte anders als lokale Objekte, wenn ein derartiges Objekt von einer VM zu einer anderen übergeben wird. Anstatt eine Kopie der Implementierung in der empfangenden VM zu erzeugen, übergibt RMI ein entferntes Platzhalterobjekt zum Zugriff auf das entfernte Objekt (sog. Stub). Ein Stub agiert hierbei als lokale Repräsentation oder Proxy eines entfernten Objekts und entspricht daher weitestgehend der entfernten Referenz. Eine Anwendung ruft dann eine Methode des lokalen Stubs auf, die dafür verantwortlich ist, den Methodenaufruf des entfernten Objekts durchzuführen. Ein Stub eines entfernten Objekts implementiert daher dieselbe Menge entfernter Interfaces, die auch das entfernte Objekt implementiert. Hierdurch kann der Typ eines Stubs auf jedes Interface abgebildet werden, die das entfernte Objekt implementiert. Dies ist gleichbedeutend damit, dass nur die Methoden von der empfangenden VM aufgerufen werden können, die im entfernten Interface definiert sind. Objektpersistenz Ruft ein Java-Programm entfernte Methoden auf, so müssen Parameter an einen Server übermittelt bzw. Rückgabewerte zurückgesendet werden. Einfache Werte können hierbei Byte für Byte übertragen werden, nicht jedoch komplexe Objekte. Instanzen entfernter Objekte benötigen den Zugriff auf den gesamten Objektgraphen, der von einem Parameter referenziert wird. Eine entfernte Methode könnte bspw. ein kompliziertes Objekt erstellen und zurückgeben, das wiederum Referenzen auf andere Objekte enthält. In diesem Fall muss die gesamte Konstruktionsinformation des Objekts zurückgegeben werden. Aus diesem Grund muss jedes Objekt, das an eine entfernte Methode übergeben wird bzw. von dieser zurückgegeben wird, eines der Interfaces Serializable oder Externalizable implementieren, um Objekte zu serialisieren. Die Verwendung dieser Interfaces wurde bereits in Kapitel 5.1 erläutert. Argumente, die an Methoden übergeben oder von diesen zurückgegeben werden, können daher die folgende Form aufweisen: Einfacher Datentyp Entferntes Objekt Serialisierbares Objekt, das das Interface java.io.Serializable implementiert Einige Objekttypen entsprechen allerdings keinem dieser Kriterien und können daher auch nicht als Argumente verwendet werden. Die meisten dieser Objekte (bspw. Dateideskriptoren) kapseln Informationen, die nur innerhalb eines bestimmten lokalen Adressraums sinnvoll sind. Viele Java-Klassen (bspw. die Klassen der Packages java.lang und java.util) implementieren allerdings das Interface Serializable. Die Regeln, die demzufolge für die Übergabe von Argumenten und für die Rückgabe von Werten anzuwenden sind, lauten wie folgt: Entfernte Objekte werden üblicherweise per Referenz übergeben. Eine Referenz auf ein entferntes Objekt wird als Stub realisiert, also in Form eines Client-seitigen Proxies, der alle entfernten Interfaces implementiert, die das entfernte Objekt selbst auch implementiert. Lokale Objekte werden als Kopien übergeben, indem der Mechanismus der Objektserialisierung verwendet wird. Standardmäßig werden alle Felder außer denen, die als static oder transient markiert sind, kopiert. Dieses Verhalten kann allerdings auf Klassenbasis überschrieben werden. Ein Objekt per Referenz zu übergeben bedeutet daher, dass alle Zustandsänderungen des Objekts, die durch entfernte Methodenaufrufe erfolgen, im entfernten Originalobjekt reflektiert werden. Wird ein entferntes Objekt übergeben, so stehen dem Empfänger nur die Interfaces zur Verfügung, die als entfernte Interfaces realisiert sind. Alle Methoden, die in der Implementierung der Klasse oder in nicht-entfernten Interfaces, die die Klasse implementiert, realisiert sind, stehen dem Empfänger nicht zur Verfügung. In entfernten Methodenaufrufen werden alle Objekte, die selbst keine entfernten Objekte sind (Parameter, Rückgabewerte und Exceptions), als Wert übergeben. In diesem Fall wird daher eine Kopie eines Objekts in der empfangenden VM erzeugt. Jede Zustandsänderung eines Objekts beim Empfänger wird dann aber nur in der Kopie des Empfängers reflektiert, nicht in der Originalinstanz. Objektaktivierung Um eine Referenz auf ein entferntes Objekt zu erhalten, muss der Server, der die Instanz des Objekts generiert, üblicherweise in einer Java-VM ablaufen. Dieser Mechanismus reicht für die meisten Anwendungen aus. In großen Systemen, die eine Vielzahl von Objekten, die nicht gleichzeitig benötigt werden, verwenden, sollte ein erweiternder Mechanismus zur Verfügung stehen, der Objekte so lange deaktiviert, wie sie nicht benötigt werden. Der Aktivierungsmechanismus in RMI übernimmt diese Funktionalität. Zunächst muss ein Objekt von der Registry mit einem Namen versehen werden. Die Aktivierung erfolgt dann zu einem späteren Zeitpunkt, indem das Objekt durch die Registry referenziert wird. Einer der großen Vorteile dieses Ansatzes ist, dass die Anwendung, die Instanzen eines entfernten Objekts erzeugt, terminieren kann, bevor das Objekt überhaupt benötigt wird. Die Möglichkeit, entfernte Objekte erst bei Bedarf zu aktivieren, erlaubt daher eine große Flexibilität bei der Entwicklung von Servern. Damit die Aktivierung erfolgen kann, wurde ein sog. Dämon-Prozess, der RMI Activation System Daemon (rmid), entwickelt. Die Aktivierung erfolgt stets in den folgenden Schritten: Ein Client erfragt bei der Registry den Namen eines Objekts. Die Registry antwortet mit einer entfernten Referenz, die den Aufruf der rmid veranlasst. Der Client ruft eine entfernte Methode bei der rmid (nicht bei der Registry!) auf, indem die entfernte Referenz verwendet wird. Die rmid leitet den Aufruf an die Instanz der Objektimplementierung weiter. Zur Verwendung dieser Möglichkeit muss rmid im Hintergrund laufen, während andere Programme ausgeführt werden können. Hierzu muss rmid in der Kommandozeile mittels start rmid ausgeführt werden. Nach dem Aufruf läuft rmid unter der Port-Nummer 1098. Dies liegt unter anderem daran, dass die Registry standardmäßig unter der Port-Nummer 1099 gestartet wird. Soll hingegen für rmid eine andere Port-Nummer verwendet werden, so muss die folgende Syntax eingegeben werden: start rmid -port [Port-Nummer] Um aktiviert werden zu können, muss ein Objekt anstelle der Klasse UnicastObject die Klasse Activatable erweitern. Wie UnicastRemoteObject-Klassen müssen auch Objekte, die diese Klasse erweitern, ein entferntes Interface implementieren und daraufhin exportiert werden, um eingehende Methodenaufrufe zu akzeptieren. Indem eine Klasse aber die Klasse Activatable erweitert, findet die Exportierung automatisch statt, wenn eine Instanz eines derartigen Objekts angelegt wird. Andere Klassen können ebenfalls exportiert werden, wenn die als static deklarierte Methode Activatable.exportObject() aufgerufen wird. Dies impliziert allerdings, dass das Interface java.rmi.Remote implementiert wird. Die Behandlung derartiger Objekte auf der Server-Seite ist schwieriger. Um ein eingehendes Verständnis zu ermöglichen, wird diese Funktion im Anwendungsbeispiel anhand eines konkreten Programms erläutert.