18.5 Das entfernte Objekt
 
Die Methoden des Servers werden letztendlich vom Client über die Stellvertreter genutzt. Der Server muss unterschiedliche Vorgaben erfüllen: Er muss eine spezielle Klasse erweitern, einen Konstruktor anbieten und die entfernte Schnittstelle implementieren. Dann kann ein Server-Objekt angemeldet werden. Das Server-Objekt implementiert daher die Geschäftslogik und sieht wie folgt aus:
Listing 18.2
AdderImpl.java
import java.rmi.*;
import java.rmi.server.*;
public class AdderImpl extends UnicastRemoteObject implements Adder
{
public AdderImpl() throws RemoteException
{
}
public int add( int x, int y ) throws RemoteException
{
return x + y;
}
}
Da die Klasse eine Implementierung der Schnittstelle ist, geben wir ihr die Endung Impl.
18.5.1 Der Bauplan für entfernte Objekte
 
Zuerst fällt in der Implementierung auf, dass wir die Klasse UnicastRemoteObject erweitern. Sie liegt im Paket java.rmi.server und zeigt so die grobe Richtung für die Verwendung an.
public class AdderImpl extends UnicastRemoteObject implements Adder
{
...
}
Diese Klasse bietet Hilfe bei der Übertagung der Daten mittels Standard-TCP-Sockets an. Der Server kann so auf eingehende Anfragen reagieren und diese bearbeiten. Weiterhin zeigt UnicastRemoteObject an, dass ein Exemplar (nicht repliziert) unserer Klasse existieren soll.
18.5.2 Der Konstruktor
 
Für den Konstruktor eines entfernten Objekts gelten zwei Eigenschaften: Wir müssen einen Standard-Konstruktor anbieten und dieser muss ebenso wie die Methoden RemoteException anzeigen.
public AdderImpl() throws RemoteException
{
}
Der Standard-Konstruktor ist notwendig, da eine Unterklasse genau diesen aufrufen möchte. Unser Konstruktor muss nichts machen. Er ruft aber automatisch den Konstruktor der Oberklasse auf, also den von UnicastRemoteObject. Wir haben schon beschrieben, dass er bei der Übertragung hilft. Wenn ein entferntes Objekt konstruiert wird, dann bindet er diesen Dienst an einen anonymen Port und horcht auf einkommende Aufrufe. Wollten wir einen speziellen Port nutzen, müssten wir im Konstruktor unserer Unterklasse einen parametrisierten Konstruktor von UnicastRemoteObject aufrufen, der einen Port annimmt.
class java.rmi.server.UnicastRemoteObject
extends RemoteServer
|
|
protected UnicastRemoteObject() throws RemoteException
Erzeugt und exportiert ein neues UnicastRemoteObject und bindet es an einen unbekannten Port. Konnte es nicht exportiert werden, löst der Konstruktor eine RemoteException aus. |
|
protected UnicastRemoteObject( int port ) throws RemoteException
Erzeugt ein UnicastRemoteObject und bindet es an den angegeben Port. Ist dieser null, so wird er vom System zugewiesen. |
Vererbung nicht möglich – was dann?
Es kann passieren, dass eine entfernte Klasse schon von einer anderen Klasse erbt, und wir wegen der fehlenden Mehrfachvererbung ein Problem bekommen. Wenn wir uns dafür entscheiden, keine Objekte über UnicastRemoteObject anzubieten, aber eine existierende Klasse trotzdem Anbieter sein möchte, so muss das Objekt im Konstruktor ein entferntes Objekt, welches Remote implementiert, mit UnicastRemoteObject.exportObject(Remote) anmelden.
class java.rmi.server.UnicastRemoteObject
extends RemoteServer
|
|
static RemoteStub exportObject( Remote obj )
Exportiert das entfernte Objekt und macht es empfänglich für hereinkommende Aufrufe. Es wird ein willkürlicher Port verwendet. |
|
static Remote exportObject( Remote obj, int port )
Verhält sich wie exportObject(Remote), nur wird der angegebene Port und nicht der Standard-Port 1099 verwendet. |
Beispiel Der Server implementiert die Schnittstelle Adder und registriert sich selbst über exportObject() beim Namensdienst. (Was das ist, erfahren wir gleich etwas genauer.)
Listing 18.3
AdderImpl2.java
import java.rmi.*;
import java.rmi.server.*;
class AdderImpl2 implements Adder
{
public AdderImpl2() throws RemoteException
{
}
public int add( int x, int y ) throws RemoteException
{
return x + y;
}
public static void main( String args[] ) throws Exception
{
AdderImpl2 server = new AdderImpl2();
UnicastRemoteObject.exportObject( server );
Naming.rebind( "adder", server );
}
}
|
Hinweis Die Erweiterung von UnicastRemoteObject ist nur eine Abkürzung für den Weg über exportObject(). Die Methode wird aber ohnehin genommen, wie der Ausschnitt aus dem Quellcode für den Konstruktor zeigt.
protected UnicastRemoteObject() throws RemoteException
{
this( 0 );
}
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject( (Remote)this, port );
}
|
18.5.3 Implementierung der entfernten Methoden
 
Im nächsten Schritt müssen die Methoden der Schnittstelle implementiert werden. Es steht frei, andere Methoden anzugeben, die nicht in der Schnittstelle vorgegeben sind, doch diese sind dann natürlich nicht nach außen sichtbar.
public int add( int x, int y ) throws RemoteException
{
return x + y;
}
Die Argumente und Rückgabewerte können von jedem beliebigen Datentyp sein. Bei primitiven Datentypen werden spezielle read()- und write()-Folgen generiert. Objekte müssen die Schnittstelle Serializable implementieren. Dann werden die lokalen Objekte als Kopie übertragen. Über die Serialisierung werden alle nicht statischen und nicht transienten Attribute übermittelt. Ist der Parameter wiederum instanceof Remote, dann wird dieser Verweis als einfache Referenz übergeben. In Wirklichkeit ist sie ein Verweis auf den Stellvertreter.
18.5.4 UnicastRemoteObjekt, RemoteServer und RemoteObject
 
Entfernte Objekte erweitern oft die Klasse UnicastRemoteObjekt. Sie selbst ist jedoch eine Unterklasse von java.rmi.server.RemoteServer, eine abstrakte Klasse, die wiederum die abstrakte Klasse java.rmi.server.RemoteObject beerbt.
RemoteObject ist ein verteiltes Objekt
RemoteObject ist nichts anderes als ein verteiltes Objekt, welches die Schnittstellen Remote und Serializable implementiert. Da beide aber nur Markierungsschnittstellen sind, taucht keine ausprogrammierte Methode auf. RemoteObject ersetzt die Klasse Object für verteilte Objekte. Die Unterscheidung tritt bei den Methoden hashCode(), equals() und toString() auf. Ebenso implementiert RemoteObject die Methoden writeObject() und readObject(), damit die verteilten Objekte serialisiert werden können.
abstract class java.rmi.server RemoteObject
implements Remote, Serializable
|
|
protected RemoteObject()
Erzeugt ein remote-Objekt. |
|
protected RemoteObject( RemoteRef newref )
Erzeugt ein remote-Objekt, welches mit angegebener remote-Referenz initialisiert ist. |
|
RemoteRef getRef()
Liefert eine remote-Referenz für das Objekt. |
|
public static Remote toStub( Remote obj ) throws NoSuchObjectException
Liefert den Stub für das remote-Objekt obj. Die Operation kann nur durchgeführt werden, wenn das Objekt schon exportiert wurde. Sonst wird eine NoSuchObjectException ausgelöst. |
|
int hashCode()
Liefert den Hashcode. Zwei entfernte Stubs, die auf dasselbe Objekt verweisen, sollten auch den gleichen Hashwert liefern. Die Methode ruft auf entfernten RemoteRef-Objekten die Methode remoteHashCode() auf und auf lokale Objekte einfach hashCode(). |
|
boolean equals( Object obj )
Vergleicht, ob zwei entfernte Objekte gleich sind. Ist obj kein entferntes Objekt, so wird ein normaler Vergleich mit equals() gemacht. Der Vergleich mit dem entfernten Objekt wird mit remoteEquals() auf dem RemoteRef-Objekt vorgenommen. |
|
String toString()
Liefert eine String-Repräsentation. |
RemoteServer
RemoteServer erweitert RemoteObject und fügt die interessante Methode getClientHost() hinzu. Damit kann der Server den Hostnamen vom Client als String erfahren. Damit der Server jedoch Informationen an den Client zurückschicken kann, muss der Client selbst als Server auftreten. Die Kommunikation ist bei RMI in der Regel immer einseitig.
abstract class java.rmi.server.RemoteServer
extends RemoteObject
|
|
protected RemoteServer()
Erzeugt für die Unterklassen ein RemoteServer()-Objekt. |
|
protected RemoteServer( RemoteRef ref )
Erzeugt für die Unterklassen ein RemoteServer()-Objekt mit der Referenz ref. |
|
static String getClientHost() throws ServerNotActiveException
Liefert den Hostnamen des aktuellen Clients. Die Ausnahme ServerNotActiveException wird ausgelöst, wenn der Aufruf außerhalb eines Servers stattfindet, der RMI anbietet. |
|