Sunteți pe pagina 1din 4

Soluii Java

Transfer de comportament
ntr-o aplicaie de tip client-server transferul de comportament nseamn posibilitatea de a executa un cod preluat
de la partener, cod care nu este disponibil nici mcar la momentul lansrii n execuie a clientului sau a serverului
respectiv.
Florentina Irina Popovici i Irina Athanasiu

Soluia la aceast problem este posibil n Java fie prin utilizarea de faciliti primitive (comunicaie la nivel de
socluri, serializare, ncrcare dinamic de clase, etc.), fie prin utilizarea mecanismelor oferite de RMI.
Utilizare de "faciliti primitive"
Considerm un exemplu de aplicaie format din mai muli clieni i un server. Clienii execut prelucrri descrise de
o interfa, a crei implementare trebuie s o obin de la server, sub forma unui obiect. n acest caz putem s
spunem c serverul este un server de comportamente. Pentru a pstra exemplul simplu i la dimensiuni minime vom
considera c serverul ofer un singur set de comportamente, care sunt conforme cu interfaa Compute. Aceast
interfa este implementat de clasele care descriu efectiv prelucrrile care trebuie executate, clase care n aplicaie
se vor numi ComputeImplxxx.
Comunicaia se realizeaz prin socluri. Deoarece va fi necesar transferul de obiecte ntre dou aplicaii s-a optat
pentru folosirea mecanismul de serializare, care ofer o soluie simpl i elegant pentru transferul de date
ncapsulate n obiecte. Simpla transmitere a unui obiect ctre client nu rezolv problema. Dup cum se tie, n mod
implicit la serializarea unui obiect se transmit datele coninute n obiect, ns nu se transmite efectiv i codul
corespunztor clasei obiectului. Cel care face deserializarea trebuie s aib acces la clasa corespunztoare obiectului
instaniat (de fapt, o clas cu acelai nume i cu aceeai semntur pentru metode, codul efectiv al metodelor poate
s fie diferit.), n caz contrar se va genera o excepie (ClassNotFoundException).
Serializarea i transferul claselor
Dac clasa creia i aparine obiectul serializat nu este disponibil este necesar aducerea acesteia. Mecanismul de
serializare permite extinderea aciunilor implicite tocmai n scopul soluionrii acestei probleme. Mai exact, clasele
ObjectOutputStream i ObjectInputStream, care sunt folosite la serializarea obiectelor, prevd metodele
annotateClass() i, respectiv, resolveClass(), care dac sunt implementate pot permite programatorilor s ncarce
clasele i din alte surse dect din sistemul local de fiiere.
Aceste metode au fost folosite n Exemplele 1 i 2.
Metoda implicit annotateClass() din ObjectOutputStream nu realizeaz nici o aciune. Dup cum se tie,
serializarea unui obiect poate s implice de fapt serializarea unui graf de obiecte, dac din obiectul iniial exist
referine la alte obiecte. Pentru fiecare clas pentru care exist un obiect instaniat serializat, se va scrie pe flux o
singur dat numele i semntura clasei dup care se apeleaz metoda annotateClass(). Metoda primete ca
parametru un obiect de tip Class ce reprezint clasa ale crei informaii sunt nregistrate n momentul curent.
Extensiile clasei ObjectOutputStream care redefinesc aceast metod pot memora pe flux orice informaii necesare
pentru a reconstitui codul clasei la deserializare.
Dup cum se poate observa din Exemplul 1, am ales soluia ncrcrii de pe reea a claselor. Cnd se scriu
informaiile despre o clas, se verific dac aceasta implementeaz interfaa Compute; n acest caz, pe flux se va
scrie numele mainii i un numr de port, de unde se poate obine codul asociat clasei. Evident, aceasta nu este
singura posibilitate, o alt variant fiind transmiterea codului obiect al clasei.
Metoda simetric metodei annotateClass() este resolveClass() din ObjectInputStream. Aceast metod va fi apelat o
singur dat pentru fiecare clas care a fost nregistrat pe flux. Unicul parametru al metodei este un obiect instan
a clasei ObjectStreamClass. Acesta descrie o clas serializat prin numele clasei i un identificator pentru
recunoaterea claselor compatibile. resolveClass() va trebui s ntoarc un obiect de tip Class care s reprezinte clasa
ale crei caracteristici corespund cu cele memorate pe flux nainte de apelarea metodei annotateClass(). Aceast
verificare se va face dup apelul lui resolveClass(), iar dac rezultatul nu este favorabil, se va genera excepia
InvalidClassException.
n Exemplul 2, s-a folosit metoda getName() a clasei ObjectStreamClass pentru a se afla numele clasei care va fi
tratat. nainte de a se ncrca clasa de pe reea se testeaz dac nu poate fi folosit metoda forName() a clasei Class.
Aceast metod va ncerca localizarea i ncrcarea clasei folosind ncrctorul de clase al clasei curente, n caz de
eec va genera excepia ClassNotFoundException.
n corpul metodei resolveClasss() se pot citi toate informaiile care au fost scrise n annotateClass(). Dup cum am
spus, pentru fiecare clas a unui obiect serializat s-a memorat i locul (numele mainii i numrul portului) de unde

poate fi citit codul clasei. Aceasta nseamn c la deserializare, pentru clasele care implementeaz interfaa
Compute, aceste informaii vor putea fi folosite pentru a se transfera codul claselor de pe maina specificat.
ncrcarea dinamic a claselor
Fiecare clas care este folosit ntr-un program Java este ncrcat de un "class loader" - ncrctor de clase. Rolul
acestuia este de a localiza sau genera date care reprezint definiia unei clase. Acest ncrctor de clase este descris
la rndul lui de o clas: n pachetul java.lang exist clasa abstract ClassLoader care specific aciunile de baz care
trebuie s fie efectuate la ncrcarea unei clase. Maina Virtual Java (MVJ) ine evidena claselor ncrcate sub
forma unui triplet: ncrctor de clase, nume clas i obiect de tip Class. Dou clase care au acelai nume nu sunt
identice dac pentru ncrcarea lor s-au folosit ncrctoare de clase diferite. n clasa Class, exist metoda
getClassLoader() care ntoarce ncrctorul de clase care a fost folosit pentru ncrcarea clasei respective.
Cei care vor s-i scrie propriul ncrctor de clase vor folosi cu siguran una din formele metodei loadClass().
Aceast metod trebuie s ntoarc un obiect de tip Class, care este reprezentarea clasei al crei nume a fost transmis
ca parametru. Dac metoda nu reuete ncrcarea clasei, atunci trebuie generat excepia ClassNotFoundException.
public Class loadClass(String nume) throws ClassNotFoundException
protected Class loadClass(String nume, boolean rezolva) throws ClassNotFoundException
Numele de ncrctor de clase nu acoper toate aciunile pe care trebuie s le realizeze acesta. Pentru clarificare
trebuie spus c o clas trebuie s suporte mai multe operaii nainte de a putea fi folosit: ncrcarea, legarea
(verificarea, prepararea i rezolvarea referinelor simbolice) i iniializarea. Legarea reprezint aciunea de integrare
a definiiei unei clase n MVJ, astfel nct clasa s poat fi executat. Este evident c nainte de crearea unei instane
sau apelarea unei metode a unei clase este necesar i legarea acesteia. Dac se folosete prima form a metodei
loadClass(), atunci se cere i legarea clasei, pe cnd pentru forma a doua acest lucru se va face dup cum parametrul
rezolva are valoarea de adevr true sau false. Legarea clasei se realizeaz prin apelarea metodei:
protected final void resolveClass(Class c);
Pentru crearea unui obiect de tip Class, care s se comporte conform cu aciunile descrise de un cod Java compilat se
folosete metoda defineClass().
protected final Class defineClass(String nume, byte data[], int deplasament, int lungime)
Parametrii primii sunt numele clasei, un vector care conine codul asociat clasei (de exemplu, cel care se gsete n
fiierul ".class"), poziia n vector de unde ncepe codul i dimensiunea lui.
n Exemplul 3 este prezentat un ncrctor de clase: NetworkClassLoader (care extinde ClassLoader). Acesta tie s
ncarce clase de pe o main al crei nume l primete n constructor. Pentru pstrarea simplitii exemplului s-a
considerat c se pot ncrca clase doar de pe o anumit main i de la un anumit port.
Ar trebui precizat i cine anume i spune ncrctorului de clase s ncarce clasele. O prim variant este ca undeva
n program s se apeleze explicit metodele unui ncrctor de clase. n Exemplul 2, se observ c se folosete
metoda loadClassFromLocation() pentru a se ncrca o clas. n cea de a doua variant, metoda este apelat implicit.
Tot pentru exemplul anterior, s presupunem c ntr-o clas ncrcat de pe reea se folosesc alte clase care nu se
gsesc pe maina local. Pentru ncrcarea lor va fi apelat implicit metoda loadClass() a ncrctorului de clase care
a ncrcat clasa care le folosete pentru prima dat.
S considerm urmtorul exemplu. Am spus c pe server se gsesc clase care implementeaz interfaa Compute. S
presupunem c una dintre clase: ComputeImplVerify, are definit cmpul verify care este o instan a clasei
VerifyConstraints. Aceast clas va fi folosit n timpul calculului pentru a se asigura ndeplinirea unor constrngeri
impuse datelor. Clientul nu cunoate dect interfaa Compute, i primete clasele cu care va realiza efectiv
prelucrrile de la server, aa c vom presupune c nu are disponibil clasa VerifyConstraints. Cnd clientul primete
clasa ComputeImplVerify, pentru a o putea folosi trebuie s cunoasc i implementarea clasei VerifyConstraints.
Deoarece VerifyConstants este folosit din cadrul clasei ComputeImplVerify, pentru ncrcarea ei MVJ va apela
metoda loadClass() a ncrctorului de clase care a ncrcat clasa ComputeImplVerify. n cazul nostru acesta va
ncerca ncrcarea clasei VerifyConstants de pe aceeai main de pe care a ncrcat i ComputeImplVerify.
Pentru verificarea celor spuse mai sus ncercai s vedei ce se ntmpl n cazul n care clientul are acces la clasa
ComputeImplVerify, dar nu i la clasa VerifyConstraints. Clasa ComputeImplVerify va fi ncrcat de ncrctorul de
clase implicit al mainii virtuale, iar pentru ncrcarea clasei VerifyConstraints se va ncerca tot folosirea lui i se va
genera o excepie.
n Java se consider ca fiind sigure clasele ncrcate local, aa c se va prefera ncrcarea local claselor, dac este
posibil. n cadrul acestei metode se remarc faptul c se ncearc ncrcarea claselor din sistemul local de fiiere prin
folosirea metodei findSystemClass(String), dei clasele ar fi putut fi ncrcate de pe reea. Doar dac aceast metod
eueaz (se genereaz excepia ClassNotFoundException), se va ncerca ncrcarea clasei de pe reea.
Tot n Exemplul 3 se observ c toate clasele care s-au ncrcat se memoreaz ntr-o tabel de dispersie pentru a nu
se ncrca de dou ori aceeai clas. Acest lucru este asigurat de folosirea metodei findLoadedClass(), care ntoarce

clasa cu numele dat ca parametru (dac ea a mai fost ncrcat de acest ncrctor de clase). Metodele loadClass()
sunt sincronizate, astfel nct ncrctorul s funcioneze corect chiar dac dou fire de execuie vor ncerca
ncrcarea simultan a unor clase.
Trebuie precizat c pentru scrierea exemplelor a fost folosit jdk1.1.x, scopul fiind mai mult prezentarea elementelor
principale folosite la ncrcarea claselor. ncepnd cu jdk1.2 se pune un mai mare accent pe securitate, un gestionar
de securitate (SecurityManager) putnd s foloseasc ncrctorul de clase pentru delimitarea unui domeniu de
securitate. n plus, fiecare ncrctor de clase are definit un printe, mai nti ncercndu-se ncrcarea unei clase de
ctre printe. n pachetul java.net este definit un astfel de ncrctor de clase care ncearc localizarea codul claselor
folosind un URL (sau mai multe).
Implementare client i server
Serverul este implementat de clasa ClassServer.(Exemplu 4) El ateapt cereri pe un anumit port i trimite pe soclu
obiectul care este cerut de clieni. Pentru serializare se folosete un flux specializat, (Annotate ClassOutputStream),
care dup cum am vzut, tie s trimit informaii necesare localizrii unei clase.
Pentru a ine compact exemplul s-a considerat c acelai server care transmite obiectele care descriu operaiile de
calcul transmite i codul claselor. Din acest motiv a fost necesar abordarea soluiei n care cererile clienilor sunt
tratate ntr-un nou fir de execuie.
Cererea pentru trimiterea codului unei clase este format din numele clasei. Dup cum se tie, numele complet al
unei clase este dat de o succesiune de nume de pachete, desprite prin ".", terminat cu numele scurt al clasei (de
exemplu java.lang.Thread este numele complet al clasei Thread). Acesta este numele obinut prin apelul metodei
getName() a clasei Class.
Pentru localizarea codului unei clase se nlocuiesc n denumirea clasei caracterele "." cu separatorul de cale folosit
pe sistemul pe care se execut programul (pentru sistemele Windows cu "\", iar pentru cele Unix cu "/"). Calea
obinut este prefixat cu un nume de director (codebase), specificat la crearea serverului. De exemplu, dac
valoarea lui codebase este "/home/user/public_html/javaCode/" (pentru un sistem Unix), iar numele clasei este
"compute.ComputeImplVerify" se va ncerca transmiterea coninutului fiierului
"/home/user/public_html/javaCode/compute/ComputeImplVerify.class".
Aplicaia care implementeaz clientul este simpl (Exemplul 5). Clientul cere serverului un obiect i apoi apeleaz
metoda compute() pentru a realiza calculul. Pentru citirea obiectului se folosete o instan a clasei
AnnotateClassInputStream care, la nevoie va folosi un ncrctor de clase pentru a aduce clasele de pe reea.
Se poate observa c pentru client lucrurile se desfoar transparent, apelul metodei compute(), executndu-se ca i
cum s-ar folosi o clas local.
RMI i transferul de comportament
Spre deosebire de RPC (Remote Procedure Call), RMI (Remote Method Invocation) permite nu numai transferul de
date ci i transferul de obiecte, ceea ce implicit poate s nsemne comportament.
Ca exemplu s considerm un client care trebuie s completeze un formular. Anumite intrri n formular trebuie s
se calculeze automat pe baza unor date introduse, conform unei formule care se poate schimba foarte des. S
presupunem c exist muli clieni pe care se execut aceeai prelucrare. Modificarea formulei ar putea s presupun
modificarea codului pentru fiecare client n parte. Alternativa este ca aceast comportare s se obin ca un obiect de
la server. Pentru orice modificare de formul se va schimba tipul obiectului care implementeaz calculul.
S presupunem c serverul transmite ca rezultat al unui apel RMI obiectul (serializat) care descrie aciunea pe care
trebuie s o realizeze clientul. Clasa a crui instan este obiectul respectiv poate s fie ncrcat dinamic la client
dac acesta nu are acces la definiia ei. Pentru aceasta, la serializarea i deserializarea parametrilor i a rezultatului
unui apel RMI se folosesc clase care extind clasele ObjectOutputStream, respectiv ObjectInputStream i care
folosesc un mecanism asemntor cu cel descris n exemplele precedente (dar bineneles mai complicat).
S considerm c pentru calcul exist interfaa din Exemplul 6. O implementare posibil pentru aceast interfa este
cea din Exemplul 7.
Interfaa oferit de server n acest caz este prezentat n Exemplul 8. Se observ c prin metoda pe care o ofer
pentru apelul la distan serverul va transmite o referin la un obiect care implementeaz interfaa calculInterf.
Exemplul 9 reprezint implementarea serverului iar o schi de cod pentru client este cea din Exemplul 10.
Dac se schimb n implementarea serverului clientUnu cu clientDoi atunci automat orice client care apeleaz
serverul va primi i codul pentru noul client.
Ar trebui spuse cteva cuvinte i despre modul n care se execut exemplul. Clientul i serverul se gsesc (de obicei)
pe maini diferite. Fiecare dintre ei trebuie s cunoasc interfeele calculInterf i serverCalculInterf, ns nu au acces
la implementrile acestora (i n general, clientul nu are acces nici la clasele xxx_Stub.class generate folosind
utilitarul rmic).

La lansarea n execuie a serverului trebuie s se defineasc proprietatea java.rmi.server. hostname care trebuie s
aib ca valoare numele mainii pe care se execut serverul. Aceast proprietate este necesar pentru a se asigura
funcionarea corect a RMI-ului n cazul cnd pe sistemul respectiv RMI nu obine numele corect al mainii. Cnd
se definete aceast proprietate este bine ca URL-ul care identific serverul la registru s aib forma
"rmi://localhost/Identificator Server". O alt proprietate care trebuie definit este java.rmi. server.codebase. Aceasta
trebuie s aib ca valoare un URL care s localizeze codul claselor care pot eventual s fie ncrcate dinamic de
client. Pentru un sistem UNIX comanda pentru lansarea unui server pe maina kermit.cs.pub.ro, cu clase care se
gsesc la locaia http://kermit.cs. pub.ro/ ~user/delaSlaC/ comanda poate s fie urmtoarea:
java -Djava.rmi.server.codebase=http://kermit.cs.pub.ro/~user/delaSlaC/
-Djava.rmi.server.hostname=kermit.cs.pub.ro clasaDeStartServer
Bineneles c este obligatoriu ca pe maina pe care se execut server-ul s existe un server de Web, care s fac
posibil folosirea protocolului http pentru transferul claselor. Fiierele care conin codul claselor (sau arhiva jar)
trebuie s aib definite suficiente drepturi de acces pentru citire.
S considerm i situaia invers n care clientul dorete ca serverul s execute o anumit prelucrare conform
algoritmului furnizat de data aceasta de ctre client. n acest caz obiectul care efectueaz prelucrarea se va transmite
de la client la server, care va face execuia. O astfel de abordare este util n situaia n care este vorba de calcule
care sunt prea consumatoare de resurse pentru a fi executate pe client.
S considerm, de exemplu, din nou, interfaa calculInterf i dou implementri calculUnu i calculDoi. De data
aceasta serverul ofer dou metode: una n care primete obiectul care efectueaz calculul i alta care efectueaz
calculul i ntoarce rezultatul corespunztor (veyi Exemplu 11 si Exemplu 12).
i la client trebuie definit proprietatea java.rmi.server.codebase (cu acceai semnificaie ca mai sus) n cazul n care
se transfer date de la client la server, iar serverul nu are acces la toate clasele obiectelor pe care le primete ntr-un
apel RMI. Comanda pentru lansarea n execuie a clientului poate fi urmtoarea:
java -Djava.rmi.server.codebase=http://fozzie.cs.pub.ro/~user/delaClaS/ clasaDeStartClient
Serverul va ncerca ncrcarea claselor de la URL-ul http://fozzie.cs.pub.ro/~user/delaClaS/
Concluzii
De multe ori se consider c programarea distribuit este numai un nlocuitor al programrii paralele. Exist ns
numeroase situaii, de tipul celor pe care le-am considerat n articolul nostru, specifice programrii distribuite i care
nu se regsesc ca problematic n programarea paralel.

S-ar putea să vă placă și