Daljinsko pozivanje metoda

25
Daljinsko pozivanje metoda
Većina programa koje smo do sad obradili izvršavala se na jednom virtualnom stroju za
Javu. Bile su samo dvije iznimke: u lekciji 18 koristili ste dva virtualna stroja dok ste učili
programiranje priključaka i u lekciji 22 JDBC program koji ste napisali komunicirao je s
drugim virtualnim strojem u kojem se izvršavao poslužitelj baze podataka. Aplikaciji koja
se izvršava na računalu korisnika nije uvijek dopušteno izravno pristupanje udaljenim
podacima – to je jedan od razloga zbog kojeg su nastale distribuirane Java aplikacije. (Riječ
„distribuirana“ znači da se dijelovi aplikacije izvršavaju na različitim računalima.) Drugi
razlog je bio da se pruži središnji poslužitelj koji će posluživati nekoliko lakih klijenata.
Postoji mnogo načina za izradu distribuiranih aplikacija u Javi koje se izvršavaju na više
virtualnih strojeva a jedan od njih je i daljinsko pozivanje metoda (engl. Remote Method
Invocation, RMI) koje se danas rijetko koristi. Na primjer, klijentska Java aplikacija
(JVM1) se povezuje s poslužiteljskom Java aplikacijom (JVM2) koja se povezuje s DBMS
sustavom na trećem računalu. Klijentska aplikacija ne zna ništa o DBMS sustavu – ona
jednostavno dobiva podatke, ArrayList objekata Employee, od poslužiteljske aplikacije
koja se izvršava na virtualnom stroju JVM2. RMI koristi serijalizaciju objekta za razmjenu podataka između strojeva JVM1 i JVM2.
Ali, za razliku od programiranja priključaka, gdje se klijent eksplicitno povezuje s
poslužiteljem, kada se koristi daljinsko pozivanje metoda jedna Java klasa može pozivati
metode na Java objektima koji postoje u drugom (udaljenom) virtualnom stroju za Javu.
Iako iz perspektive sintakse izgleda kao da su pozivatelj i klasa poslužitelja na istom
virtualnom stroju, oni mogu biti međusobno udaljeni tisućama kilometara. RMI klijent
neće imati kopiju poslužiteljske metode već samo njenog lokalnog predstavnika.
Aplikacija s daljinskim pozivanjem metoda sastoji se od RMI poslužitelja, klijenta i registra (usluge imenovanja). Te tri komponente mogu se izvršavati na različitim virtualnim
strojevima za Javu na različitim umreženim računalima. RMI poslužitelj stvara Java
objekte koji implementiraju poslovnu logiku, registrira ih u registru i čeka da udaljeni
klijenti pozovu metode na njima.
Klijentska aplikacija uzima iz registra referencu na udaljeni poslužiteljski objekt, ili
objekte, i zatim na tim udaljenim objektima poziva metode. Glavni koncept daljinskog
pozivanja metoda je u tome što se metode pozivaju na klijentskom virtualnom stroju za
274 ❘ Lekcija 25 Daljinsko pozivanje metoda
Javu a izvršavaju na poslužiteljskom. Klase za podršku daljinskom pozivanju metoda i
registar uključeni su uz Java SE.
Razvoj aplikacije s daljinskim pozivanjem metoda
Ova lekcija je koncipirana kao ilustracija primjera RMI aplikacije s minimalnom količinom teorije. Detaljniji opis daljinskog pozivanja metoda dostupan je na Web stranici
http://download.oracle.com/javase/6/docs/technotes/guides/rmi/index.html.
Razvoj distribuirane RMI aplikacije podrazumijeva sljedeće korake:
1.
2.
3.
4.
5.
Deklariranje udaljenog sučelja.
Implementiranje udaljenog sučelja.
Razvoj Java klijenta koji se spaja s udaljenim poslužiteljem i poziva udaljene metode.
Pokretanje registra i registriranje RMI poslužitelja u njemu.
Pokretanje poslužitelja i klijentske aplikacije.
Kroz sve ove korake proći ćemo u primjeru razvoja aplikacije Stock Quotes Server (njena
verzija koja koristi priključke opisana je u lekciji 18) koja će klijentu pružati cijene odabranih dionica. Neki od sljedećih koraka se mogu objediniti, ali ću zbog jednostavnosti
napisati posebnu klasu za svaku traženu akciju.
Definiranje udaljenih sučelja
Java klase koje planirate postaviti na stranu poslužitelja moraju implementirati udaljena
sučelja koja deklariraju poslovnu metodu (ili metode) koje će RMI klijenti daljinski pozivati. Kôd klijenta izgledat će kao da poziva lokalne metode, ali će ti pozivi biti preusmjeravani udaljenom poslužitelju preko RMI protokola. Evo pravila za izradu udaljenih sučelja:
➤➤
Udaljeno sučelje mora deklarirati javne metode kako bi klijentima dopustilo
daljinsko pozivanje metoda.
➤➤
Udaljeno sučelje mora proširivati java.rmi.Remote.
➤➤
Svaka metoda mora deklarirati java.rmi.RemoteException.
➤➤
Budući da će argumenti metode i vraćeni podaci putovati preko mreže pomoću
Java serijalizacije, njihov tip podataka mora biti Serializable.
Razvoj poslužiteljskog dijela distribuirane Java aplikacije započinjete postavljanjem
pitanja „Koje poslovne metode moraju biti izložene klijentskoj aplikaciji i koji bi trebali
biti njihovi potpisi?“. Kada znate odgovor na to pitanje, definirajte udaljena sučelja koja
deklariraju te metode.
Primjer 25-1 prikazuje kôd udaljenog sučelja StockServer koje će biti implementirano
na poslužitelju ali mora postojati i s klijentske strane. To sučelje deklarira dvije metode:
getQuote() i getNasdaqSymbols(). Prva će generirati proizvoljnu cijenu za zadani simbol dionice a druga će vratiti popis simbola dionica koji se mogu zadati.
Razvoj aplikacije s daljinskim pozivanjem metoda ❘ 275
Primjer 25-1: Sučelje StockServer
package com.practicaljava.lesson25;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
public interface StockServer extends Remote {
public String getQuote(String symbol) throws RemoteException;
public List<String> getNasdaqSymbols()
throws RemoteException;
}
Implementiranje udaljenog sučelja
Iako udaljeno sučelje samo deklarira metode, morate izraditi klasu koja će se izvršavati
na poslužitelju i za njih pružati implementaciju. Postoji poseban zahtjev da se takva klasa
izveze u Java RMI izvršno okruženje kako bi mogla primati pozive. To je donekle slično
povezivanju s portom u slučaju ServerSocket (pogledajte primjer 18-5), ali u slučaju
daljinskog pozivanja metoda poslužitelj također pravi „lažnu“ klasu (za klijentsku stranu)
koja sadrži posrednike svih metoda implementiranih u udaljenom sučelju.
Najlakši način za izvoženje instance RMI poslužitelja je proširivanje iz java.rmi.
server.UnicastRemoteObject, kao u primjeru 25-2. Ako poslužitelj mora biti
proširen iz druge klase, možete eksplicitno izvesti objekt poslužitelja pozivanjem
UnicastRemoteObject.export().
Primjer 25-2 prikazuje implementaciju klase StockServerImpl koja će obrađivati
zahtjeve klijenta. Ta klasa generira proizvoljne cijene za dionice nabrojane u ArryList po
imenu nsadaqSymbols.
Primjer 25-2: Klasa StockServerImpl
package com.practicaljava.lesson25;
import java.rmi.*;
import java.rmi.server.*;
import java.util.ArrayList;
public class StockServerImpl extends UnicastRemoteObject implements
StockServer {
private String price=null;
private ArrayList<String> nasdaqSymbols = new ArrayList<String>();
public StockServerImpl() throws RemoteException {
super();
// Izravno upisujemo neke NASDAQ simbole
nasdaqSymbols.add("AAPL");
nasdaqSymbols.add("MSFT");
nasdaqSymbols.add("YHOO");
nasdaqSymbols.add("AMZN");
nasdaqSymbols.add("MOT");
nastavak na sljedećoj stranici
276 ❘ Lekcija 25 Daljinsko pozivanje metoda
Primjer 25-3 (nastavak)
}
public String getQuote(String symbol)
throws RemoteException {
if(nasdaqSymbols.indexOf(symbol.toUpperCase()) != -1) {
// Generira proizvoljnu cijenu za ispravne simbole
price = (new Double(Math.random()*100)).toString();
}
// Ova verzija koda ne obrađuje slučaj kada korisnik unese
// neispravan simbol dionice – on samo vraća vrijednost
// koja je trenutno spremljena u varijabli price
return price;
}
public ArrayList<String> getNasdaqSymbols()throws RemoteException {
return nasdaqSymbols;
}
}
Registriranje udaljenih objekata
Kako biste učinili da udaljeni objekt bude dostupan klijentima morate ga pridružiti
nekom imenu u registru, usluzi imenika koja točno zna gdje se u mreži izvršava vaš RMI
poslužitelj StockServerImpl. To Java klijentima omogućava da po imenu potraže objekt
na domaćinskom računalu.
Primjer 25-3 prikazuje kôd koji povezuje instancu StockServerImpl s portom 1099 na
domaćinskom računalu, a to je u našem slučaju lokalno računalo. Ostatku svijeta ovaj
poslužitelj bit će poznat pod imenom QuoteService.
Primjer 25-3: Pokretanje poslužitelja i povezivanje u registru
package com.practicaljava.lesson25;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.rmi.Naming;
public class StartServer {
public static void main (String args[]) {
try {
StockServerImpl ssi = new StockServerImpl();
Naming.rebind("rmi://localhost:1099/QuoteService",ssi);
System.out.println("<QuoteService> server is ready.");
}catch (MalformedURLException e1){
Razvoj aplikacije s daljinskim pozivanjem metoda ❘ 277
System.out.println(e1.getMessage());
}catch(RemoteException ex) {
ex.printStackTrace();
}
}
}
U klasi java.rmi.Naming postoje dvije metode koje mogu povezati objekt u registru.
Metoda bind() povezuje RMI poslužitelj s imenom. Ako povezivanje već postoji, izbacit će AlreadyBoundException. Metode rebind() zamjenjuje postojeće povezivanje s
novim. Osim što povezuje poslužitelj s imenom, time se osigurava i da klijenti koji zahtijevaju usluge poput getQuotes() ili getNasdaqSymbols() prime lokalne posrednike
udaljenih metoda.
Registar mora biti spreman i pokrenut u trenutku kada pokrenete program iz primjera
25-3. Jedan način da pokrenete registar je da unesete start rmiregistry u prozoru
naredbenog reda Windowsa ili rmiregistry na Mac OS-u.
Umjesto ručnog pokretanja registra, možete ga pokrenuti unutar samog programa
StarterServer pozivanjem metode:
LocateRegistry.sreateRegistry(1099);
Ako znate da je neki drugi proces već pokrenuo registar, samo uzmite referencu na
njega i povežite poslužitelj s njim. Metoda getRegistry() može biti pozvana bez argumenata ako se RMI registar izvršava na podrazumijevanom portu 1099. Ako to nije
slučaj, zadajte broj porata (5048 u sljedećem primjeru). Varijabla registry u sljedećem
odlomku koda je posrednik do udaljenog objekta StockServerImpl:
StockServerImpl ssi = new StockServerImpl();
Registry registry = LocateRegistry.getRegistry(5048);
registry.bind("QuoteService", ssi);
Razvoj RMI klijenata
Klijentski program koji se izvršava bilo gdje na Internetu pretražuje registar na domaćinskom računalu (koristeći ime domene domaćinskog računala ili njegovu IP adresu)
i dobiva referencu na udaljeni objekt. U primjeru 25-4 prikazan je primjer klijentskog
programa. Obratite pozornost na pretvaranje u tip podataka StockServer koji odgovara
podacima vraćenim iz metode lookup().
Iako je klasa StockServerImpl povezana s imenom QuoteService, budući da ta klasa
implementira sučelje StockServer, možemo u njega pretvoriti vraćeno objekt. Varijabla
myServer će vidjeti samo metode definirane u tom sučelju dok klasa StockServerImpl
može imati i druge javne metode.
Primjer 25-4: RMI klijent
package client;
import java.rmi.*;
278 ❘ Lekcija 25 Daljinsko pozivanje metoda
import com.practicaljava.lesson25.StockServer;
public class Client {
public static void main (String args[]) {
if (args.length == 0) {
System.out.println("\nUsage: java -Djava.security.policy=security.
policy Client AAPL");
System.exit(0);
}
try {
StockServer myServer = (StockServer)
Naming.lookup("rmi://localhost:1099/QuoteService");
String price = myServer.getQuote(args[0]);
if (price != null){
System.out.println("The price of " + args[0] +
" is: $" + price);
}
else{
System.out.println("Invalid Nasdaq symbol. " +
"Please use one of these:" +
myServer.getNasdaqSymbols().toString());
}
} catch (MalformedURLException ex) {
System.out.println(ex.getMessage());
}catch (RemoteException ex2) {
System.out.println(ex2.getMessage());
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
Pitanja sigurnosti
Može li bilo koji RMI klijent ograničiti aktivnosti koje daljinski učitan kôd može izvršavati na lokalnom računalu? Može li poslužitelj ograničiti pristup? Možete zadati datoteku sa sigurnosnim pravilima u kojoj su popisana ograničenja pristupa. Na primjer, u
kodu iz primjera 25-3 i 25-4 možete pokrenuti metodu main() na sljedeći način:
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
Klasa java.rmi.RMISecurityManager proširuje klasu java.lang.SecurityManager i pruža
sigurnosni kontekst u kojem se izvršavaju RMI aplikacije. Kod RMI klijenata cilj je spriječiti
da daljinski učitan kôd preuzima neprovjeren kôd preko daljinskog pozivanja metoda.
RMI klijent koristi datoteku u kojoj su definirana sigurnosna pravila. Možete koristiti
podrazumijevanu sigurnosnu datoteku, java.policy, koja se nalazi u podmapi lib/
security mape u kojoj je instaliran JDK ili JRE. Podrazumijevana pravila kodu daju sva
Pokušajte sami ❘ 279
dopuštenja, ali možete izraditi vlastitu datoteku i pružit je preko parametra zadanog u
naredbenom redu ili u kodu, prije postavljanja sigurnosnog upravljača:
System.setProperty("java.security.policy", "mypolicyfile");
Za više informacija o datotekama sa sigurnosnim pravilima pogledajte dokument dostupan na adresi: http://download.oracle.com/javase/1.3/docs/guide/security/
PolicyFiles.html.
Java apleti također mogu poslužiti kao RMI klijenti, ali njima nisu potrebni RMI sigurnosni
upravljači. Jedino ograničenje koje im je nametnuto jeste da se mogu spajati samo sa RMI
poslužiteljem koji se izvršava na istom domaćinskom računalu na kojem su i oni instalirani.
Pronalaženje udaljenih objekata
Rmi klijenti pronalaze udaljene usluge korištenjem imenovanja ili usluge imenika. Usluga
imenovanja izvršava se na poznatom domaćinskom računalu i portu. Detaljnije ćemo o
njima govoriti u lekciji 31.
Do sada ste naučili da RMI poslužitelj može pokrenuti vlastiti registar koji nudi usluge
imenovanja za RMI klijente. Ponašanje registra definirano je sučeljem java.rmi.registry.Registry a primjer povezivanja s registrom vidjeli ste u dijelu „Registriranje udaljenih objekata“.
RMI registar se podrazumijevano izvršava na portu 1099, ali možete zadati i neki drugi.
Kada klijent želi pozivati metode na udaljenom objektu, on uzima referencu na taj objekt
pretraživanjem po imenu. Pretraživanje klijentu vraća daljinsku referencu.
Metoda lookup() uzima URL imena objekta u sljedećem formatu:
rmi://<domaćinsko_računalo>[:<port_usluge_imenika>]/<naziv_usluge>
domaćinsko_računalo je naziva računala u lokalnoj mreži ili naziv sustava imena
domena (DNS) na Internetu. port_usluge_imenika mora se zadati samo ako se usluga
imenovanja izvršava na portu drugačijem od podrazumijevanog. naziv_usluge stoji za
ime udaljenog objekta koji treba biti povezan s registrom.
Slika 25-1 prikazuje arhitekturu RMI aplikacije. U sljedećem dijelu implementirat ćete
tu arhitekturu za naš primjer
usluge pružanja cijena dionica.
Pokušajte sami
Cilj ove vježbe je da pokrenete
i testirate sve dijelove distribuirane aplikacije Stock Server.
Svi njeni dijelovi izvršavat će se
na istom računalu. Kako bismo
3. Client traži
poslužitelj po
imenu
RMI
klijent
4. Poslužitelj je
pronađen, odlomak
je primljen
5. Client poziva
odlomak metode
koji komunicira
s metodom na
poslužitelju
Slika 25-1
RMI
registar
1. Pokretanje
registra
2. Registriranje
poslužitelja
u registru
Vaš RMI
poslužitelj
280 ❘ Lekcija 25 Daljinsko pozivanje metoda
emulirali više računala otvorit ćete tri prozora naredbenog reda: jedan a RMI klijent,
drugi za registar i treći za poslužitelj. Ako sve bude funkcioniralo kako je planirano moći
ćete pokrenuti RMI klijent s jednim od simbola dionica poznatim poslužitelju i dobiti
cijenu te dionice.
Zahtjevi lekcije
Trebate imati instaliranu Javu.
Izvorni kôd i resurse primjera možete preuzeti sa stranice knjige na
www.wrox.com. Primjeri za ovu lekciju nalaze se u datoteci Lesson25.
Savjeti
Postoji RMI dodatak za Eclipse RMI koji može biti koristan za razvoj distribuiranih aplikacija temeljenih na daljinskom pozivanju metoda. On sadrži koristan pomoćni program RMI
Spy koji prikazuje sve dolazne i odlazne pozive metoda te mjeri vrijeme izvršavanja. Drugi
koristan program u tom dodatku je Registry Inspector koji prikazuje informacije o objektima iz registra. RMI dodatak možete preuzeti s adrese www.genady.net/rmi/index.html.
Korak po korak
1.
Izradite Eclipse projekt Lesson25 sa dva paketa: client i com.practicaljava.
lesson25.
2.
U paketu com.practicaljava.lesson25 izradite sučelje StockServer koje proširuje java.rmi.Remote, kao u primjeru 25-1.
3.
U paketu com.practicaljava.lesson25 izradite klasu StockServerImpl koja
proširuje UnicastRemoteObject i implementira sučelje StockServer, kao u primjeru 25-2.
4.
U paketu com.practicaljava.lesson25 izradite klasu StartServer iz primjera
25-3 da biste pokrenuli poslužitelj i povezali ga s uslugom imenika.
5.
U paketu client izradite klasu Client koja će pristupati udaljenom poslužitelju.
Slijedite kôd iz primjera 25-4.
Kompajlirajte sve klase.
6.
7.
8.
Otvorite tri prozora naredbenog reda.
Pokrenite RMI registar iz prvog prozora naredbenog reda. Usluga imenika osluškivat će na portu 1099. Ako program pokrećete na računalu s Windowsima
upotrijebite naredbu start rmiregistry. Ako koristite računalo s operativnim
sustavom Mac OS, zadajte naredbu rmiregistry.
Nemojte očekivati da ćete vidjeti potvrdu o uspješno pokrenutom registru. Ako
nema poruka o pogreškama, znači da je sve u redu.
Pokušajte sami 9.
❘ 281
U drugom prozoru naredbenog reda pokrenite i registrirajte StockServer s uslugom imenika (registrom) koji ste maloprije pokrenuli. U naredbenom redu zadajte
sistemsko svojstvo java.rmi.server.codebase – tu se nalazi kompajlirana klasa
StockServer. Ovaj primjer izradio sam na računalu Apple MacBook na koje sam
bio prijavljen s korisničkim imenom yfain11. Eclipse radni prostor bio je smješten
u mapi practicalJava. Opcija naredbenog reda codebase počinje s –D i prikazana je podebljano.
java -classpath /Users/yfain11/practicalJava/workspace/Lesson25/bin
-Djava.rmi.server.codebase=
file:/Users/yfain11/practicalJava/workspace/Lesson25/bin/ com.
practicaljava.lesson25.StartServer
Kad ste zadali naredbu sličnu prethodnoj poslužitelj će se pokrenuti, povezati s
registrom i ispisati sljedeću poruku u prozoru naredbenog reda:
<QuoteService> server is ready.
10.
Otvorite treći prozor naredbenog reda i prijeđite u mapu bin – u njoj bi se trebala nalaziti mapa client koju ste izradili u prvom koraku. Pokrenite program
client i proslijedite mu simbol dionice kao argument naredbenog reda. Client
će se povezati s „udaljenim“ poslužiteljem i primiti cijenu dionice. Na primjer:
java client.Client AAPL
Slika 25-2 prikazuje kako tri prozora naredbenog reda izgledaju na mom računalu.
Slično će izgledati i na računalu s Windowsima.
Slika 25-2
Odaberite Lesson 25 na DVD-u da biste pregledali prateći video za
ovu lekciju.