8.1.5 Java RMI – Remote Method Invocation (http://java.sun.com/products/jdk/rmi ) (http://java.sun.com/j2se/1.5/docs/guide/rmi ) (http://java.sun.com/docs/books/tutorial/rmi ) (http://java.sun.com/developer/onlineTraining/rmi/RMI.html ) Unterstützung von Fernaufrufen durch Bibliothekspakete java.rmi java.rmi.server java.rmi.registry und weitere . . . RMI bietet leider nur begrenzte Verteilungsabstraktion. vs8.1.5 1 8.1.5.1 Grundzüge der Fernaufruf-Programmierung in Java 1 interface Remote Für ein fernaufrufbares Objekt und sein Vertreterobjekt muss eine gemeinsame Schnittstelle definiert werden, die von java.rmi.Remote erben muss, 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. vs8.1.5 2 2 class UnicastRemoteObject Klasse eines fernaufrufbaren Objekts muss von java.rmi.server.UnicastRemoteObject erben, z.B. import java.rmi.*; import java.rmi.server.*; class Server extends UnicastRemoteObject implements RemoteService { public Server() throws RemoteException { } nötig! private String memory = ""; public String echo(String s) // throws entbehrlich! { return memory += s; } } UnicastRemoteObject redefiniert die Operationen von Object (z.B. equals() für Fernvergleich) und registriert das fernaufrufbare Objekt bei der RMI-Verwaltung (≈ Adapter). 3 vs8.1.5 Achtung: Ein Server-Objekt kann durchaus auch lokal benutzt werden. 3 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 (weitere Operationen: bind, unbind, list ) vs8.1.5 4 Der Parameter String name ist im URL-Format anzugeben – er identifiziert Station und Port des Namensdienstes und enthält den eigentlichen Objektnamen: [ // host [ : port ] / ] name Adresse des Namensdienstes Standard-Host = lokale Station Standard-Port = 1099 Ein Namensdienst wird (unter Unix) gestartet mit rmiregistry [ port ] & (und sollte – wenn nicht mehr benötigt – mit kill beendet werden) Achtung: Einträge verändern nur lokal, aber abfragen auch entfernt ! vs8.1.5 5 Aufsetzten und Verwendung Anbieterseitig: Server -Objekt erzeugen und beim (lokalen) Namensdienst registrieren: Naming.rebind("Service", new Server()); Klientenseitig: Klient erfragt Objekt beim Namensdienst: RemoteService s = (RemoteService)Naming.lookup("//obelix/Service"); System.out.println(s.echo("bla"); Casting erforderlich! Es wird geprüft, ob das von lookup gelieferte Vertreterobjekt tatsächlich die benötigte Schnittstelle implementiert. vs8.1.5 6 8.1.5.2 Vertretererzeugung und -installation (< JDK 1.5) Vertretergenerator heißt RMI Compiler und wird aufgerufen mit rmic <ServerClass> also z.B. rmic Server ( - nicht mit Schnittstelle RemoteService !) und generiert Stubs – bereits als .class -Dateien Eingabe Server.class Ausgabe Server_Stub.class (Vertreter) Server_Skel.class (Treiber) vs8.1.5 7 Installation der .class-Dateien: CLASSPATH geeignet setzen! Beim Erzeugen des fernaufrufbaren Objekts muss der zugehörige Treiber-Code greifbar sein. Beim Registrieren des fernaufrufbaren Objekts beim Namensdienst muss auch der zugehörige Vertreter-Code greifbar sein - ferner: codebase wird dem Namensdienst zur späteren Weitergabe mitgeteilt (s.u. ). Beim Erzeugen des Vertreter-Objekts beim Klienten – als Folge der Anfrage beim Namensdienst – muss dort der Vertreter-Code greifbar sein. (Alternative: Namensdienst liefert codebase des Vertreter-Objekts – Security Manager muss das Herunterladen erlauben; vgl. 8.1.5.6) vs8.1.5 8 8.1.5.3 Von der Programmierung bis zur verteilten Ausführung // RemoteService.java – sowohl für Client als auch Server interface RemoteService extends Remote { String echo(String s) throws RemoteException; } // Server.java class Server extends UnicastRemoteObject implements RemoteService { public Server() throws RemoteException { } private String memory = ""; public String echo(String s) { return memory += s+" "; } public static void main(String[] arg) throws Exception { Naming.rebind("Service", new Server()); // Programm stoppt hier nicht – wegen verborgener RMI Threads } } vs8.1.5 9 Übersetzen auf Server-Maschine obelix: javac RemoteService.java liefert RemoteService.class liefert Server.class javac Server.java Erzeugung der Stubs (optional*): liefert Server_Stub.class Server_Skel.class rmic Server Namensdienst einrichten (falls nicht schon vorhanden): rmiregistry & Server starten (er registriert sich selbst beim Namensdienst): java Server & vs8.1.5 10 … und hier ein Beispiel-Klient: // Client.java, benötigt RemoteService.java class Client { public static void main(String[] arg) throws Exception { RemoteService s = (RemoteService)Naming.lookup("//"+arg[0]+"/Service"); System.out.println(s.echo(arg[1])+"\n"); System.exit(0); } } Damit das Programm nicht Fernaufrufe wegen der RMI Threads hängenbleibt vs8.1.5 11 Übersetzen auf irgendeiner Klienten-Maschine: javac RemoteService.java javac Client.java Vertreter-Code bereitstellen, Server_Stub.class , über Netzdateisystem oder Dateiübertragung von Server-Maschine obelix Klient z.B. wiederholt starten: > java Client obelix hallo hallo > java Client obelix hallo hallo hallo > java Client obelix hallo hallo hallo hallo vs8.1.5 12 Alternatives Szenario mit separater Entwickler-Station: Entwickler-Station > > > > Klienten-Station Anbieter-Station javac RemoteService.java javac Server.java javac Client.java rmic Server RemoteService.class RemoteService.class RemoteService.class Server.class Server.class Client.class Client.class Server_Stub.class Server_Stub.class Server_Stub.class Server_Skel.class Server_Skel.class > rmiregistry & > java Server & > java Client elfe bla bla vs8.1.5 > 13 Mit JDK 1.5 wird der Generator rmic nicht mehr unbedingt benötigt. Stattdessen kommt Java Reflection zum Einsatz. Zur Erinnerung: // Dynamischer Aufruf Class[] types = { String.class, Integer.class }; Method method = MyClass.getDeclaredMethod("myMethod", types); Object[] args = { "Hallo", new Integer(42) }; method.invoke(obj, args); // Dynamischer Proxy public interface Foo { public void bar(); } public class MyHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { if (method.getName().equals("bar")) ... } }; Class[] interfaces = { Foo.class }; Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), interfaces, handler); f.bar(); // -> handler.invoke(...) vs8.1.5 14 8.1.5.4 Verweise in Fernaufruf-Parametern Übergeben wird entweder Vertreter oder Kopie des Objekts: 1 Schnittstelle des formalen Parameters erbt von Remote: aktueller Parameter muss (statisch) gleiche Schnittstelle implementieren, (dynamisch) von UnicastRemoteObject erben, andernfalls MarshalingException Netzverweis/Vertreterobjekt wird übergeben vs8.1.5 15 2 Schnittstelle des formalen Parameters erbt nicht von Remote: aktueller Parameter muss (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 ! vs8.1.5 16 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 UnicastRemoteObject ü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 ! (siehe dazu auch Brose/Löhr/Spiegel: Java resists transparent distribution) vs8.1.5 17 8.1.5.5 Aktivierbare Objekte werden erst bei Bedarf erzeugt – allerdings nach vorangegangener Registrierung über den RMI Daemon rmid import java.rmi.activation.*; class Server extends Activatable implements RemoteService {............ public Server(ActivationID id, MarshalledObject data) throws RemoteException { super(id, 0); } } Server.class muss bei rmid, die zugehörige Vertreterklasse beim Namensdienst registriert werden – dafür muss ein setup-Programm geschrieben werden. http://java.sun.com/j2se/1.5.0/docs/guide/rmi/activation/overview.html vs8.1.5 18 8.1.5.6 Dynamisches Nachladen von Code Stationen müssen den Code von Vertreter-Objekten (Remote) oder sogar Implementierungen (Serializable) kennen! Java-Code ist plattformunabhängig, kann über das Netz nachgeladen werden (via HTTP, FTP, ...) Für jedes Programm, das Code nachladen muss (rmiregistry, Client): ● Security-Manager am Anfang des Programms festlegen: System.setSecurityManager(new RMISecurityManager()); in policy -Datei gewähren grant { permission java.net.SocketPermission "*", "accept,connect,listen,resolve"; }; (in ~/.java.policy oder mit -Djava.security.policy=...) ● Socket-Verwendung vs8.1.5 19 Für jedes Programm, das nachladbaren Code anbietet (Server): ● Klassen oder JAR auf Web-Server bereitstellen: http://myhost.de/RemoteService.jar (enthält RemoteService.class, Server_Stub.class) ● Codebase des Servers beim Start mit angeben: java -Djava.rmi.server.codebase= "http://myhost.de/RemoteService.jar" Server & Die Codebase-URL wird bei der rmiregistry hinterlegt. Klienten erhalten bei lookup nicht nur einen Netzverweis, sondern auch die zugehörige Codebase-URL, und laden den Code von dort nach. 20 vs8.1.5 [Siehe http://java.sun.com/j2se/1.5.0/docs/guide/rmi/codebase.html]