Java Remote Method Invocation RMI
Willemers Informatik-Ecke
In einigen Dokumentationen im Web wird rmic erwähnt. Der rmic ist ein RMI-Compiler, der dem JDK beiliegt. Er erzeugt aus den kommunizierenden Klassen stub bzw skeleton für die Kommunikation. Seit Java 1.5.0 ist er allerdings nicht mehr erforderlich, da der stub von der virtuellen Maschine übernommen wird. (vgl. Wikipedia-Artikel)

Ein kleines Beispiel

Das Interface des Servers

Der Fernaufruf basiert auf einem Interface, das Java.rmi.Remote erweitert. Der Server wird es implementieren und der Client aufrufen.
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ServerInterface extends Remote {
     public void bitteMeldeDich() throws RemoteException;
     public int gibZahl() throws RemoteException;
}
Dieses Interface wird nun vom Server implementiert.

Der RMI-Server

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class MeinServer extends UnicastRemoteObject implements ServerInterface {

    MeinServer() throws RemoteException {
        super();
    }

    public static void main(String[] args) {

        try {
            LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
            Naming.rebind("MeinServer", new MeinServer());
        } catch (MalformedURLException ex) {
            System.out.println(ex.getMessage());
        } catch (RemoteException ex) {
            System.out.println(ex.getMessage());
        }
    }
    // Die Remote veröffentlichten Methoden
    public void bitteMeldeDich() throws RemoteException  {
        System.out.println("Hier MeinServer!");
    }
    public int gibZahl() throws RemoteException  {
        System.out.println("Zahlenabwurf");
        return 42;
    }
}
Der Server erzeugt eine Registry auf der Standard-Portnummer und bindet sich unter dem Namen MeinServer an diesen Port. Ansonsten fängt er nur die möglichen Exceptions. Die beiden Methoden des Interfaces werden implementiert.

Der Server wird von der Konsole einfach gestartet und blockiert, ohne einen Ton von sich zu geben, sofern alles in Ordnung ist.

Ein RMI-Client

Der Client sucht nun nach dem Namen MeinServer in der lokalen Registry. Dann ruft er die beiden Methoden des Servers nacheinander auf.
import java.rmi.Naming;

public class MeinClient {

    public static void main(String[] args) {
        String url = "rmi://127.0.0.1/MeinServer";
        try {
            ServerInterface server = (ServerInterface) Naming.lookup(url);
            server.bitteMeldeDich();
            int zahl = server.gibZahl();
            System.out.println(zahl);
        } catch (Exception e) {
            System.err.println("Client Exception: " + e.toString());
            e.printStackTrace();
        }
    }
}
Der URL-String hat den allgemeinen Aufbau:
rmi://host:port/objekt
Wie oben gesehen, kann der Port entfallen.

Vor dem Start des Clients muss der Server natürlich gestartet sein. Der Start des Clients bewirkt, dass der Server auf seiner Konsole anzeigt, dass die beiden Methoden aufgerufen wurden. Der Client zeigt eine 42 als Zeichen, dass er die Zahl vom Server erhalten hat.

Anmerkungen

Der Expert von Remote-Objekten wird durch die Klasse UnicastRemoteObject erreicht. Hier gibt es zwei Vorgehensweisen. Im hier vorgeführten Fall erweitert die Serverklasse die Klasse UnicastRemoteObject. Dann muss sie im Standardkonstruktor mit super() den Konstruktor der Basisklasse aufrufen. Diese muss dann auch die RemoteException werfen.

Alternativ kann auch das Server-Objekt mit der statischen Methode UnicastRemoteObject.exportObject exportiert werden. Der Rückgabewert ist ein Remote-Objekt-Stub, der als Parameter beim bind verwendet werden kann.

Wird die Registry über das Programm rmiregistry gestartet, muss dieser im Verzeichnis der Klassen des Servers gestartet werden oder durch die Option -Djava.rmi.server.codebase=file:/pfad auf dieses Verzeichnis verweisen.

Beispiel mit Objekten

Komplexe Objekte werden für die Übermittlung serialisiert. Entsprechend müssen übertragbare Objekte die Schnittstelle Serializable implementieren.
import java.io.Serializable;

public class Person implements Serializable {
    private int alter = 0;
    int getAlter() {
        return alter;
    }
    public void altere() {
        alter++;
    }
}
Das Interface muss für Client und Server gleich sein. Der Server implementiert es, der Client verwendet es, um die Server-Methoden aufzurufen.
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RmiInterface extends Remote {
    static final String servername = "RmiServer";

    public String vollerName(String vorname, String nachname) 
            throws RemoteException;
    public Person altere(Person person) 
            throws RemoteException;
}
Der RMI-Server implementiert das Interface. Er bindet sich über die lokale Registry an den Namen.
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RmiServer extends UnicastRemoteObject
                       implements RmiInterface {

    RmiServer() throws RemoteException {
        super();
    }
    
    public static void main(String[] args) 
            throws RemoteException, MalformedURLException {

        LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
        Naming.rebind(RmiInterface.servername, new RmiServer());
    }
    
    @Override
    public String vollerName(String vorname, String nachname) throws RemoteException {
        return vorname + " " + nachname;
    }

    @Override
    public Person altere(Person person) throws RemoteException {
        person.altere();
        return person;
    }
}
Der Client ruft den Server auf. Dazu sucht er auf dem Zielrechner den RMI-Server mit dem passenden Namen und erhält ein RmiInterface, über das er die Methoden aufruft, als seien sie lokal.
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RmiClient {
    public static void main(String[] args) 
            throws MalformedURLException, 
            RemoteException, NotBoundException {
        String url = "rmi://127.0.0.1/" + RmiInterface.servername;
        RmiInterface server = (RmiInterface) Naming.lookup(url);
        String name = server.vollerName("Otto", "Muster");
        Person person = new Person();
        Person altePerson = server.altere(person);
        System.out.println(name + " " + altePerson.getAlter());
    }
}