Eine Kaufhaus-Anwendung ist eine Sitzungs-orientierte Anwendung, wobei
in einer Sitzung üblicherweise der Benutzer und die aktuell gewählten
Artikel gespeichert werden. Das Beispiel wird mit Hilfe von zwei
unterschiedlichen Typen von Server-Objekten implementiert:
- Ein zentrales Server-Objekt, das eingehende Verbindungen entgegennimmt
und dafür zuständig ist, neue Sitzungen zu erzeugen. Dieses Objekt ist somit
für die Ausgabe eines Warenkorbs zuständig.
- Mehrere dezentrale Objekte, die den jeweiligen Warenkorb eines
Clients repräsentieren. Die Daten im Warenkorb werden von Client-Seite hinzugefügt
und auf dem Server im Warenkorb zwischengespeichert.
Da es sich hier um zwei verschiedene Typen von Server-Objekten handelt,
müssen zwei Server-Schnittstellen definiert werden. Die Schnittstelle des zentralen
Server-Objekts hat dabei folgenden Aufbau:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ShopServer extends Remote {
public Cart createCart() throws RemoteException;
}
Wenn ein neuer Client einen Warenkorb benötigt, ruft er die
Methode createCart() und bekommt ein Cart-Exemplar
zurück. Da der Inhalt des Warenkorbs auf dem Server gespeichert werden soll,
wird er in einer eigenen Server-Schnittstelle modelliert:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Cart extends Remote {
public void addProduct(String name)
throws RemoteException;
public void removeProduct(String name)
throws RemoteException;
public String[] listContents() throws RemoteException;
public void buy(String custID) throws RemoteException;
}
Man kann dem Warenkorb neue Produkte hinzufügen, Produkte entfernen,
die vorhandenen Produkte auflisten und alle darin enthaltenen Produkte kaufen.
Zur Vereinfachung werden die Produkte hier nur durch ihren Namen identifiziert.
Das zentrale Server-Objekt wird von der Klasse ShopServerImpl
implementiert. Es stellt die Hauptanwendung dar, die als eigener Server gestartet wird,
und verfügt deshalb über eine main()-Methode, in der
das Objekt beim Namensdienst registriert wird.
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;
public class ShopServerImpl extends UnicastRemoteObject
implements ShopServer {
public ShopServerImpl() throws RemoteException {
}
public Cart createCart() throws RemoteException {
System.out.println("create cart");
return new CartImpl();
}
public static void main(String args[]) {
try {
Naming.rebind("shop-server", new ShopServerImpl());
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Beim Aufruf von createCart() wird ein neues Cart-Exemplar
zurückgegeben. Die Implementierung wird hier durch die Klasse CartImpl
bereitgestellt. Dass es sich hierbei auch um ein Objekt handelt, dessen Methode auf dem
Server und nicht auf dem Client ausgeführt werden, wird dadurch sichtbar, dass
die Klasse auch von UnicastRemoteObject abgeleitet ist:
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.util.List;
import java.util.ArrayList;
public class CartImpl extends UnicastRemoteObject
implements Cart {
List products;
boolean pending = false;
public CartImpl() throws RemoteException {
products = new ArrayList();
}
public void addProduct(String name) {
System.out.println("add product: "+name);
products.add(name);
}
public void removeProduct(String name) {
System.out.println("remove product: "+name);
products.remove(name);
}
public String[] listContents() {
System.out.println("list contents");
String[] contents = new String[products.size()];
products.toArray(contents);
return contents;
}
public void buy(String custID) {
System.out.println("buy products: "+custID);
pending = false;
// Hier Bestellung verschicken ...
}
}
Wenn eine Server-Methode ein neues Exemplar einer Klasse zurückgibt, das von
UnicastRemoteObject erbt, wird nicht das neue Exemplar per
Objekt-Serialisierung auf den Client übertragen, sondern es wird auf
Serverseite ein neues Server-Objekt erzeugt und lediglich der Client-Side Proxy auf den
Client übertragen. Das heißt, wenn der Client nach dem Abrufen eines Cart-Exemplars
dessen Methoden aufruft, werden diese Methoden auf Serverseite ausgeführt.
Mit folgender Client-Anwendung kann das getestet werden:
import java.rmi.Naming;
public class ShopClient {
public static void main(String args[]) {
try {
ShopServer server =
(ShopServer)Naming.lookup("shop-server");
Cart cart = server.createCart();
cart.addProduct("Coffee");
cart.addProduct("Tea");
cart.addProduct("Java");
System.out.println("listing contents ...");
String[] contents = cart.listContents();
for(int i=0; i < contents.length; i++)
System.out.println(" - "+contents[i]);
cart.buy("rsinger");
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Nacheinander werden in diesem Beispiel die verschiedenen Methoden auf dem Server aufgerufen.
Zunächst wird über den Namens-Dienst das zentrale ShopServer-Exemplar
ermittelt. Von diesem Exemplar wird anschließend ein neuer Warenkorb abgerufen, dem
drei Produkte hinzugefügt werden. Danach wird der Inhalt des Warenkorbs aufgelistet
und schließlich durch Aufruf von buy() gekauft.
Wenn man die Anwendung ausführt, kann man feststellen, dass die Ausgaben in CartImpl
alle im Server-Prozess ausgegeben werden und der Client somit lediglich einen Verweis
auf den im Server gespeicherten Warenkorb bekommt.