Java Remote Method Invocation (RMI) Alexander Petry 13. Mai 2003 engl.: Entfernter Methodenaufruf 1
Übersicht 1. Einleitung 2. RMI Interfaces und Klassen 3. Parameterübergabe 4. Dynamisches Nachladen von Klassendateien 5. Implementierung 6. Kompilieren 7. Ausführen 2
1. Einleitung Verteilte Systeme, die Berechnungen oder ähnliches ausführen, müssen miteinander kommunizieren Java TM stellt Sockets für die low-level Kommunikation (TCP, UDP) bereit + ausgezeichnet für allgemeine Kommunikation Kommunikations-Protokoll muss selbst implementiert werden fehleranfällig, aufwendig 3
Ein weiteres Konzept ist Remote Procedure Call (RPC) + Abstrahiert die Kommunikation soweit, dass der Programmierer einen einfachen Prozeduraufruf machen kann entfernter Aufruf wirkt wie ein lokaler Aufruf schlecht auf objektorientierte Systeme anwendbar, da dort Kommunikation zwischen Objekten auf verschiedenen Systemen stattfindet 4
Verteilte Objekt-Systeme benötigen Remote Method Invocation (RMI) + Remote method invocation (RMI) ist der Aufruf auf Client-Seite einer Remote Interface Methode, die zu einem Remote Object gehört. + Der Aufruf einer Methode auf einem entfernten Objekt hat genau die gleiche Syntax wie ein lokaler Methodenaufruf. + In solchen Systemen regelt ein lokales Objekt (Stub) den Aufruf der entfernten Methode + Man kann weiterhin objektorientiert arbeiten, da auch ganze Objekte übertragen werden können 5
Ablauf eines entfernten Methodenaufrufs real communication Client virtual communication remobj.m() Remote Object Stub network 6
2. RMI Interfaces und Klassen 1. java.rmi.remote Bei RMI definiert ein Remote Interface Methoden, die eine entfernte JVM aufrufen kann. Es muss folgende Bedingungen erfüllen: (a) Das remote Interface muss direkt oder indirekt das java.rmi.remote Interface erweitern (b) Jede Methode muss eine java.rmi.remoteexception werfen (oder eine ihrer Oberklassen) (c) Alle Remote Objekte, die als Parameter oder Rückgabewerte direkt oder indirekt auftauchen, müssen als Interface angegeben werden, nicht als die das Interface implementierende Klasse 7
Beispiel: // erweitert nicht das Remote Interface, erfüllt aber alle anderen // Bedingungen public interface Alpha { public Object foo(object obj) throws java.rmi.remoteexception; // erweitert das obige und das Remote Interface public interface Beta extends Alpha, java.rmi.remote { public void ping() throws java.rmi.remoteexception; 8
2. java.rmi.server.remoteobject Stellt u.a. mit den Unterklassen java.rmi.server.remoteserver und java.rmi.server.unicastremoteobject RMI-Server Funktionen bereit Die Methoden, die zum Erstellen und Exportieren von Remote Objekten benötigt werden, stellt u.a. die Klasse UnicastRemoteObject zur Verfügung Das UnicastRemoteObject definiert ein Singleton (Unicast) Remote Objekt, es ist nur solange gültig, wie der Server lebt 9
3. Parameterübergabe Parameter oder Rückgabewerte von Remote Objekten können alle Objekte sein, die serialisierbar sind Also alle primitiven Typen, Remote Objekte und nicht-remote Objekte, die das java.io.serializable Interface implementieren (die meisten Java Klassen implementieren dieses Interface) Klassen für Parameter oder Rückgabewerte, die nicht lokal zur Verfügung stehen, können automatisch nachgeladen werden (mehr dazu später) 10
Übergabe von nicht-remote Objekten Ein nicht-remote Objekt wird als Kopie übergeben. Das heißt, es wird vor dem Verschicken mittels des Java Objekt-Serialisierungs Mechanismus kopiert. Auf der jeweiligen Empfängerseite wird ein neues Objekt mit denselben Daten angelegt. Übergabe von Remote Objekten Bei einem Remote Objekt (z.b. Unterklasse von UnicastRemoteObject) wird anstelle der Implementierung der Stub verschickt. Man erreicht dadurch ein call by reference. Ein Remote Objekt, das als Parameter übergeben wird, kann nur Remote Interfaces implementieren. 11
4. Dynamisches Nachladen von Klassen Das RMI Framework bietet die Möglichkeit, dynamisch nicht vorhandene Klassenbeschreibungen nachzuladen Dazu wird ein Objekt beim Verschicken annotiert (mit zusätzlichen Informationen ausgestattet) Diese Informationen beinhalten die so genannte Codebase (vergleichbar mit dem Classpath ). Man kann sie wie folgt beim Starten des Servers setzen: java -Djava.rmi.server.codebase=<URL> <Server> Wobei URL das FTP, HTTP oder das File Protokoll beschreiben kann. 12
Wozu das Ganze? Der Client benötigt keinen direkten Zugriff auf die Stub Klassen, er kann sie sich während der Ausführung besorgen. Beim Verschicken von Objekten werden im Remote Interface meist nicht die implementierenden Klassen angegeben sondern Interfaces, der Client braucht nur Zugriff zu den Interfaces, die Implementierung kann er automatisch nachladen. Das birgt einen gewaltigen Vorteil: Man kann die Implementierungen auf der Serverseite ändern, ohne etwas am Client ändern zu müssen! (vorausgesetzt das Interface bleibt unverändert) 13
Dynamisches Nachladen eines Stubs 14
5. Implementierung 1. Erstellen eines Remote Interfaces package shared; // Importieren benötigter Klassen import java.rmi.remote; import java.rmi.remoteexception; // Definieren des Remote Interfaces -- wichtig ist, // dass es java.rmi.remote erweitert public interface Service extends Remote { // liefert ein Remote Objekt zurück (Stub) public MyRemoteObject getremoteobject() throws RemoteException; // liefert ein serialisierbares Objekt zurück public MyObject getobject() throws RemoteException; 15
2. Implementieren des Interfaces package server; // Importieren benötigter Klassen import java.rmi.*; import java.rmi.server.*; import shared.*; // Implementieren des Remote Interfaces -- wichtig ist, dass man // die Klasse von UnicastRemoteObject erben lässt. public class ServiceImpl extends UnicastRemoteObject implements Service { // Konstruktor wirft Exception, da super() Exception wirft public ServiceImpl() throws RemoteException { /* nothing to do */ 16
// liefert ein Remote Objekt zurück (Stub) public MyRemoteObject getremoteobject() throws RemoteException { return new MyRemoteObjectImpl(); // liefert ein serialisierbares Objekt zurück public MyObject getobject() throws RemoteException { return new MyObjectImpl(); 17
3. Implementierung der restlichen Interfaces // MyObjectImpl.java package server; import java.io.serializable; import shared.*; // wichtig ist, dass dieses Objekt das java.io.serializable // Interface implementiert, damit es durch RMI übertragen // werden kann public class MyObjectImpl implements MyObject, Serializable { public MyObjectImpl() { /* nothing to do */ 18
// MyRemoteObjectImpl.java package server; import java.rmi.*; import java.rmi.server.*; import shared.*; // Als Remote Objekt sollte es UnicastRemoteObject erweitern, // damit beim Übertragen der Stub und nicht das Objekt // selbst übertragen wird public class MyRemoteObjectImpl extends UnicastRemoteObject implements MyRemoteObject { public MyRemoteObjectImpl() throws RemoteException { /* nothing to do */ 19
4. Implementierung des Servers // Server.java package server; import java.rmi.*; import java.rmi.server.*; import java.io.*; public class Server { private static String host = "localhost"; public static void main(string[] args) { if (System.getSecurityManager() == null) System.setSecurityManager(new RMISecurityManager()); if (args.length!= 0) host = args[0]; 20
try { Naming.rebind("rmi://"+host+"/Service", new ServiceImpl()); // Auf Benutzereingabe zum Beenden warten (muss sein, da // nach einem unbind noch ein Thread existiert und sich // die JVM nicht beendet.) BufferedReader in = new BufferedReader (new InputStreamReader(System.in)); String input = ""; while (! input.equals("q") ) { System.out.print ("Zum Beenden bitte ein [q] " + "gefolgt von [Enter] drücken: "); input = in.readline(); Naming.unbind("rmi://"+host+"/Service"); System.exit(0); 21
catch (Exception e) { System.err.println("Ein Fehler ist aufgetreten!"); System.err.println(e); 22
4. Implementierung des Clients package client; import java.rmi.*; import shared.*; public class Client { private static Service service; private static MyRemoteObject remobj; private static MyObject obj; private static String host = "localhost"; public static void main( String[] args ) { // SecurityManager setzen und evtl. host als Parameter // speichern... 23
try { System.out.print ("Referenz auf Service Objekt holen..."); service = (Service)Naming.lookup ("rmi://" + host + "/Service"); System.out.println("ok."); System.out.print ("Name der Referenz: " + service.getclass().getname()); // weitere Methodenaufrufe... catch (Exception e) {... 24
6. Kompilieren 1. Kompilieren des Servers und Erzeugen der Stubs cd $PROJECT_DIR/src javac -classpath. -d../classes/ server/server.java cd../classes rmic -v1.2 server.serviceimpl rmic -v1.2 server.myremoteobjectimpl 2. Kompilieren des Clients cd $PROJECT_DIR/src javac -classpath. -d../classes/ client/client.java 25
7. Ausführung 1. RMI-Registry starten rmiregistry & 2. Server starten cd classes && java -classpath. \ -Djava.rmi.server.codebase=file:///... \ -Djava.security.policy=../lib/java.policy server.server 3. Client starten cd classes && java -classpath. \ -Djava.security.policy=../lib/java.policy client.client 26