Indhold:
Forstå principperne i RMI
Kalde metoder i fjerne objekter
Kapitlet forudsættes ikke i resten af bogen.
Forudsætter kapitel 12, Interfaces, kapitel 18, Serialisering og kendskab til netværk.
Med RMI (Remote Method Invocation) kan man arbejde med objekter, der eksisterer i en anden Java virtuel maskine (ofte på en anden fysisk maskine), som om de var lokale objekter.
Herunder er tegnet, hvad der sker, når en klient på maskine A laver et kald til et serverobjekt (værts-objekt), der er i maskine B.
Serverobjektet findes slet ikke på maskine A, i stedet er der en såkaldt RMI-stub, der repræsenterer det. Når der sker et kald til RMI-stubben på maskine A, sørger den for at transportere kaldet og alle parametre til maskine B, hvor serverobjektet bliver kaldt, som om det var et lokalt kald. Serverobjektets svar bliver transporteret tilbage til RMI-stubben, der returnerer det til klienten.
Denne proces foregår helt automatisk og er usynlig for klienten såvel som serverobjektet.
RMI benytter serialisering til at transportere parametre og returværdi mellem maskinerne, så man skal huske, at alle objekter, der sendes over netværket, skal implementere Serializable-interfacet, og at variabler, der ikke skal overføres, skal mærkes med nøgleordet transient.
Der skal være defineret et interface (kaldet fjerninterfacet) til de metoder på serverobjektet, som skal være tilgængelige for klienten. Serverobjekt skal implementere dette interface.
Lad os forestille os, at serveren har et konto-objekt, hvor man kan overføre penge, spørge om saldo og få bevægelserne. Disse metoder skal være tilgængelige over netværket, så vi definerer et fjerninterface til kontoen (her kaldt KontoI):
import java.util.ArrayList; public interface KontoI extends java.rmi.Remote { public void overførsel(int kroner) throws java.rmi.RemoteException; public int saldo() throws java.rmi.RemoteException; public ArrayList bevægelser() throws java.rmi.RemoteException; }
Fjerninterfacet skal arve fra interfacet java.rmi.Remote, og alle metoder skal kunne kaste undtagelsen java.rmi.RemoteException.
På serversiden skal vi implementere Konto-interfacet og programmere den funktionalitet, der skjuler sig bag det, i et serverobjekt, der skal arve fra UnicastRemoteObject. Klassenavnet ender normalt på Impl (for at vise, at det er implementationen af fjerninterfacet).
import java.util.ArrayList; import java.rmi.server.UnicastRemoteObject; public class KontoImpl extends UnicastRemoteObject implements KontoI { public int saldo; public ArrayList bevægelser; public KontoImpl() throws java.rmi.RemoteException { // man starter med 100 kroner saldo = 100; bevægelser = new ArrayList(); } public void overførsel(int kroner) { saldo = saldo + kroner; String s = "Overførsel på "+kroner+" kr. Ny saldo er "+saldo+" kr."; bevægelser.add(s); System.out.println(s); } public int saldo() { System.out.println("Der spørges om saldoen. Den er "+saldo+" kr."); return saldo; } public ArrayList bevægelser() { System.out.println("Der spørges på alle bevægelser."); return bevægelser; } }
Nu skal vi oprette et serverobjekt og registrere vores tjeneste under et navn i RMI:
import java.rmi.Naming; public class Kontoserver { public static void main(String[] arg) throws Exception { KontoI k = new KontoImpl(); Naming.rebind("rmi://localhost/kontotjeneste", k); System.out.println("Kontotjeneste registreret."); } }
Kontotjeneste registreret. ...
Programmet afslutter ikke, men venter på, at noget henvender sig for at bruge tjenesten.
For at registreringen kan foregå, skal der køre en RMI-navnetjeneste, der holder styr på, hvilke tjenester der udbydes under hvilke navne, og formidler kontakten til dem. Det er et lille program, der hedder rmiregistry. Det skal kende definitionen af de klasser, der overføres, så man starter det ofte samme sted som selve RMI-tjenesten.
Når vi skal køre vores server, sker det i fire trin:
alle kildetekster oversættes til
bytekode:
javac *.java
(eller i et udviklingsværktøj)
ud fra KontoImpl.class laves RMI-stub-klassen
(KontoImpl_Stub.class):
rmic
KontoImpl
rmiregistry startes i et separat vindue (fra
samme katalog, som bytekoden ligger i):
rmiregistry
til sidst kan Kontoserver startes fra et
separat vindue:
java Kontoserver
(eller i et udviklingsværktøj)
På klientsiden skal vi slå serverobjektet op i RMI-tjenesten og derefter bruge det objekt, vi får retur, som om det var serverobjektet selv (i virkeligheden er det RMI-stubben):
import java.util.ArrayList; import java.rmi.Naming; public class Kontoklient {
public static void main(String[] arg) { try { KontoI k =(KontoI) Naming.lookup("rmi://localhost/kontotjeneste"); k.overførsel(100); k.overførsel(50); System.out.println( "Saldo er: "+ k.saldo() ); k.overførsel(-200); k.overførsel(51); System.out.println( "Saldo er: "+ k.saldo() ); ArrayList bevægelser = k.bevægelser(); System.out.println( "Bevægelser er: "+ bevægelser ); } catch (Exception e) { e.printStackTrace(); } } }
Saldo er: 250 Saldo er: 101 Bevægelser er: [Overførsel på 100 kr. Ny saldo er 200 kr., Overførsel på 50 kr. Ny saldo er 250 kr., Overførsel på -200 kr. Ny saldo er 50 kr., Overførsel på 51 kr. Ny saldo er 101 kr.]
Sammen med Kontoklient skal ligge fjerninterfacet KontoI og KontoImpl_Stub.
Mens kontoklienten kører, kommer der følgende uddata fra Kontoserver:
Overførsel på 100 kr. Ny saldo er 200 kr. Overførsel på 50 kr. Ny saldo er 250 kr. Der spørges om saldoen. Den er 250 kr. Overførsel på -200 kr. Ny saldo er 50 kr. Overførsel på 51 kr. Ny saldo er 101 kr. Der spørges om saldoen. Den er 101 kr. Der spørges på alle bevægelser.
Herunder ses de enkelte klassers funktioner.
Prøv eksemplet på RMI. Oversæt .java-filerne og find ud af hvor .class-filerne ligger. Start tre terminalvinduer (DOS-vinduer i Windows). I hvert vindue skal du skrive
cd <stedet hvor .class-filerne ligger>
Skriv derefter, i vindue 1:
rmic KontoImpl rmiregistry
Skriv derefter, i vindue 2:
java Kontoserver
Skriv derefter, i vindue 3:
java Kontoklient
Når ovenstående fungerer, så prøv at køre klienten i et andet katalog: Kopiér KontoI.class, Kontoklient.class og KontoImpl_Stub.class over i et andet katalog, og prøv derfra.
Prøv derefter at rette i Kontoklient, sådan at klienten slår op i rmiregistry på en anden maskine. Er værtsmaskinens IP-nummer f.eks. 192.168.1.42, retter du i Kontoklient til:
KontoI k =(KontoI) Naming.lookup("rmi://192.168.1.42/kontotjeneste");