7.1.5 Java RMI – Remote Method Invocation (http://java.sun.com/products/jdk/rmi) (http://java.sun.com/j2se/1.3/docs/guide/rmi/spec/rmiTOC.html) bietet leider nur begrenzte Verteilungsabstraktion Unterstützung von Fernaufrufen durch Bibliothekspakete java.rmi java.rmi.server java.rmi.registry und weitere . . . 7.1.5.1 Elementare Fernaufruf-Programmierung interface Remote: Für fernaufrufbares Objekt und sein Vertreterobjekt muß gemeinsame Schnittstelle definiert werden, die von java.rmi.Remote erben muß, z.B. import java.rmi.*; interface RemoteService extends Remote { String echo(String s) throws RemoteException; } Spätere Vertretererzeugung gelingt nur dann, wenn die Ausnahme java.rmi.RemoteException vereinbart wird. class UnicastRemoteObject: Klasse eines fernaufrufbaren Objekts muß von java.rmi.server.UnicastRemoteObject erben, z.B. import java.rmi.*; import java.rmi.server.*; class Server extends UnicastRemoteObject implements RemoteService { public String echo(String s) {return s+s;} public Server() throws RemoteException { } } UnicastRemoteObject redefiniert die Operationen von Object und registriert das fernaufrufbare Objekt bei der RMI-Verwaltung. Achtung: Ein Server-Objekt kann durchaus auch lokal benutzt werden. class Naming: Namensdienst ist über statische Operationen der Klasse java.rmi.Naming erreichbar: public static void rebind(String name, Remote object) throws RemoteException, MalformedURLException // java.net public static Remote lookup(String name) throws NotBoundException, RemoteException, MalformedURLException erfordert Casting RemoteService s = (RemoteService)Naming.lookup(...); (weitere Operationen: bind, unbind, list ) Der jeweilige Name ist wie folgt im URL-Format anzugeben: [ // host [ : port ] / ] name Adresse des Namensdienstes Standard-Host = lokale Station Standard-Port = 1099 Der Namensdienst wird (unter Unix) gestartet mit rmiregistry [ port ] & (und sollte – wenn nicht mehr benötigt – mit kill beendet werden) Achtung: Inhalt verändern nur lokal, Inhalt befragen auch entfernt ! 7.1.5.2 Vertretererzeugung und -installation Vertretergenerator heißt RMI Compiler und wird aufgerufen mit rmic <remoteServerClass> also z.B. rmic Server ( - nicht mit Schnittstelle RemoteService !) und generiert bereits übersetzte Stubs: Eingabe Ausgabe Server.class Server_Stub.class (Vertreter) Server_Skel.class (Treiber) Einfaches Beispiel (s.o.): import java.rmi.*; import java.rmi.server.*; interface RemoteServer extends Remote { String echo(String s) throws RemoteException; } class Server extends UnicastRemoteObject implements RemoteServer { public String echo(String s) {return s+s; public Server() throws RemoteException { } } public static void main(String[] arg) throws Exception { Server s = new Server(); Naming.rebind("Server", s); } } class Client {// start: java Client <serverHost> <text> public static void main(String[] arg) throws Exception { RemoteServer server = (RemoteServer)Naming.lookup("//"+arg[0]+"/Server"); String text = server.echo(arg[1]); System.out.println(text); System.exit(0); // prevent hangup due to RMI threads } } Installation der .class-Dateien: CLASSPATH geeignet setzen! Beim Erzeugen des fernaufrufbaren Objekts muß der zugehörige Treiber-Code greifbar sein. Beim Registrieren des fernaufrufbaren Objekts beim Namensdienst muß der zugehörige Vertreter-Code greifbar sein - denn Vertreter-Objekt samt Code wird zum Namensdienst geschickt. Beim Erzeugen des Vertreter-Objekts beim Klienten - als Folge der Anfrage beim Namensdienst – muß dort der Vertreter-Code greifbar sein. (Alternative: Namensdienst liefert Vertreter-Objekt samt Code (s.o.) - sofern Security Manager das Herunterladen erlaubt; vgl. 7.2) Einfaches Szenario: Entwicklungs-Station Klienten-Station Anbieter-Station >javac test.java >rmic Server (test.java RemoteServer.class Client.class Server.class Server_Stub.class Server_Skel.class) (Client.class RemoteServer.class Server_Stub.class) (Server.class RemoteServer.class Server_Stub.class Server_Skel.class) >rmiregistry & >java Server & >java Client elfe bla blabla > 7.1.5.3 Verweise in Fernaufruf-Parameten Übergeben wird entweder Vertreter oder Kopie des Objekts: Schnittstelle des formalen Parameters erbt von Remote: aktueller Parameter muß (statisch) gleiche Schnittstelle implementieren, (dynamisch) von UnicastRemoteObject erben, andernfalls MarshalingException Vertreterobjekt wird übergeben Schnittstelle des formalen Parameters erbt nicht von Remote: aktueller Parameter muß (statisch) gleiche Schnittstelle implementieren, (dynamisch) Schnittstelle java.io.Serializable implementieren, andernfalls MarshalingException Objektkopie wird übergeben In Objekte eingebettete Verweise werden entsprechend behandelt. Felder (arrays) und Zeichenketten (strings) sind serializable ! Achtung! Wenn ein formaler Parameter einen Schnittstellentyp hat, kennt man vom aktuellen Parameter nur diese Schnittstelle (unabhängig davon, ob es sich um einem lokalen oder einen Fernaufruf handelt). Wird ein fernaufrufbares Objekt (7.1.5.1) übergeben, so weiß man nicht, ob das Objekt tatsächlich entfernt oder aber lokal vorliegt. Handelt es sich um ein serialisierbares Objekt, so wird in Abhängigkeit von seiner Lage entweder eine Kopie oder das Objekt selbst geliefert! Die Semantik des Aufrufs ist nicht eindeutig bestimmt. 7.1.5.4 Verteilte Speicherbereinigung wird von Java RMI eingeschränkt unterstützt: Verwendung von Verweiszählern (reference counts) und Kooperation mit den lokalen Garbage Collectors mit Einsatz von Javas schwachen Verweisen (weak references)