Professional Documents
Culture Documents
Java I Internet Programiranje
Java I Internet Programiranje
Sadraj
0. Namena i program kursa..........................................................................................1 0.1 Potrebno predznanje 1 0.2 Program kursa 2 1. Uvod u programski jezik Java.................................................................................3 1.1 Java virtuelna maina 3 1.2 Programski jezik Java 4 1.3 Osnovni koncepti 4
1.3.1 Tipovi podataka 4
1.4 Klase i objekti 1.5 Prevoenje i pokretanje programa 1.6 Reference na objekte 1.7 Operatori 1.8 Kontrola toka programa 1.9 Inicijalizacija objekata 1.10 Unitavanje objekata 1.11 Metode i njihovi parametri 1.12 Kljuna re final 1.13 Kljuna re static 1.14 Nizovi 1.15 Viedimenzionalni nizovi 1.16 Paketi, CLASSPATH i JAR arhive
1.16.1 Paketi 1.16.2 CLASSPATH 1.16.3 JAR arhive 1.16.4 Podrazumevane komponente u CLASSPATH-u
5 6 7 9 9 10 10 11 13 14 14 15 17
17 19 19 20
1.17 Zadatak: klasa Matrix 1.18 Nasleivanje 1.19 Modifikatori pristupa 1.20 Redefinisanje metoda 1.21 Apstraktne klase 1.22 Interfejsi 1.23 Unutranje klase 1.24 Polimorfizam
21 22 22 23 23 24 24 25
1.25 Izuzeci 1.26 Klasa Object 1.27 Klasa String 1.28 Primeri nekih klasa iz standardne biblioteke
1.28.1 Klasa java.util.Vector 1.28.2 Klasa java.util.Hashtable 1.28.3 Klasa java.util.StringTokenizer
25 28 28 29
30 30 31
1.29 Konvencije davanja imena 1.30 Generisanje programske dokumentacije i javadoc 1.31 Zadaci: modifikacije klase Matrix
31 31 33
2. Konkurentno programiranje u Javi......................................................................35 2.1 Kreiranje programskih niti 35 2.2 Daemon i non-daemon niti 36 2.3 Primer programa sa vie niti 37 2.4 Sinhronizacija niti 39 2.5 Dodatne metode za sinhronizaciju 40 2.6 Primer programa sa sinhronizacijom niti 40 2.7 Zadatak: problem pet filozofa 44 3. GUI aplikacije i JavaBeans ....................................................................................45 3.1 AWT i Swing 45 3.2 Event-driven model 46 3.3 Osnovna struktura GUI aplikacije 46 3.4 Razlika u konstrukciji GUI-ja za Windows i Java aplikacije 47 3.5 Dodavanje komponenti na prozor 47 3.6 Prostorni raspored komponenti 48 3.7 Rukovanje dogaajima 50
3.7.1 Dogaaji, oslukivai i komponente 3.7.2 Oslukivai kao unutranje klase 50 51
53 57
57 58 59
3.10. Aplet i aplikacija istovremeno 3.11. Korisniki definisane komponente 3.12. JavaBeans
60 61 63
4.2. Identifikacija vorova mree 4.3. Klasa Socket 4.4. Tipian tok komunikacije klijent strana 4.5. Klasa ServerSocket 4.6. Tipian tok komunikacije server strana 4.7. Server koji opsluuje vie klijenata 4.8 Primer klijent/server komunikacije
67 67 68 69 69 70 70
74
5. Veba: chat aplikacija .............................................................................................75 5.1. Uvodna razmatranja 76 5.2. Funkcije klijenta 76 5.3. Funkcije servera 78 6. Rad sa bazama podataka JDBC..........................................................................81 6.1. Osnovne odrednice 81 6.2. JDBC drajveri 81 6.3. Uspostavljanje veze sa bazom podataka 82 6.4. Postavljanje upita 83 6.5. DML operacije 85 6.6. Uzastopno izvravanje istih SQL naredbi 85 6.7. Pozivanje uskladitenih procedura 87 6.8. Upravljanje transakcijama 90 6.9. Dodatak: inicijalizacija drajvera 91 7. Uvod u vieslojne klijent/server sisteme ............................................................93 7.1. Klasini klijent/server sistemi 93 7.2. WWW i Java kao platforma za klijente 94 7.3. Troslojni klijent/server sistemi 95 8. Dinamiko generisanje HTML-a i servleti .........................................................99 8.1. HTTP protokol 99 8.2. Statiki i dinamiki Web sadraji 101 8.3. Servleti 101
8.3.1. Metoda init 8.3.2. Metoda destroy 8.3.3. Metoda doGet 102 102 102
8.4. Primer: elementarni servlet 8.5. Analiza zaglavlja HTTP zahteva 8.6. Konkurentni pristup servletu 8.7. Praenje sesije korisnika 8.8. Preuzimanje podataka sa HTML formi
8.8.1 GET i POST zahtevi
111
9. Java Server Pages ...................................................................................................114 9.1. JSP koncept 114 9.2. Vrste dinamikih elemenata 115
9.2.1. Izrazi 9.2.2. Skriptleti 9.2.3. Deklaracije 9.2.4. Direktive 115 115 117 117
9.3. Predefinisane promenljive 9.4. Skladitenje podataka i JavaBeans 9.5. Opseg vidljivosti JavaBean komponenti 9.6. Definisanje sopstvenih tagova
10. Tehnologije distribuiranih objekata ...............................................................123 10.1. Osnovni koncepti 123 10.2. RMI 124
10.2.1. Faze u pisanju RMI programa 10.2.2. RMI interfejs 10.2.3. RMI serverski objekat 10.2.4. RMI registry 10.2.5. RMI klijent 10.2.6. Primer RMI programa 10.2.7. RMI i multithreading 124 125 126 126 127 127 129
10.3. CORBA
10.3.1. Osnovne odrednice 10.3.2. IDL 10.3.3. CORBA Naming Service 10.3.4. Proces pisanja CORBA programa 10.3.5. CORBA izuzeci 10.3.6. Pozivi unatrag 10.3.7. RMI i CORBA
129
129 130 131 131 134 136 137
137
138 139 140 140
11. Veba: Web shop aplikacija ..............................................................................144 11.1. Model podataka 144 11.2. Struktura Web sajta 145 11.3. Softverske komponente 147 11.4. Dodatna razmatranja 147
11.4.1. Rukovanje konekcijama sa bazom podataka 147
Poglavlje 0
Za polaznike je poeljno, ali ne i obavezno, poznavanje jezika C++. Polaznici koji poznaju ovaj jezik mogu daleko bre usvajati materiju koja se izlae na poetku kursa.
U prvom poglavlju, Uvod u programski jezik Java, govori se o osnovnim karakteristikama Jave, kao programskog jezika, i kao platforme za izvravanje programa. Upoznaje se koncept Java virtuelne maine (JVM) i prenosivosti prevedenog Java koda. Zatim se vri pregled osobina Jave kao programskog jezika, i obrauju se jezike konstrukcije. Drugo poglavlje nosi naziv Konkurentno programiranje u Javi i donosi pregled jezikih koncepata koji omoguavaju pisanje konkurentnih programa. Tree poglavlje, GUI aplikacije i JavaBeans, predstavlja saeti prikaz pisanja aplikacija i apleta sa grafikim korisnikim interfejsom. Definie se struktura ovakvih aplikacija, nain reagovanja na dogaaje koje izaziva korisnik i navode primeri korienja brojnih komponenti za izgradnju korisnikog interfejsa. etvrto poglavlje, Mreno programiranje u Javi, definie pojmove koji se koriste prilikom pisanja programa koji komuniciraju preko mree, a zatim opisuje elemente jezika koji se koriste za pisanje ovakvih programa. Podrazumeva se rad preko TCP/IP mree. Peto poglavlje sadri vebu, iji je cilj konstrukcija mrene klijent/server aplikacije za chat preko TCP/IP mree. Konstrukcija ovakvog sistema obuhvata sve prethodno obraene teme. U estom poglavlju Rad sa bazama podataka JDBC dat je uvod u metode pristupa i korienja relacionih baza podataka iz Java programa. Podrazumeva se korienje sistema za upravljanje relacionim bazama podataka sa kojima se komunicira preko jezika SQL. Sedmo poglavlje, Uvod u vieslojne klijent/server sisteme, definie okvire u kojima se nalazi materija izloena u narednim poglavljima. Osmo poglavlje Dinamiko generisanje HTML-a i servleti prikazuje servlete, osnovnu Java tehnologiju za dinamiko generisanje Web sadraja i izgradnju Web sajtova. U narednom poglavlju, Java Server Pages, predstavljena je tehnologija za pisanje dinamikih Web stranica koja omoguava razdvajanje zadataka Web dizajnera i programera, pojednostavljujui tako razvoj Web-orijentisanih informacionih sistema. Deseto poglavlje, Tehnologije distribuiranih objekata, donosi saet prikaz tehnologija namenjenih za pisanje distribuiranih objektno-orijentisanih aplikacija dostupnih iz programskog jezika Java. Poslednje poglavlje sadri vebu iji je cilj konstrukcija Web aplikacije za elektronsko poslovanje. Zadatak je napisati softver za Web sajt koji omoguava kupovinu putem Web-a.
Poglavlje 1
Kao posledica prethodno reenog, moe se rei da je Java kombinacija programskog jezika i platforme za izvravanje programa koja ima nekoliko vanih osobina: Projektovana je tako da to manje zavisi od karakteristika konkretnog raunarskog sistema na kome se izvrava. Jednom napisan i preveden program se moe pokretati na bilo kojoj platformi za koju postoji odgovarajui JVM interpreter. Dakle, prenosivost programa je garantovana na nivou izvrnog (prevedenog) koda. Java je interpretirani jezik, to ima odgovarajui efekat na brzinu izvravanja programa.
Proizvod prevoenja izvornog Java koda je program predvien za rad u okviru JVM, koji se esto naziva bajt-kod (byte-code).
Iz tabele se vidi da Java raspolae primitivnim tipovima koji su na isti nain definisani i u drugim programskim jezicima. Jedini izuzetak je tip char, koji zauzima dva bajta, umesto uobiajenog jednog bajta. Radi se o tome da se tipom char moe predstaviti svaki karakter definisan Unicode standardom koji definie kodni raspored koji obuhvata praktino sve dananje jezike (ukljuujui indoevropske, dalekoistone, itd). To znai da su Java programi u startu osposobljeni da rade sa viejezinim tekstom, ili u naim uslovima, ravnopravno sa srpskom latinicom i srpskom irilicom. Treba primetiti da string, kao esto korien tip podatka, nema odgovarajui primitivni tip u Javi, slino jezicima C i C++.
(Plavom bojom su navedene kljune rei jezika). Klasa ima naziv Automobil, definie jedan atribut koji se zove radi i logikog je tipa (boolean), i definie dve metode koje se mogu pozvati nad objektima te klase, metode upali i ugasi. Kreiranje objekata koji predstavljaju instance (primerke) ove klase moe se obaviti na sledei nain:
Automobil a = new Automobil(); Automobil b = new Automobil();
Time su kreirana dva objekta klase Automobil, koji su nazvani a i b. Atributu radi objekta a moe se pristupiti pomou:
a.radi
Ovo do sada reeno izuzetno podsea na C++. Neke od osobina Jave koje je bitno razlikuju u odnosu na C++ su: Nije mogue definisati promenljive i funkcije izvan neke klase. Samim tim, nije mogue definisati globalne promenljive, niti globalne funkcije ili procedure. Ne postoje odvojene deklaracija i definicija klase. Java poznaje samo definiciju klase. Prema tome, ne postoje posebni header fajlovi koji sadre deklaraciju klase.
Kako Java ne doputa postojanje bilo ega to bi postojalo izvan neke klase, postavlja se pitanje odakle poinje izvravanje Java programa. C i C++ koriste funkciju main kao osnovnu funkciju od koje poinje izvravanje programa. Java takoe koristi funkciju main, samo to i ta funkcija mora biti metoda neke klase (u C++ terminologiji bi se reklo funkcija lanica). Izgled jedne klase koja sadri metodu main, i predstavlja primer jednog izvrivog Java programa dat je u sledeem primeru:
class Hello { public static void main(String args[]) { System.out.println(Hello world!); } }
(Trenutno nije bitno zato metoda main mora biti definisana kao public static void, ali mora biti tako.) Kompletan tekst ove klase smeten je u datoteku Hello.java. Treba obratiti panju na naziv ove datoteke: njena ekstenzija je obavezno .java, a ime mora biti jednako imenu klase, ukljuujui i razliku izmeu velikih i malih slova. Standardna preporuka je da se svaka klasa programa smeta u posebnu datoteku. Naziv datoteke mora odgovarati nazivu klase na prethodno opisani nain. Iako e neki prevodioci dopustiti smetanje teksta vie klasa u isti fajl, ta praksa se ne preporuuje. Dakle, svakoj Java klasi odgovara jedan fajl sa identinim nazivom i ekstenzijom .java.
(Primeri za prevoenje i pokretanje opisuju korienje alata iz standardnog JDK paketa). Dakle, kompajler se poziva komandom javac, a kao parametri navode se imena onih datoteka koje elimo da prevedemo (moe ih biti vie, i moemo da koristimo doker-znake). Treba obratiti panju na to da je navoenje ekstenzije datoteke obavezno, iako je ekstenzija uvek .java.
Prevoenjem datoteke Hello.java dobija se datoteka Hello.class, koja sadri JVM bajt-kod koji pripada klasi Hello. Naziv te datoteke obavezno mora imati ekstenziju .class, i naziv mora biti jednak nazivu klase. Svaka klasa, data u odgovarajuem .java fajlu, kao rezultat prevoenja daje odgovarajui .class fajl. Treba obratiti panju da je kod prevoenja ovog primera neophodno pozicionirati se (u okviru DOS Prompt-a ili nekog shell-a na UNIX-u) u onaj direktorijum gde se nalazi .java fajl. Za pokretanje ovog programa dovoljan je dobijeni Hello.class fajl. Program emo pozvati iz komandne linije (ponovo se moramo nalaziti u istom direktorijumu gde i .class fajl) sledeom komandom:
java Hello
Ovog puta nije dozvoljeno navoenje ekstenzije .class prilikom pokretanja. U sluaju da se tako uini dobiemo poruku o greci. Sada sledi primer jednog programa koji se sastoji iz dve klase:
Automobil.java class Automobil { boolean radi; void upali() { radi = true; } void ugasi() { radi = false; } } Test.java class Test { public static void main(String args[]) { Automobil a; a = new Automobil(); a.upali(); } }
Ove dve klase su smetene u odgovarajuim datotekama Automobil.java i Test.java. Njihovim prevoenjem dobijaju se dva .class fajla, Automobil.class i Test.class. Program se pokree tako to se navodi ime one klase koja sadri metodu main, to bi u ovom primeru bilo
java Test
Nema nikakve prepreke da vie klasa koje ine program poseduju metodu main. Odakle e se poeti sa izvravanjem programa? To se odreuje prilikom pokretanja programa, tako to se navodi ime one klase iju metodu main elimo da pokrenemo.
deklarisali smo promenljivu a tipa Automobil, a zatim kreirali objekat klase Automobil i vezali ga za tu promenljivu a.
Promenljiva a predstavlja, zapravo, referencu na objekat klase Automobil. Promenljiva a je lokalna promenljiva u metodi main, tako da se smeta na stek, na slian nain kako se to odvija u drugim jezicima, dok se memorija za objekat klase Automobil zauzima na heap-u programa. Slika 1.1 prikazuje tu situaciju.
a stek
heap
U tom smislu kae se da je a referenca na objekat klase Automobil. Promenljiva a nije pointer u smislu kako ga definie C++, jer nije doputena nikakva aritmetika sa ovakvim promenljivama, niti dodeljivanje proizvoljnih vrednosti. Jedina vrednost koju referenca moe da sadri je adresa (namerno je pod navodnicima jer to nije prava adresa u memoriji) pravilno inicijalizovanog objekta na koga ukazuje. Sledei primer prikazuje kreiranje dva objekta klase Automobil i inicijalizaciju referenci tako da ukazuju na odgovarajue objekte. Reference se nalaze na steku programa, dok su objekti smeteni na heap.
Automobil a = new Automobil(); Automobil b = new Automobil();
b a stek
b a stek
Postavlja se pitanje ta se u ovoj situaciji deava sa objektom na koga je ukazivala referenca b: taj objekat vie nije dostupan ni na jedan nain, jer je jedina mogunost da se nekoj referenci dodeli vrednost dodela vrednosti postojee reference (tipa b = a) ili dodela vrednosti reference na novokreiran objekat (tipa a = new ...). Kako objekat vie nije dostupan, valjalo bi ga ukloniti iz memorije kako bi se izbeglo curenje memorije. Java ne poseduje posebnu jeziku konstrukciju kojom se memorija dealocira (poput operatora delete u jeziku C++). Za dealokaciju memorije zaduen je poseban pozadinski proces programa koji se naziva garbage collector (skuplja ubreta). O garbage collector-u e biti vie rei u odeljku 1.10.
1.7 Operatori
Operatori koji slue za gradnju Java izraza su operatori koji su, praktino, preuzeti iz jezika C++. Moemo ih grupisati u nekoliko grupa: aritmetiki operatori (+, -, *, /) relacioni operatori (==, <, >, =, !=, >=, <=) logiki operatori (&&, ||, !) bit-operatori (&, |, !) operator dodele (=)
Bitna razlika u odnosu na C++ je postojanje primitivnog tipa boolean; vrednost logikih izraza mora biti ovog tipa. To znai da su vrednosti u if ili while konstrukcijama logikog tipa, pa nije mogue pisati
while (1)
ili, jo opasnije
if (a = 1)
Prioritet operatora je definisan na standardan nain. Detaljna specifikacija operatora data je u specifikaciji jezika.
U ovom primeru, konstruktor e biti pozvan prilikom kreiranja objekta klase A. Na primer:
A varA = new A();
je deklaracija reference varA koja ukazuje na objekat klase A, pri emu se odmah vri i inicijalizacija ove reference na novokreirani objekat. U trenutku kada se izvri ovaj red (zapravo kreiranje objekta pomou new A()), na konzoli e se ispisati
konstruktor
to je rezultat izvravanja konstruktora. Ukoliko se unutar definicije klase ne navede nijedan konstruktor, kompajler e sam generisati tzv. podrazumevani konstruktor koji nema parametre i telo mu je prazno.
neophodno obaviti neku operaciju neposredno pre nego to GC uniti objekat, ta operacija se moe implementirati u okviru specijalne metode finalize. Metoda finalize se poziva neposredno pre unitavanja objekta od strane GC-a. Ovde je vano naglasiti da metodu finalize ne treba koristiti za oslobaanje zauzetih resursa, jer se metoda finalize ne mora pozvati! Naime, GC sam odreuje kada e ukloniti objekat iz memorije, i lako se moe desiti da se to nikad ne dogodi: program je pokrenut, radio je neko vreme, raunar raspolae sa dovoljno radne memorije tako da GC nije poeo sa uklanjanjem objekata, i tako sve do zavretka rada programa; GC nije uklonio objekat, samim tim nije pozvao metodu finalize, i eventualno oslobaanje zauzetih resursa se nije ni desilo. Sledi primer klase koja implementira metodu finalize (pokretanjem ovog programa sa java A verovatno e se demonstrirati mogunost da se GC nikad ne aktivira):
class A { A() { System.out.println("Konstruktor"); } protected void finalize() throws Throwable { System.out.println("finalized"); } public static void main(String[] args) { A a = new A(); System.out.println("main running..."); } }
Parametri metode mogu biti primitivni tipovi i reference na objekte; tip rezultata metode moe biti primitivni tip ili referenca na objekat. esto se postavlja se pitanje da li promena vrednosti parametra u okviru metode ima efekta na promenljivu koja je koriena kao parametar nakon povratka iz metode. Posmatrajmo sledeu metodu:
void test(Automobil a) { a.radi = true; }
U pitanju je metoda koja u okviru svog tela vri modifikaciju svog parametra a preko metode upali. (Koristi se klasa Automobil definisana u prethodnim primerima). U sluaju da se ova metoda pozove u sledeem segmentu koda:
Automobil x = new Automobil();
11
vrednost atributa radi objekta x bie true. Slika 1.4 ilustruje ta se zapravo desilo: na steku je kreirana referenca x na objekat klase Automobil. Zatim je vrednost atributa radi ovog objekta postavljena na false (slika a). Nakon toga pozvana je metoda test, sa referencom x kao parametrom. Parametri metoda se, slino kao i u drugim programskim jezicima, smetaju na stek prilikom poziva metode (ovde je nebitno u kom redosledu). Tako je i referenca x iskopirana na stek jo jednom (slika b). U okviru tela metode test ova druga kopija reference x se koristi kao parametar metode i preko nje se pristupa istom onom objektu na koji ukazuje i originalna referenca x. Pristup objektu se u ovom sluaju svodi na promenu vrednosti atributa radi na true (slika c). Kod vraanja iz metode nazad, sa steka se uklanjaju parametri korieni prilikom poziva metode. Tako se sa steka uklanja druga kopija reference x i ostaje samo originalna referenca. Kada preko nje pristupimo atributu radi, videemo da je on promenio vrednost (slika d).
radi: false x x a) x b)
radi: false
radi: true x x c) x d)
radi: true
Nakon ovog primera moe se zakljuiti sledee: promene nad parametrima metode nainjene u okviru tela metode koji su reference na objekte su vidljive nakon povratka iz metode. Ili, kako se to sree u drugim programskim jezicima, efekat je isti kao kod prenosa parametara po adresi. Sa primitivnim tipovima stvari stoje upravo suprotno: oni se, kao parametri metoda ponaaju kao kod prenosa parametara po vrednosti. Sledi primer:
void test(int a) { a = 1; } ... int a = 0; test(a); // koliko je ovde vrednost a?
Slika 1.5 prikazuje ta se deava u ovom sluaju: deklarie se promenljiva a tipa int i odmah se inicijalizuje na vrednost 0. Lokalne promenljive primitivnog
12
tipa se smetaju na stek (trenutno stanje ilustruje slika a). Zatim se poziva metoda test sa parametrom a; parametar a se smeta na stek (zapravo, njegova vrednost se kopira jo jednom slika b). U okviru metode vrednost parametra se menja u 1, pri emu se menja druga kopija na steku (slika c). Nakon povratka iz metode, parametar se uklanja sa steka i na steku ostaje originalna vrednost promenljive a koja nije menjana (slika d).
Preklapanje metoda (method overloading) je u Javi doputeno. Preklopljene metode su metode koje imaju isto ime, ali se razlikuju po listi parametara. Kompajler ih smatra za sasvim razliite metode, bez obzira to imaju isto ime. Metode ne mogu da se razlikuju samo po tipu rezultata kojeg vraaju. Sledi primer klase sa tri preklopljene metode:
class int int int } A { metoda() { ... } metoda(int i) { ... } metoda(String s) { ... }
Kljuna re final ima drugo znaenje kod metoda: oznaava metode koje se ne mogu redefinisati prilikom nasleivanja date klase. O nasleivanju e vie biti rei u odeljku 1.18, a o redefinsanju metoda u odeljku 1.20. Primer jedne final metode glasi:
final int metoda(int i) { ... }
13
Tada e sledei programski segment izazvati promenu vrednosti atributa i u oba objekta:
StaticTest a = new StaticTest(); StaticTest b = new StaticTest(); a.i++; // ovde je a.i == b.i == 1
Statiki atribut je pridruen klasi, a ne njenim instancama. U tom smislu, moe mu se pristupiti i kada nije kreiran nijedna instanca klase. Tada se atributu pristupa tako to se navodi ime klase, pa zatim ime atributa, kao u sledeem primeru:
StaticTest.i++;
Statike metode su metode koje ne mogu biti pozvane nad objektimainstancama klase, ve nad klasom samom. U tom smislu, nije mogue pisati
a.metoda();
nego samo
StaticTest.metoda();
Statike metode imaju pristup samo statikim atributima klase. esto korieni primer upotrebe statikog atributa je ispisivanje na konzolu:
System.out.println("Hello, world!");
pri emu se poziva metoda println objekta out koji je statiki atribut klase System (out zapravo predstavlja standardni izlaz, slino kao stdout u jeziku C).
1.14 Nizovi
Nizovi se u Javi definiu vrlo slino kao u jeziku C++. Na primer, niz iji su elementi tipa int se definie na sledei nain:
int[] a;
ili
int a[];
Ovim je samo definisana referenca na niz; niz se nakon toga mora kreirati na nain slian kreiranju objekata. Sledi primer gde se alocira niz od pet int elemenata:
a = new int[5];
14
Prvi element niza ima indeks nula. Postoji i nain da se niz definie, alocira memorija za njega i odmah inicijalizuje, kao u sledeem primeru:
int[] a = { 1, 2, 3, 4, 5 };
Treba voditi rauna o tome da se prilikom definicije niza referenca na niz uva na steku, dok se elementi niza uvaju na heap-u, slino kao i objekti. Slika 1.6 ilustruje ovu situaciju za niz definisan u prethodnom primeru.
heap
Kada je u pitanju niz iji su elementi primitivnog tipa, alokacija memorije za elemente niza se odvija automatski. To nije sluaj kada je u pitanju niz iji su elementi objekti neke klase. Na primer, niz od 5 elemenata klase Automobil se definie kao na primer:
Automobil[] parking = new Automobil[5];
Ovim je zapravo definisan niz iji elementi su reference na objekte klase Automobil. Slika 1.7 ilustruje ovu situaciju.
parking
heap
Ovakav niz referenci na objekte se moe inicijalizovati, recimo, u odgovarajuoj for petlji, kao u primeru:
for (int i = 0; i < parking.length; i++) parking[i] = new Automobil();
(Svaki niz ima definisan atribut length koji predstavlja duinu alociranog niza). Sada e stanje u memoriji izgledati kao na slici 1.8.
parking
a1
a2
a3
a4
a5
heap
e kreirati niz od dva elementa koji su reference na nizove od tri elementa tipa int. Stanje u memoriji e nakon kreiranja ovakvog niza izgledati kao na slici 1.9.
4 a 1 2 3
6 heap
pri emu e se izvriti potrebna alokacija memorije, ali ne i inicijalizacija vrednosti elemenata niza. Dvodimenzionalni niz se moe kreirati i postupno, kao u sledeem primeru:
int[][] a = new int[2][]; for (int i = 0; i < a.length; i++) a[i] = new int[3];
Prilikom kreiranja viedimenzionalnog niza iji su elementi objekti, a ne primitivnog tipa, potrebno je jo izvriti i dodatno kreiranje svakog od objekata. Na primer:
Automobil[][] parking = new Automobil[2][]; for (int i = 0; i < parking.length; i++) { parking[i] = new Automobil[3]; for (int j = 0; j < parking[i].length; i++) parking[i][j] = new Automobil(); }
Stanje u memoriji nakon kreiranja ovakvog niza izgledae kao na slici 1.10.
a4 parking a1 a2 a3
a5
a6 heap
Viedimenzionalni niz objekata moe se inicijalizovati odmah prilikom definicije, slino kao kod viedimenzionalnog niza primitivnih tipova. Sledi primer:
Automobil[][] a = { { new Automobil(), new Automobil() }, { new Automobil(), new Automobil() } };
16
Paketi i klase su u okviru fajl-sistema zaista i organizovani kao direktorijumi i datoteke: paketi su predstavljeni direktorijumima, a klase se nalaze u odgovarajuim datotekama. Klasa koja se nalazi u nekom paketu (osim korenskog), mora u okviru svoje datoteke imati odgovarajuu deklaraciju, kao u sledeem primeru:
package paket1; class Automobil { ... }
Deklaracija package se mora nalaziti na samom poetku teksta datoteke, tj. nijedna druga deklaracija se ne sme nalaziti ispred nje. Datoteka Automobil.java mora biti smetena u direktorijum paket1 koji se nalazi u korenskom direktorijumu aplikacije. Naziv korenskog direktorijuma nije vaan, niti je vano gde se on nalazi u okviru fajl-sistema. Prevoenje klase Automobil se mora obaviti komandom:
Vano je primetiti da se komanda za prevoenje poziva iz korenskog direktorijuma projekta i da se kao parametar navodi ime .java datoteke, zajedno sa relativnom putanjom do nje. Analogno tome, klasa Tocak koja se nalazi u paketu paket3 gornjeg primera, prevela bi se komandom:
17
Vidimo da se za separaciju imena paketa u okviru Java programa koristi taka, a ne kosa crta ili obrnuta kosa crta. Ve je reeno da se prilikom pokretanja programa navodi ime one klase koja sadri metodu main. Prilikom navoenja imena ove klase mora se navesti njeno puno ime, ukljuujui i paket u kome se klasa nalazi. Na primer, ukoliko klasa Tocak poseduje metodu main, i elimo da odatle pone izvravanje programa, program moramo pokrenuti pomou sledee komande:
Dakle, ponovo je vano sa kog mesta se poziva Java interpreter: to mora biti korenski direktorijum aplikacije. Svaka prevedena klasa se u okviru aplikacije vidi u okviru paketa ija je putanja jednaka relativnoj putanji do odgovarajueg direktorijuma. Separator naziva paketa je, kao to je ve reeno, taka, a ne kosa crta ili obrnuta kosa crta. Programski jezik Java stie sa velikim brojem klasa grupisanim u pakete. Te klase su dostupne kao i klase koje sami piemo (ak se moe dobiti i njihov izvorni kod). Recimo klasa Vector koja se nalazi u paketu java.util je u programima dostupna kao java.util.Vector. Kako bi svako pominjanje ove klase u tekstu programa zahtevalo navoenje pune putanje do nje (odnosno navoenje odgovarajueg paketa), to bi program uinilo manje itljivim. Zato je mogue na poetku teksta klase deklarisati da se koristi ta-i-ta klasa koja se nalazi u tom-i-tom paketu. Na primer:
package paket1; import java.util.Vector; class Automobil { ... }
Nadalje se u tekstu klase Automobil klasa Vector koristi samo navoenjem njenog imena, bez imena paketa u kome se nalazi. Treba obratiti panju na to da se import deklaracija mora nalaziti izmeu (opcione) package deklaracije i definicije klase. Ukoliko koristimo vie klasa iz istog paketa, moramo svaku od njih navesti u odgovarajuoj import deklaraciji. Drugi nain je da se importuju sve klase iz datog paketa pomou doker-znaka *:
package paket1; import java.util.*; class Automobil { ... }
Ovakav nain importovanja ne obuhvata i sve potpakete importovanog paketa! Niti je dozvoljeno korienje doker znakova kao u primeru:
import java.util.Vec*; // nije dozvoljeno!
Klase koje se nalaze u paketu java.lang nije potrebno importovati. Odgovarajua import deklaracija se podrazumeva.
18
1.16.2 CLASSPATH S obzirom na do sada izloeno, korienje klase Vector iz paketa java.util bi znailo da se odgovarajue stablo direktorijuma java\util\... koje sadri kompajlirane klase mora kopirati unutar strukture direktorijuma svake aplikacije koju piemo. Time se bespotrebno zauzima prostor i komplikuje odravanje softvera. Zato postoji nain da se paketi sa klasama koji se koriste iz vie aplikacija uvaju na jednom mestu, a sve aplikacije e pomou odgovarajueg mehanizma te klase videti kao da je struktura direktorijuma iskopirana u okviru svake aplikacije. U pitanju je mehanizam slian korienju PATH promenljive okruenja (environment variable). Java interpreter za ovu svrhu koristi promenljivu okruenja koja se naziva CLASSPATH. Ona sadri listu direktorijuma u kojima treba traiti klase koje se koriste. Na primer, ukoliko je cela java\... hijerarhija paketa smetena u direktorijum C:\java\lib, vrednost CLASSPATH promenljive bi mogla da glasi:
CLASSPATH=C:\java\lib
ime bi sve klase smetene po svojim paketima unutar direktorijuma C:\java\lib bile vidljive za sve Java aplikacije. Ukoliko CLASSPATH treba da sadri vie direktorijuma, oni se navode jedan za drugim, sa takom-zarez kao separatorom. Na primer:
CLASSPATH=C:\java\lib;D:\mojalib
Na UNIX sistemima sintaksa navoenja vrednosti CLASSPATH promenljive se unekoliko razlikuje, time to se direktorijumi razdvajaju znakom dvotaka. Ukoliko u CLASSPATH dodamo direktorijum D:\temp\korenski paket iz prethodnih primera, na primer komandom:
tada i na program moemo pokrenuti sa bilo kog mesta u okviru fajl sistema, jer e klase biti vidljive preko CLASSPATH-a. Na primer, komanda:
iako
se
ne
nalazimo
direktorijumu
1.16.3 JAR arhive Distribucija biblioteka klasa smetenih u svoje pakete nije preterano elegantna u sluaju veeg broja klasa i paketa, jer se poveava broj datoteka i direktorijuma koje treba instalirati i navesti u CLASSPATH-u. Zbog toga je omogueno arhiviranje biblioteka u tzv. JAR arhive. U pitanju su arhive koje sadre klase u svojim paketima arhivirane u klasinom Zip formatu. Podrazumevana ekstenzija im je .jar (mada moe biti i .zip). Ovakve arhive se mogu generisati alatkom jar koja je sastavni deo JDK paketa, ali mogu i bilo kojim drugim programom koji moe da generie Zip arhive (ekstenziju moemo sami promeniti kasnije). Sve klase iz osnovne Java biblioteke su, prilikom instalacije
19
JDK paketa, smetene u datoteku %JAVA_HOME%\jre\lib\rt.jar, gde je JAVA_HOME direktorijum gde je instaliran JDK paket. Ovu datoteku moemo otvoriti, recimo, programom WinZip, kao na slici 1.12.
Na slici vidimo da se klasa Vector (zapravo, prevedena datoteka Vector.class) u okviru arhive nalazi u direktorijumu java\util, dakle onom koji odgovara paketu u kome se nalazi klasa. Umesto da u okviru CLASSPATH-a navodimo direktorijum u kome se nalazi raspakovan sadraj arhive rt.jar, moemo navesti samu datoteku rt.jar (sa svojom putanjom) i dobiemo isti efekat. Na primer:
CLASSPATH=C:\jdk1.3\jre\lib\rt.jar
Dakle, CLASSPATH moe da sadri nazive direktorijuma i Zip arhiva u kojima se nalaze deljene biblioteke. Korienje direktorijuma u arhiva je u ovom sluaju potpuno ravnopravno. 1.16.4 Podrazumevane komponente u CLASSPATH-u U dosadanjim primerima je naglaavano da se prilikom pokretanja programa moramo nalaziti u korenskom direktorijumu aplikacije. To je, zapravo, posledica injenice da se tekui direktorijum u kome se nalazimo nalazi u CLASSPATH-u kada on nije definisan, kao da je
CLASSPATH=.
gde je taka (.) oznaka za tekui direktorijum. Ako se CLASSPATH promenljiva definie, tekui direktorijum se mora eksplicitno navesti u CLASSPATH-u. Jo jedna komponenta CLASSPATH-a se podrazumeva, a to je upravo biblioteka rt.jar o kojoj je bilo rei u prethodnom odeljku. Nju ne moramo navoditi ak ni kada definiemo promenljivu CLASSPATH. Dakle, svaki CLASSPATH uvek sadri ovu dve komponentu:
CLASSPATH=C:\jdk1.3\jre\lib\rt.jar
Ukoliko CLASSPATH uopte nije definisan, onda on obuhvata i tekui direktorijum, tako da se moe rei da CLASSPATH u tom sluaju glasi:
20
CLASSPATH=.;C:\jdk1.3\jre\lib\rt.jar
Ovde treba obratiti panju da je ovaj podrazumevani skup komponenti CLASSPATH-a uveden tek od Java verzije 1.2. U starijim verzijama CLASSPATH nema podrazumevanih komponenti. Iako se u ovom tekstu u primerima poziva Java kompajlera koristi standardni javac, u praksi se umesto njega esto koristi IBM-ov kompajler jikes, koji je znatno bri. jikes nije deo standardne JDK instalacije i mora se instalirati posebno. Do svoje verzije 1.02 (tekua verzija u ovom trenutku) on se ponaa kao Java 1.1 kompajler, tako da nema podrazumevanih komponenti u CLASSPATH-u. Kako je prilino nezgodno menjati sadraj CLASSPATH promenljive naizmenino za kompajliranje jikes-om i pokretanje java-om, problem se moe prevazii korienjem promenljive okruenja JIKESPATH koju koristi iskljuivo jikes. Ona ima isto znaenje kao CLASSPATH do Java verzije 1.1. Dakle, ako CLASSPATH ima sadraj:
CLASSPATH=.;D:\nekamojabibl.zip
21
viedimenzionalnih nizova. */ public static void main(String[] args) {...} /* Sadrzaj matrice */ double[][] data; /* Dimenzije matrice */ int n, m;
1.18 Nasleivanje
Nasleivanje, kao jedan od osnovnih koncepata objektno-orijentisanog programiranja, postoji i u Javi. Kada jedna klasa nasleuje drugu, potrebno je to naglasiti u okviru teksta klase klazulom extends kao u sledeem primeru, gde klasa BorbeniAvion nasleuje klasu Avion:
class Avion { Krilo levo, desno; void poleti() { ... } void sleti() { ... } } class BorbeniAvion extends Avion { Top top; Bomba[] bombe; void poleti() { ... } void pucaj() { ... } }
Java ne doputa viestruko nasleivanje (onako kako je to definisano recimo u jeziku C++). Dakle klasa moe da nasledi najvie jednu klasu.
programu
protected: atribut ili metoda su vidljivi samo za klase naslednice private: atribut ili metoda su vidljivi samo unutar svoje klase
nespecificiran (tzv. friendly): atribut ili metoda su vidljivi sa klase iz istog paketa
Modifikatori pristupa se navode ispred definicije metode ili atributa. Sledi primer:
class Avion { protected Krilo levo, desno; public void poleti() { ... } public void sleti() { ... } }
22
na konzoli e se ispisati:
metoda1 metoda1 metoda2 metoda2 klase klase klase klase A B A A
(metoda1 je redefinisana u klasi B, tako da je promenjen ispis na konzolu, dok metoda2 nije redefinisana, pa se za klasu B preuzima implementacija metode iz klase A).
metoda metoda2 je proglaena za apstraktnu korienjem kljune rei abstract. Njena implementacija nije navedena. Samim tim klasa je apstraktna pa se i za nju to mora navesti navoenjem kljune rei abstract ispred class. Dakle iskaz poput:
A x = new A();
23
nije doputen.
1.22 Interfejsi
Interfejsi su poseban koncept u Javi: nisu u pitanju klase, ali interfejsi mogu da sadre deklaracije apstraktnih metoda, konstanti i statikih atributa. Sledi primer:
interface Instrument { void sviraj(); void nastimaj(); }
Vidimo da interfejsi podseaju na apstraktne klase. Veza izmeu klasa i interfejsa je sledea: kae se da klasa implementira (a ne nasleuje) interfejs. Klasa moe da implementira vie interfejsa istovremeno, to nije sluaj sa nasleivanjem. Nema prepreke da klasa koja nasleuje drugu klasu implementira i neke interfejse. Jedan interfejs moe da nasledi drugi interfejs. Sledi primer:
class Klarinet implements Instrument { void sviraj() { ... } void nastimaj() { ... } }
Time to je klasa implementirala interfejs zapravo se obavezala da e implementirati sve njegove metode. Kompajler nee dopustiti prevoenje klase koja implementira interfejs a nije redefinisala sve njegove metode.
Klasa Unutrasnja je, u principu, vidljiva samo unutar klase Spoljasnja, mada se to moe promeniti modifikatorima pristupa na uobiajen nain. Instanca unutranje klase se moe kreirati i izvan nje, ali samo preko instance spoljanje klase, kao u sledeem primeru:
Spoljasnja s = new Spoljasnja(); Spoljasnja.Unutrasnja u = s.new Unutrasnja();
Sledei izraz (pokuaj konstrukcije instance inutranje klase bez instance spoljanje klase) nije dozvoljen:
Spoljasnja.Unutrasnja u = new Spoljasnja.Unutrasnja();
Koncept unutranjih klasa se najvie koristi prilikom izgradnje grafikog korisnikog interfejsa, o emu e vie rei biti u poglavlju 3.
24
1.24 Polimorfizam
Polimorfizam je koncept koji omoguava objektima da ispolje razliito ponaanje, zavisno od njihove klase, bez obzira to se oni koriste kao instance nekog zajednikog roditelja. Posmatrajmo sledee tri klase:
abstract class Instrument { abstract void sviraj(); } class Violina extends Instrument { void sviraj() { ... } } class Klarinet extends Instrument { void sviraj() { ... } }
Dakle, primer definie tri klase: klasa Instrument je apstraktna klasa (njena metoda sviraj je apstraktna), a klase Violina i Klarinet nasleuju klasu Instrument i, naravno, implementiraju (zapravo, redefiniu) apstraktnu metodu. Posmatrajmo sada klasu Muzicar:
class Muzicar { void sviraj(Instrument i) { i.sviraj(); } }
Vidimo da klasa Muzicar ima metodu sviraj koja kao parametar ima instancu klase Instrument; sa druge strane, znamo da klasa Instrument ne moe imati instance, jer je apstraktna. Ova metoda e ipak biti upotrebljiva, jer se njoj kao parametar moe proslediti instanca neke klase koja nasleuje klasu Instrument u ovom sluaju instance klasa Violina i Klarinet. Iskaz
Muzicar m = new Muzicar(); m.sviraj(new Klarinet());
e izazvati pozivanje metode sviraj klase Klarinet (iako se to nigde eksplicitno ne navodi u metodi sviraj klase Muzicar). Iskaz
m.sviraj(new Violina());
e izazvati pozivanje metode sviraj klase Violina po istom principu. Dakle, poziv metode sviraj klase Muzicar e imati razliite efekte zavisno od toga koji objekat prosledimo kao parametar. Odreivanje koja metoda e se pozvati se obavlja u toku izvravanja programa. U primeru se vidi da ovo specijalno ponaanje metoda nije niim naglaeno u tekstu programa. Ovakav efekat se u jeziku C++ postizao korienjem tzv. virtuelnih funkcija lanica, a u Javi je ovo podrazumevano (i jedino mogue) ponaanje. Dakle, moemo rei, u terminologiji jezika C++, da su sve metode u Javi virtuelne, pa se ta osobina ne mora naglaavati posebno u programu.
1.25 Izuzeci
Izuzeci su mehanizam za kontrolu toka programa koji se koristi za obradu greaka nastalih u toku izvravanja programa. Segment programskog koda za
25
koji smatramo da moe da izazove izuzetak moemo da smestimo u tzv. try/catch blok, kao u sledeem primeru:
try { // kod koji moe da izazove izuzetak } catch (Exception ex) { System.out.println("Desio se izuzetak: " + ex); }
Izuzetak moe biti, na primer, deljenje nulom, pristup elementu niza koji je izvan granice niza, itd. Ukoliko se prilikom izvravanja koda koji se nalazi u try bloku desi izuzetak, tok izvravanja programa se automatski prebacuje na poetak catch bloka. Nakon izvravanja koda u catch bloku, program dalje nastavlja rad. U okviru catch bloka informacije o samom izuzetku koji se dogodio su dostupne preko objekta klase Exception ili neke njene naslednice. U primeru je to objekat ex. Razliite vrste izuzetaka su predstavljene razliitim exception klasama, na primer: svi izuzeci prilikom izvravanja aritmetikih operacija (deljenje nulom, overflow, itd.) su predstavljene klasom ArithmeticException, pristup elementu iji je indeks izvan granice niza je predstavljen klasom ArrayIndexOutOfBoundsException, itd. Klasa Exception je zajedniki predak svim exception klasama. Jedan try blok moe imati vie sebi pridruenih catch blokova, kao u sledeem primeru:
try { // kod koji moe da izazove // izuzetak } catch (ArithmeticException ex) { System.out.println("Deljenje nulom"); } catch (ArrayIndexOutOfBoundsException ex) { System.out.println("Pristup van granica niza"); } catch (Exception ex) { System.out.println("Svi ostali izuzeci"); } finally { // kod koji se izvrava u svakom sluaju }
Kada se dogodi izuzetak, niz catch blokova se sekvencijalno obilazi i ulazi se u onaj catch blok ija klasa odgovara izuzetku koji se dogodio. Re odgovara u ovom sluaju znai: u pitanju je klasa kojoj exception objekat pripada, ili njen predak. Kada poslednji catch blok hvata izuzetak klase Exception, to znai da e svi izuzeci biti obraeni, jer je klasa Exception zajedniki roditelj. Blok finally se ne mora navesti. On sadri blok koda koji e se izvriti u svakom sluaju, desio se izuzetak ili ne.
26
Kao to postoje odgovarajue klase koje opisuju razliite vrste izuzetaka, mogue je definisati i nove vrste izuzetaka definicijom odgovarajue klase. Na primer, moemo da definiemo novu vrstu izuzetka predstavljenog klasom MojException koja je data u primeru:
public class MojException extends Exception { public MojException() { super(); } public MojException(String msg) { super(msg); } }
Klasa MojException ima dva konstruktora koji pozivaju odgovarajue konstruktore roditeljske klase Exception. Pisanje ovakvih konstruktora nije obavezno, ali je obavezno naslediti klasu Exception (ili nekog njenog potomka). Ovakav korisniki izuzetak moe biti izazvan samo programski, pomou kljune rei throw, kao u sledeem primeru:
if (errorCheck()) throw new MojException("Houston, we have a problem.");
Programski kod koji sadri ovakvu throw naredbu mora biti smeten unutar try bloka koji hvata izuzetak MojException na to e nas naterati kompajler. Dakle, ovo bi moglo da izgleda na sledei nain:
try { if (errorCheck()) throw new MojException("Houston, we have a problem."); } catch (MojException ex) { System.out.println("Exception: " + ex); }
Drugi nain da obradimo nastanak ovakvog izuzetka je da metodu u kojoj se nalazi throw naredba oznaimo kao metodu u kojoj moe da nastane izuzetak date vrste. Na primer:
public void metoda() throws MojException { ... if (errorCheck()) throw new MojException("Houston, we have a problem."); ... }
Sada poziv ovakve metode mora biti u odgovarajuem try bloku, ili metoda u kojoj sadri ovaj poziv mora isto biti oznaena da moe da izazove izuzetak. Dakle:
public void m1() { try { metoda(); } catch (MojException ex) { System.out.println("Exception: " + ex); } }
27
ili:
public void m1() throws MojException { metoda(); }
Na ovaj nain odgovornost za obradu izuzetka moe propagirati sve do metode main od koje poinje izvravanje programa!
Koristi se prilikom poreenja objekata; poreenje tipa (a == b) je zapravo poreenje referenci. Poreenje (a.equals(b)) vraa rezultat zavisno od implementacije metode equals klase kojoj pripada objekat a. Klasa Object definie podrazumevano poreenje objekata koje se svodi na poreenje referenci.
public int hashCode();
Vraa string reprezentaciju objekta. Ukoliko se ne redefinie, poziva se implementacija iz klase Object koja vraa prilino nerazumljiv rezultat. Ova metoda ima donekle specijalan tretman od strane kompajlera, o emu e vie rei biti u sledeem odeljku.
2. Java ne omoguava redefinisanje operatora (kao to je to mogue u jeziku C++). Meutim, operator + moe da se upotrebi za konkatenaciju stringova. Kada naie na izraz poput
"ime" + "prezime"
ili
x + "prezime"
ili x + y
kompajler e generisati kod koji e izvriti konkatenaciju stringova i vratiti rezultat u obliku novokreiranog objekta klase String. Konkatenacija moe da obuhvati i primitivne tipove ili objekte drugih klasa. Na primer, iskaz
int a = 10; String x = "vrednost: " + a;
rezultira kreiranjem novog String objekta iji sadraj je "vrednost: 10". U sluaju konkatenacije stringa sa objektom neke druge klase, kao na primer:
Automobil a = new Automobil(); String x = "Moj auto je: " + a;
bie pozvana metoda toString klase Automobil, pa e se zatim izvriti konkatenacija stringova pomou dobijenog stringa. 3. Metoda koja kao parametar ima tip String, moe u svom pozivu da primi i objekat neke druge klase, pri emu e kompajler automatski generisati kod koji poziva metodu toString() objekta, i zatim taj rezultat prosleuje metodi koja se poziva. Na primer, posmatrajmo metodu
public void handleMessage(String message) { ... }
i njen poziv
handleMessage(new Automobil());
4. Za razliku od svih ostalih parametara, parametri metoda tipa String se ponaaju kao da se prenose po vrednosti a ne po referenci, iako su u pitanju objekti, a ne primitivni tipovi. Na primer, poziv sledee metode
public void handleMessage(String message) { message += "xxx"; }
nee izazvati promenu objekta koji je prosleen kao parametar, jer e se u telu metode, prilikom konkatenacije, generisati novi String objekat koji e biti dodeljen lokalnoj kopiji reference message. Po povratku iz metode unitava se promenjena lokalna kopija reference, i ostaje samo originalna referenca koja i dalje ukazuje na stari String objekat.
29
ove klase dostupne u svakoj Java instalaciji, pa se klase iz biblioteke mogu slobodno koristiti, bez bojazni da ih nee biti na raunaru gde softver instalira, ili ak, u sluaju Jave 1.2, da ih nee biti u CLASSPATH-u. 1.28.1 Klasa java.util.Vector Klasa Vector slui kao kontejnerska klasa koja uva kolekciju objekata kao sekvencu. Elementima sekvence se moe pristupati po indeksu, a elementi se mogu dodavati i uklanjati na proizvoljnom mestu. Klasa je pisana tako da radi sa Object objektima, tako da je u stanju da prihvati sve objekte koje joj prosledimo. Sledi primer u kome se u jednom Vector objektu uva niz String-ova. Za dodavanje elemenata u vektor koristi se metoda addElement, a za pristup objektima koristi se metoda elementAt. Iako ovaj primer demonstrira smetanje objekata iste klase u vektor, to ne mora biti sluaj. Nema prepreke da se u isti vektor smetaju objekti razliitih klasa.
import java.util.Vector; class VectorTest { public static void main(String args[]) { Vector v = new Vector(); v.addElement("Ovo"); v.addElement("je"); v.addElement("probni"); v.addElement("tekst"); for (int i = 0; i < v.size(); i++) System.out.print((String)v.elementAt(i) + " "); }
}
1.28.2 Klasa java.util.Hashtable Klasa Hashtable implementira hash-tabelu koja mapira kljueve na vrednosti (i kljuevi i vrednosti su predstavljeni odgovarajuim objektima). Parovi (klju, vrednost) se u metodu smetaju metodom put. Vrednost se u tabeli trai pomou svog kljua metodom get. Sledei primer predstavlja generisanje 10000 sluajnih celih brojeva u opsegu [0, 19] i brojanje koliko se puta koji broj pojavio pomou hash-tabele. Tabela kao klju koristi generisani broj, a vrednost je objekat klase Counter koji slui kao broja pojava odgovarajueg broja.
class Counter { int i = 1; public String toString() { return Integer.toString(i) + "\n"; } } import java.util.*; class Statistics { public static void main(String args[]) { Hashtable ht = new Hashtable(); for (int i = 0; i < 10000; i++) { Integer r =
30
new Integer((int)(Math.random() * 20)); if (ht.containsKey(r)) ((Counter)ht.get(r)).i++; else ht.put(r, new Counter()); } } } System.out.println(ht);
1.28.3 Klasa java.util.StringTokenizer Klasa StringTokenizer je namenjena za parsiranje stringova oko datih delimitera. Za dati string i delimitere, generiu se tokeni u odgovarajuem redosledu. Metoda hasMoreTokens vraa true ako nije iscrpljena lista tokena dobijenih parsiranjem. Metoda nextToken vraa string koji predstavlja tekui token u parsiranju. Dati primer prikazuje parsiranje teksta oko blanko-znakova i ispisivanje dobijenih tokena na konzolu.
import java.util.*; class TokenizerTest { public static void main(String args[]) { String text = "Ovo je probni tekst"; StringTokenizer st = new StringTokenizer(text, " "); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } } }
tacije koriste se specijalni komentari u izvornom kodu, koji poinju sa /** i zavravaju sa */ (za razliku od klasinih komentara /* ... */). Rezultat rada javadoc alata je skup HTML dokumenata koji opisuju dati program. Specijalni komentar koji se nalazi neposredno ispred definicije klase smatra se opisom klase koji se smeta na odgovarajue mesto u dokumentaciji. Slino tome, specijalni komentar koji se nalazi neposredno ispred definicije metode ili atributa smatra se opisom te metode ili atributa. Unutar specijalnih komentara mogu se koristiti HTML tagovi za formatiranje teksta. Dostupne su takoe i sledee posebne oznake (tzv. doc tags):
@param @return @throws @see
Podaci o autoru.
@author author-info
Opis verzije.
@version version-info
Navodi od koje verzije nekog objekta dati kod moe da radi. Obino se koristi da oznai minimalnu potrebnu verziju JDK paketa.
@since since-info
@deprecated
Oznaava zastarelu mogunost koja moe biti naputena u sledeim verzijama. Kompajler e generisati upozorenje ako se koriste ovakve metode ili atributi.
Primer jedne Java klase koja sadri odgovarajue javadoc komentare dat je u nastavku.
/** Klasa namenjena za rad sa matricama * @author Branko Milosavljevi * @version 1.0 */ public class Matrix { /** Konstruktor * @param n Broj redova matrice * @param m Broj kolona matrice * @throws MathException U sluaju da je neka od * dimenzija manja od 1 */ public Matrix(int n, int m) throws MathException { ... } /** Postavlja sadraj matrice. * @param x Nova vrednost matrice * @throws MathException U sluaju da je neka od
32
* dimenzija manja od 1, ili je vrednost parametra * <code>null</code> */ public void setData(double[][] x) throws MathException { ... } /** Vraa sadraj matrice. * @return Tekua vrednost matrice */ public double[][] getData() { ... } /** Mnoi sadraj matrice objekta koji je pozvan (this) sa * sadrajem matrice b (objekta koji je prosleen kao * parametar). Rezultat mnoenja smeta u novi objekat koga * vraa kao rezultat metode. * @param b Druga matrica u mnoenju * @return Rezultat mnoenja * @throws MathException U sluaju neispravnih dimenzija matrica */ public Matrix multiply(Matrix b) throws MathException { ... } /** Mnoi sadraj dve date matrice i rezultat mnoenja * vraa kao rezultat metode. Obratiti panju da je ovo * statika metoda! * @param a Prvi inilac poizvoda * @param b Drugi inilac proizvoda * @return Rezultat mnoenja * @throws MathException U sluaju neispravnih dimenzija matrica */ public static Matrix multiply(Matrix a, Matrix b) throws MathException { ... } /** Mnoi sadraj matrice objekta koji je pozvan (this) sa * sadrajem matrice b (objekta koji je prosleen kao * parametar). Rezultat mnoenja se smeta u matricu objekta * koji je pozvan. Metoda ne vraa nikakav rezultat! * @param b Drugi inilac proizvoda * @throws MathException U sluaju neispravnih dimenzija matrica */ public void multiply2(Matrix b) throws MathException { ... } /** Vraa string reprezentaciju objekta. * @return String reprezentacija objekta */ public String toString() { ... } /** Sadraj matrice */ private double[][] data; /** Dimenzije matrice */ private int n, m;
33
Zadatak 3. Napisati klasu TestMatrix sa metodom main iz klase Matrix. Metodu main ukloniti iz klase Matrix. Klase Matrix i MathException smestiti u paket vta.math, a klasu TestMatrix u implicitni (korenski) paket. Prevesti i pokrenuti program. Zadatak 4. a) Isprobati pokretanje klase TestMatrix iz nekog drugog direktorijuma; modifikovati sistemsku varijablu CLASSPATH na odgovarajui nain. b) Paket vta.math spakovati u JAR arhivu vta.jar. Arhivu dodati u CLASSPATH i pokrenuti klasu TestMatrix.
34
Poglavlje 2
(U primerima e crvenom bojom biti naglaeni elementi programskog koda na koje se eli skrenuti posebna panja). Kreiranje instance ovakve klase nije dovoljno da bi se nit pokrenula. Za pokretanje niti potrebno je pozvati metodu start. Ne treba pozivati metodu run direktno, jer to nee izazvati potreban efekat. Metoda start (koja se obino ne redefinie) e obaviti potrebnu inicijalizaciju i pozvati metodu run. Dakle, pokretanje nove niti klase MojThread moe da se obavi sledeim segmentom koda:
35
Od ove take nadalje, na program se sastoji iz dve niti: osnovne niti programa koja poinje svoje izvravanje od metode main, i novostvorene niti koja je opisana u metodi run klase MojThread. Slika 2.1 ilustruje ovu situaciju.
osnovna nit
mt
Drugi nain za kreiranje programske niti je implementacija interfejsa Runnable. Ovaj postupak se obino koristi kada nije mogue naslediti klasu Thread (zato to nova klasa ve nasleuje neku klasu, a viestruko nasleivanje nije dozvoljeno). Implementiranje interfejsa Runnable se svodi na implementiranje metode run koja ima istu funkciju kao i u prethodnom sluaju. Na primer, klasa MojaNit u ovom sluaju je definisana tako da implementira interfejs Runnable.
public class MojaNit implements Runnable { public void run() { // programski kod niti je ovde } }
Pokretanje ovakve niti se unekoliko razlikuje od prethodnog sluaja. Evo kako se to radi:
MojaNit mn = new MojaNit (); Thread t = new Thread(mn); t.start();
Ukoliko je programski kod metode run jednak u oba sluaja, obe varijante su funkcionalno ekvivalentne.
public class PrviThread extends Thread { /** Konstruktor * @param threadID Identifikator niti */ public PrviThread(int threadID) { this.threadID = threadID; counter = 10000; } /** Nit: na svaki 1000-ti prolaz petlje ispisi poruku na konzolu. */ public void run() { while (counter > 0) { if (counter % 1000 == 0) System.out.println("Thread[" + threadID + "]: " + (counter/1000)); counter--; } } /** Brojac petlje unutar niti */ private int counter; /** ID niti */ private int threadID; }
Klasa ThreadTest sadri metodu main odakle poinje izvravanje programa. U okviru ove metode kreira se deset novih niti klase PrviThread. Treba primetiti da se u telu petlje u istom redu kreira novi objekat klase PrviThread (sa new PrviThread(i)) i nad tim objektom odmah poziva metoda start. Nigde se ne uva referenca na ovaj objekat, jer ona nije ni potrebna. Nakon izlaska iz petlje ispisuje se poruka da je kreiranje niti zavreno i tu je kraj metode main. Klasa PrviThread ima konstruktor koji prima identifikator niti kao parametar (identifikator smo sami definisali). U konstruktoru se inicijalizuje i vrednost brojake promenljive counter. U okviru metode run izvrava se petlja od 10000 iteracija (pomou brojaa counter) i u svakom hiljaditom prolazu ispisuje se poruka na konzolu. Nakon izlaska iz petlje nit se zavrava.
37
Posmatrajmo poetak jedne mogue varijante izvravanja ovog programa prikazane na slici 2.2.
Na slici vidimo da je prva ispisana poruka zapravo poruka koju ispisuje metoda main kada zavrava sa radom. To znai da je u ovom sluaju, prilikom pokretanja programa, osnovna nit programa stigla da izvri celokupan svoj programski kod pre nego to su druge niti dobile priliku da zauzmu procesor. Samim tim, ovo je ilustracija sluaja gde zavravanje osnovne niti programa ne predstavlja i zavravanje celog programa: postoji jo deset non-daemon niti koje nisu zavrile svoj rad. Ova situacija bi se grafiki mogla predstaviti kao na slici 2.3.
osnovna nit new PrviThread(0).start(); new PrviThread(1).start(); new PrviThread(2).start(); ... new PrviThread(9).start();
x x x x
x
Slika 2.3. Grafika predstava izvravanja programa ThreadTest
38
Poetak synchronized bloka predstavlja zakljuavanje objekta od strane niti. Kraj bloka predstavlja oslobaanje objekta. Kada hronoloki prva nit pokua da ue u synchronized blok, dobie pravo pristupa i zakljuae objekat (acquire lock). Sve dok ta nit ne oslobodi objekat (release lock), druge niti nee moi da mu pristupe. Ako neka druga nit pokua da ue u svoj synchronized blok, bie blokirana u toj taki sve dok prva nit ne oslobodi objekat. Tada e druga nit dobiti pravo pristupa, zakljuati objekat i ui u svoj synchronized blok. Slika 2.4 ilustruje ovu situaciju.
nit A sync {
1) lock 2) locked?
nit B
obj
4) unlock
sync {
}
6) unlock
Drugi nain za implementaciju mehanizma zakljuavanja objekata su tzv. synchronized metode. Synchronized metoda se definie kao u sledeem primeru:
public synchronized void metoda() { ... }
39
Poziv ovakve metode se ponaa kao ulazak u synchronized blok: za vreme izvravanja metode samo nit koja je pozvala metodu ima prava pristupa objektu.
nit B
} }
Ove tri metode mogu biti pozvane samo unutar synchronized bloka i to nad objektom nad kojim se vri sinhronizacija.
prazniti bafer (osim ako nije prazan, tada mora da eka). U naem primeru nit proizvoa je implementirana klasom Producer, a nit potroa klasom Consumer.
public class Consumer extends Thread { public Consumer(Buffer buffer, int count) { this.buffer = buffer; this.count = count; } public void run() { for (int i = 0; i < count; i++) buffer.read(); } private Buffer buffer; private int count; } public class Producer extends Thread { public Producer(Buffer buffer, int count) { this.buffer = buffer; this.count = count; } public void run() { for (int i = 0; i < count; i++) buffer.write((int)Math.round(Math.random() * 100)); } private Buffer buffer; private int count; }
Vidimo da klase Consumer i Producer ne poseduju nikakav programski kod koji vri sinhronizaciju pristupa. Sinhronizacija je obavljena na nivou metoda read i write klase Buffer. Objekti klasa Consumer i Producer preko svojih konstruktora primaju bafer sa kojim e raditi i broj podataka koje treba da proitaju, odnosno upiu u bafer. Klasa Buffer sadri sve to je potrebno za sinhronizaciju niti. Sledi programski kod klase Buffer.
/** Implementacija krunog bafera */ public class Buffer { /** Konstruktor * @param size Veliina krunog bafera */ public Buffer(int size) { this.size = size; data = new int[size]; readPos = 0; writePos = 0;
41
} /** Upisuje novu vrednost u bafer. * @param value Nova vrednost koja se upisuje */ public synchronized void write(int value) { if (isFull()) { System.out.println("Waiting to write..."); try { wait(); } catch (Exception ex) { ex.printStackTrace(); } } data[writePos] = value; if (++writePos == size) writePos = 0; notify(); System.out.println("Written: "+value); } /** ita narednu vrednost iz bafera. * @return Proitana vrednost */ public synchronized int read() { if (isEmpty()) { System.out.println("Waiting to read..."); try { wait(); } catch (Exception ex) { ex.printStackTrace(); } } int retVal = data[readPos]; if (++readPos == size) readPos = 0; System.out.println("Read: "+retVal); notify(); return retVal; } /** Ispituje da li je bafer prazan. * @return Vraa <code>true</code> ako je bafer prazan */ public synchronized boolean isEmpty() { return readPos == writePos; } /** Ispituje da li je bafer pun. * @return Vraca <code>true</code> ako je bafer pun */ public synchronized boolean isFull() { return readPos == (writePos + 1) % size; } /** Veliina krunog bafera */ private int size; /** Sadraj krunog bafera */ private int[] data; /** Naredna lokacija za itanje */ private int readPos; /** Naredna lokacija za pisanje */
42
Konstruktor klase Buffer prima kao parametar veliinu bafera. U konstruktoru se alocira memorija za bafer, i inicijalizuju se indeksi lokacije za itanje iz bafera i lokacije za pisanje u bafer. Metoda isEmpty slui za testiranje da li je bafer prazan, a metoda isFull za testiranje da li je bafer pun. Smatra se da je bafer prazan ako su indeksi pozicija lokacija za itanje i pisanje jednaki. Bafer je pun ako je indeks pozicije za itanje za jedan manji od lokacije za pisanje ili je indeks lokacije za itanje jednak nuli, a indeks lokacije za pisanje jednak indeksu poslednjeg elementa niza koga koristi bafer. Slika 2.6 ilustruje ove situacije.
a)
writePos readPos
writePos readPos
b)
writePos
readPos
readPos
writePos
c)
readPos
writePos
Slika 2.6. a) Sluajevi kada je bafer prazan; b) sluajevi kada je bafer pun; c) sluajevi kada bafer nije ni prazan ni pun
Metoda read je namenjena za itanje podataka i njihovo uklanjanje iz bafera. Metoda je definisana kao synchronized, tako da obezbeuje ekskluzivno pravo pristupa baferu onoj niti koja je pozove. U okviru metode, prvo se proveri da li je bafer prazan; ako nije, prvi podatak koji je na redu za itanje se uklanja iz bafera i vraa se kao rezultat metode. Ako je bafer prazan, poziva se wait metoda, ime se izvravanje ove niti suspenduje sve dok proizvoa nit ne upie novi podatak u bafer; tada e se, u okviru metode write, pozvati metoda notify, ime e se potroa nit ponovo aktivirati. Metoda write je namenjena za pisanje podataka u bafer. Takoe je definisana kao synchronized. Njeno funkcionisanje je simetrino metodi read. U okviru metode prvo se proverava da li je bafer pun; ako nije, novi podatak se upisuje u bafer. Ako je bafer pun, poziva se metoda wait, ime se nit suspenduje sve dok potroa nit ne proita podatak iz bafera, ime e se proizvoa nit ponovo aktivirati. Klasa Test je namenjena za pokretanje programa. Sadri samo metodu main u okviru koje se kreira bafer, proizvoa nit, potroa nit i niti se pokrenu.
public class Test { public static final int BUFFER_SIZE = 100;
43
public static final int PRODUCE_COUNT = 100; public static void main(String[] args) { Buffer buffer = new Buffer(BUFFER_SIZE); Producer p = new Producer(buffer, PRODUCE_COUNT); Consumer c = new Consumer(buffer, PRODUCE_COUNT); p.start(); c.start(); } }
44
Poglavlje 3
45
tivnom sistemu). Kasnije su se pojavili look-and-feel dodaci sa Macintosh izgledom, itd. Promena izgleda aplikacije moe da se obavi ak i za vreme izvravanja programa. Iako se Swing biblioteka moe koristiti i sa Java verzijom 1.1 (uz dodavanje biblioteke u CLASSPATH), sve mogunosti biblioteke su dostupne tek od verzije 1.2. Od verzije 1.2 ova biblioteka je proglaena za standard za razvoj korisnikog interfejsa u Java aplikacijama, dok je AWT zadran zbog kompatibilnosti sa starijim programima. Swing je postao sastavni deo vee biblioteke nazvane Java Foundation Classes (JFC). U ovom praktikumu bie rei iskljuivo o Swing komponentama.
Sledi primer jedne elementarne GUI aplikacije koja ima main metodu i otvara prozor.
public class MyApp { public static void main(String[] args) { MainFrame mf = new MainFrame(); mf.setVisible(true); } } import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setSize(300, 200); setTitle("My First GUI App"); } }
Aplikacija se sastoji iz dve klase: klasa MyApp samo sadri main metodu. U okviru main metode kreira se objekat klase MainFrame (to predstavlja inicijalizaciju glavnog prozora aplikacije) i zatim se taj prozor prikae na ekranu (poziv metode setVisible). Klasa MainFrame nasleuje klasu JFrame, to je standardan nain za definisanje novih prozora. Klasa JFrame je deo Swing biblioteke smetene u paket javax.swing. Komponente Swing korisnikog interfejsa po pravilu poinju velikim slovom J. U okviru konstruktora klase MainFrame se postavlja veliina prozora u pikselima (setSize) i naslov prozora (setTitle).
47
import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setSize(300, 200); setTitle("My Second GUI App"); // dodajemo komponente na formu: getContentPane().add(bOK, BorderLayout.NORTH); getContentPane().add(bCancel, BorderLayout.SOUTH); } // elementi na formi su najee privatni atributi klase private JButton bOK = new JButton("OK"); private JButton bCancel = new JButton("Cancel"); }
Prozoru iz prethodnog primera dodata su dva dugmeta, predstavljena klasom JButton. Ovakve komponente korisnikog interfejsa najee se definiu kao atributi prozorske klase, u ovom sluaju MainFrame. U konstruktoru je dodato postavljanje komponenti na prozor pozivi metode add. Jedan od osnovnih principa Swing biblioteke je da se komponente mogu smestiti samo unutar nekog kontejnera objekta koji je namenjen za prihvat komponenti. Svaki prozor ve ima svoj kontejner, koga moemo dobiti pozivom metode getContentPane. Slika 3.1 prikazuje izgled ove aplikacije. Klasa MyApp je preuzeta iz prethodnog primera.
WEST
CENTER
SOUTH
48
EAST
Razliiti layout manager-i e iste komponente rasporeivati na razliit nain, zavisno od sopstvenog algoritma rasporeivanja. Sledei primer prikazuje upotrebu raznih layout manager-a u istom prozoru.
import java.awt.*; import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setSize(300, 200); setTitle("My Second GUI App"); // biramo layout manager: getContentPane().setLayout(new FlowLayout()); //getContentPane().setLayout(new BorderLayout()); //getContentPane().setLayout(new GridLayout(3, 3)); getContentPane().add(bOK, BorderLayout.NORTH); getContentPane().add(bCancel, BorderLayout.SOUTH); } private JButton bOK = new JButton("OK"); private JButton bCancel = new JButton("Cancel"); }
Kada se ovaj primer pokrene sa korienjem FlowLayout manager-a dobie se prozor ije su varijante prikazane na slici 3.3.
a)
b)
Sa slike 3.3 se vidi da FlowLayout rasporeuje komponente sleva na desno, dajui im neku podrazumevanu veliinu. Kada se prvi red sa komponentama popuni (to zavisi od irine prozora i broja i oblika komponenti) prelazi se u sledei red i tako dalje. Layout manager mehanizam se koristi kako u Swing, tako i u AWT biblioteci. Odgovarajue xxxLayout klase zato se nalaze u starijem paketu java.awt (odatle prva import deklaracija). Komponenta JPanel je zanimljiva po tome to ona predstavlja i komponentu i kontejner istovremeno. Korienjem ove komponente mogu se dobiti sloeni rasporedi komponenti na formi. Zanimljivo je, meutim, da osnovni skup layout manager-a ne obuhvata nijedan koji omoguava postavljanje komponenti na prozor na nain kako su to navikli Windows programeri postavljanjem komponente na tano odreeno mesto na
49
prozoru, odreeno koordinatama datim u pikselima. Kompajler Borland JBuilder ima u svojoj biblioteci upravo takav layout manager, zvani XYLayout. Sledi primer njegove upotrebe u klasi MainFrame:
import import import public java.awt.*; javax.swing.*; com.borland.jbcl.layout.*; class MainFrame extends JFrame {
public MainFrame() { setSize(300, 200); setTitle("My Second GUI App"); // biramo layout manager: getContentPane().setLayout(new XYLayout()); // dodajemo komponente na formu: getContentPane().add(bOK, new XYConstraints(10, 10, 100, -1)); getContentPane().add(bCancel, new XYConstraints(10, 50, 100, -1)); } private JButton bOK = new JButton("OK"); private JButton bCancel = new JButton("Cancel"); }
Slika 3.4 predstavlja izgled ovakve aplikacije u dve varijante veliine prozora.
a)
b)
Interfejs ActionListener definie jednu metodu, actionPerformed. Njen parametar je objekat klase ActionEvent koji blie opisuje dogaaj. U prikazanom primeru oslukiva e, kada se dogaaj dogodi, zatvoriti aplikaciju (metoda exit). Listener mehanizam se koristi kako u Swing, tako i u AWT biblioteci tako da su svi dogaaji definisani u starijim paketima java.awt i java.awt.event. Oslukiva dogaaja (instanca neke Listener klase) se pridruuje onoj komponenti korisnikog interfejsa za koju elimo da reaguje na taj dogaaj. To pridruivanje se obavlja metodom addxxxListener koju ima komponenta. Sledi primer MainFrame klase iz prethodnih primera koja je modifikovana tako to je dugmadima dodat prethodno prikazani oslukiva.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setSize(300, 200); setTitle("My Second GUI App"); getContentPane().setLayout(new FlowLayout()); getContentPane().add(bOK); getContentPane().add(bCancel); // dodajemo reakcije na dogadjaje dugmadima bOK.addActionListener(new MyListener()); bCancel.addActionListener(new MyListener()); } private JButton bOK = new JButton("OK"); private JButton bCancel = new JButton("Cancel"); }
Ovakav prozor sadri dva dugmeta koja e, na klik miem, reagovati na isti nain koriste isti Listener zatvaranjem aplikacije. (Ovo morate probati praktino, slika ne pomae puno). 3.7.2 Oslukivai kao unutranje klase Prozori esto znaju biti pretrpani komponentama koje, sa svoje strane, obrauju vie vrsta dogaaja. Rezultat moe biti jedna prozorska klasa koja sadri par desetina komponenti, i nekoliko desetina Listener klasa. Definisati pedesetak Listener klasa samo za jedan prozor moe uiniti program nepreglednim. Zato se Listener klase najee definiu kao unutranje klase u okviru prozorske klase. Posmatrajmo sledei segment programskog koda:
ActionListener a = new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } });
U pitanju je definicija reference a na objekat klase koja implementira ActionListener interfejs. Ime klase nigde nije navedeno! Samim tim, ne moemo
51
konstruisati jo jedan objekat ove klase. Sve to nam je iz ove komplikovane konstrukcije potrebno je referenca na objekat, koju moemo iskoristiti na sledei nain:
bCancel.addActionListener(a);
Time smo definisali Listener klasu i njenu instancu (oslukiva) pridruili dugmetu bCancel. Ove dve operacije (kreiranje oslukivaa i njegovo pridruivanje dugmetu) se mogu obaviti jednim iskazom:
bCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } });
Upravo je ovaj poslednji nain i najee korieni nain za definisanje oslukivaa (ovakve iskaze koriste i alati poput Borland JBuilder-a kada generiu kod). Njegova komplikovana sintaksa trai malo navikavanja, ali je kudikamo preglednija od desetina odvojenih datoteka u kojima se nalaze definicije oslukivaa onako kako je to u prvom primeru prikazano. Sledei primer predstavlja prozor sa dva dugmeta, od kojih dugme Cancel zatvara aplikaciju, a klik na dugme OK menja boju samog dugmeta sluajnim izborom.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setSize(300, 200); setTitle("My Second GUI App"); getContentPane().setLayout(new FlowLayout()); getContentPane().add(bOK); getContentPane().add(bCancel); // dodajemo reakcije na dogadjaje dugmadima bOK.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { int r = (int)Math.round(Math.random()*256); int g = (int)Math.round(Math.random()*256); int b = (int)Math.round(Math.random()*256); bOK.setBackground(new Color(r, g, b)); } }); bCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } }); } private JButton bOK = new JButton("OK"); private JButton bCancel = new JButton("Cancel"); }
52
Na isti nain na koji je dugme u prethodnim primerima reagovalo na klik miem, moe se dodati oslukiva neke druge vrste dogaaja nekoj komponenti. Tabela 3.2 prikazuje najee koriene dogaaje i odgovarajue Listener-e.
Tip dogaaja ActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent KeyEvent MouseEvent WindowEvent ItemEvent TextEvent Odgovarajui listener ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener KeyListener MouseListener WindowListener ItemListener TextListener
Tabela 3.2. Tipovi dogaaja i odgovarajui listener-i
U narednih nekoliko primera bie ilustrovana upotreba razliitih komponenti korisnikog interfejsa i reakcije na dogaaje vezane za te komponente. Prvi primer ilustruje reagovanje na pritisnut taster (KeyEvent) u tekstualnom polju za unos (JTextField). Komponenta za unos teksta tf ima svog oslukivaa dogaaja, objekat klase Reakcija. Klasa Reakcija ne implementira interfejs KeyListener, to bismo oekivali, ve nasleuje klasu KeyAdapter. Radi se o tome da interfejs KeyListener definie vie metoda za obradu dogaaja (keyPressed,
53
keyReleased, itd). Kako elimo da reagujemo samo na jednu vrstu dogaaja otputen taster, tj. keyReleased, elimo da redefiniemo samo tu metodu. Poto implementiranje interfejsa obavezuje implementiranje svih njegovih metoda, morali bismo da napiemo i nekoliko metoda sa praznim telom, da bismo zadovoljili formu. Klasa KeyAdapter nam reava taj problem, jer ona implementira interfejs KeyAdapter na takav nain da su joj sve metode prazne. Tako emo nasleivanjem klase KeyAdapter i redefinisanjem samo onih metoda koje su nam potrebne utedeti neto kodiranja i doprineti preglednosti programskog koda. Prilikom otputanja pritisnutog tastera dok je fokus na tf komponenti, poziva se oslukiva klase Reakcija. Ukoliko je pritisnut taster A, tekst labele l se menja u Pritisnuo taster a, a inae se menja u Tekst.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JTextFieldTest extends JFrame { public JTextFieldTest() { setSize(400, 200); setTitle("Component test"); getContentPane().setLayout(new FlowLayout()); getContentPane().add(l); getContentPane().add(tf); tf.addKeyListener(new Reakcija()); } /** Rukovalac dogadjajima definisan kao inner klasa */ class Reakcija extends KeyAdapter { public void keyReleased(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_A) l.setText("Pritisnuo taster a"); else l.setText("Tekst"); } } JTextField tf = new JTextField(30); JLabel l = new JLabel("Tekst"); }
Sledei primer predstavlja mogunost obrade dogaaja pomeranja kursora unutar tekstualnog polja. Kursor se moe pomeriti kucanjem teksta, brisanjem teksta, strelicama za kretanje po tekstu, itd. Prilikom pomeranja kursora (CaretEvent) u gornjem JTextArea polju, novi poloaj kursora se upisuje kao sadraj donjeg JTextArea polja. Za oslukivanje ovakvog dogaaja potrebno je implementirati CaretListener interfejs. Metoda koja se poziva prilikom pomeranja kursora je caretUpdate. CaretEvent klasa koja predstavlja dogaaj ima metodu getDot koja vraa novi poloaj kursora.
import java.awt.*; import javax.swing.event.*; import javax.swing.*;
54
public class JTextAreaTest extends JFrame { public JTextAreaTest() { setSize(400, 300); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); ta1 = new JTextArea("Tekst1", 5, 30); ta1.addCaretListener(new Reakcija()); cp.add(ta1); ta2 = new JTextArea("Tekst2", 5, 30); cp.add(ta2); } class Reakcija implements CaretListener { public void caretUpdate(CaretEvent e) { ta2.setText("" + e.getDot()); } } JTextArea ta1; JTextArea ta2; }
Naredni primer demonstrira reagovanje na dogaaj izbora stavke (ItemEvent) u check box polju (JCheckBox) i radio button polju (JRadioButton). Interfejs koji treba implementirati je ItemListener, metoda koja se poziva prilikom dogaaja je itemStateChanged, a metoda getItem klase ItemEvent vraa referencu na onaj objekat tj. komponentu kojoj je stavka izabrana. Prilikom izbora stavke labeli l se menja tekst u Odabrao stavku: + <tekst komponente na koju je kliknuto>. Da bi dva radio button-a radila u paru, tj. da bi selekcija jednog izazvala deselekciju drugog, potrebno je staviti ih u istu grupu, tj. ButtonGroup objekat. Metoda add klase ButtonGroup slui za dodavanje komponenti u grupu.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JCheckBoxTest extends JFrame { public JCheckBoxTest() { setSize(500, 200); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); l = new JLabel("Tekst"); cp.add(cb1); group.add(rb1); // dodajemo radio button-e u grupu group.add(rb2); // kako bi radili u paru cp.add(rb1); cp.add(rb2); cb1.addItemListener(new Reakcija()); Reakcija r = new Reakcija(); rb1.addItemListener(r); rb2.addItemListener(r);
55
cp.add(l);
class Reakcija implements ItemListener { public void itemStateChanged(ItemEvent e) { l.setText("Odabrao stavku: " + ((AbstractButton)e.getItem()).getText()); } } JCheckBox cb1 = new JCheckBox("CheckBox1"); ButtonGroup group = new ButtonGroup(); JRadioButton rb1 = new JRadioButton("RadioButton1", true); JRadioButton rb2 = new JRadioButton("RadioButton2", false); JLabel l; }
Poslednji primer u ovom odeljku ilustruje reagovanje na dogaaj izbora stavke (ItemEvent) u combo box-u (JComboBox). Primer je slian prethodnom, sa malom razlikom to se u metodi itemStateChanged koristi metoda getSource klase ItemEvent koja vraa objekat koji je izvor dogaaja. Proverava se da li je taj objekat instanca klase JComboBox, i ako jeste tekst labele l se postavlja na izabrani tekst u combo box-u. Stavka combo box-a koja je izabrana se moe dobiti pozivom metode getSelectedItem klase JComboBox.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JComboBoxTest extends JFrame { public JComboBoxTest() { setSize(500, 200); // napuni combo box stavkama for (int i = 0; i < items.length; i++) c.addItem(items[i]); c.addItemListener(new Reakcija()); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(c); cp.add(l); } class Reakcija implements ItemListener { public void itemStateChanged(ItemEvent e) { if (e.getSource() instanceof JComboBox) l.setText((String)c.getSelectedItem()); } } String[] items = {"Prva opcija", "Druga opcija", "Treca opcija"}; JComboBox c = new JComboBox(); JLabel l = new JLabel("Labela");
56
Ispitivanje da li je izvor dogaaja instanca klase JComboBox u prethodnom primeru je ilustracija kako se jedan oslukiva dogaaja moe upotrebiti za obradu dogaaja koji potiu od razliitih komponenti korisnikog interfejsa.
3.9 Apleti
3.9.1 Pojam apleta Apleti su posebna vrsta Java programa koji su namenjeni za ugraivanje u HTML stranice. U okviru stranice aplet dobija na raspolaganje odreenu pravougaonu povrinu ije su dimenzije date u pikselima. U tom smislu, aplet se u HTML stranicu ugrauje na slian nain kao i slika. Kada Web ita pristupi stranici koja sadri aplet, automatski e preuzeti i programski kod apleta (prevedene Java klase), pokrenuti Java virtuelnu mainu i poeti izvravanje apleta. Aplet je Java program koji na raspolaganju ima gotovo sve mogunosti klasinih Java aplikacija, izuzev dva bitna ogranienja, uvedena iz bezbednosnih razloga: apleti ne mogu da pristupe fajl-sistemu raunara na kome se izvravaju; apleti ne mogu da uspostave mrenu konekciju sa bilo kojim raunarom osim sa Web serverom sa koga su preuzeti.
Aplet je zapravo klasa koja nasleuje klasu Applet (za AWT) ili JApplet (za Swing). Pisanje apleta svodi se na nasleivanje klase JApplet i implementiranje odgovarajuih metoda. Neke od najvanijih metoda pobrojane su ovde: init: poziva je Web ita prilikom uitavanja apleta u JVM Web itaa destroy: poziva je Web ita prilikom uklanjanja apleta iz JVM Web itaa; obino se koristi za oslobaanje zauzetih resursa (npr. zaustavljanje niti ili zatvaranje mrene konekcije) start: poziva je Web ita kada hoe da naznai da aplet treba da pone sa svojim izvravanjem stop: analogno prethodnom, poziva je Web ita kada aplet treba da prekine sa svojim izvravanjem paint: poziva je Web ita kada je potrebno da aplet iscrta svoj sadraj
import java.awt.*; import javax.swing.*; public class AppletTest extends JApplet { public void init() { getContentPane().add(new JLabel("Applet!")); } }
57
Aplet klasa se zove AppletTest, nasleuje klasu JApplet i redefinie metodu init. U okviru init metode vri se inicijalizacija apleta: u ovom sluaju to se svodi na postavljanje jedne labele na povrinu apleta. Aplet nema metodu main, tako da ga ne moemo pokrenuti na do sada poznat nain iz komandne linije. Potrebno je ovakav aplet ugraditi u HTML stranicu u okviru koje e biti prikazan. Sledi primer ovakve HTML stranice.
<html> <head> <title>Test stranica sa apletom</title> </head> <body> <applet code = "AppletTest" width = 100 height = 50> </applet> </body> </html>
Ugraivanje apleta u stranicu se postie tagom applet. Njegovi atributi su code (naziv aplet klase), width (irina apleta u pikselima) i height (visina apleta u pikselima). Ovakva stranica, kada se otvori u Web itau, izgleda kao na slici 3.5.
3.9.2 Web itai i Java Plug-In Kada Web ita prilikom analize HTML stranice koju je preuzeo naie na applet tag, vri inicijalizaciju svoje Java virtuelne maine, preuzima aplet i pokree ga. To znai da se aplet izvrava u okviru JVM koja pripada Web itau. Dananji Web itai veinom imaju svoje JVM, ali je njihova kompatibilnost sa standardnom JVM problematina. Naime, Internet Explorer (u verzijama do 5.5) i Netscape Navigator (u verzijama do 4.7) poseduju virtuelne maine koje odgovaraju Java verziji 1.1, i ak nemaju sve mogunosti koje verzija 1.1 definie. Najuoljiviji nedostatak je izostanak podrke za Swing biblioteku. Da bi apleti koji koriste Swing bili upotrebljivi u okviru ovih Web itaa, Sun je izdao Java 1.2 Plug-In za te itae. Radi se o klasinim plug-in dodacima za Netscape Navigator i Internet Explorer, slino kao to se koristi Macromedia Flash plug-in. Namena tog plug-ina je da zameni osnovnu virtuelnu mainu Web itaa svojom, koja je u potpunosti kompatibilna sa Java verzijom 1.2.
58
Instalacija ovog plug-ina se odvija automatski prilikom instaliranja paketa JDK ili JRE (Java Runtime Environment, samo Java interpeter, bez razvojnih alata). Sama instalacija nije dovoljna da bi se plug-in zaista i koristio. Naime, kada Web ita, analizirajui HTML stranicu, naie na applet tag, on e ponovo pokrenuti svoju JVM umesto nove koju je instalirao plug-in. Ugraivanje plugina u stranicu se razlikuje od ugraivanja apleta, po tome to se koristi object tag (za Internet Explorer), odnosno embed tag (za Netscape Navigator). Sledei primer prikazuje HTML stranicu koja sadri aplet iz prethodnog primera, samo to se umesto podrazumevane JVM Web itaa koristi instalirani Java 1.2 plug-in.
<html> <head> <title>Applet1</title> </head> <body> <OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" width="100" height="50" align="baseline" codebase="http://java.sun.com/products/plugin/1.2.2/ jinstall-1_2_2-win.cab#Version=1,2,2,0"> <PARAM NAME="code" VALUE="AppletTest.class"> <PARAM NAME="codebase" VALUE="."> <PARAM NAME="type" VALUE="application/x-java-applet;version=1.2.2"> <COMMENT> <EMBED type="application/x-java-applet;version=1.2.2" width="100" height="50" align="baseline" code="AppletTest.class" codebase="." pluginspage="http://java.sun.com/ products/plugin/1.2/plugin-install.html"> <NOEMBED> No Java 2 support for APPLET!! </NOEMBED> </EMBED> </COMMENT> </OBJECT> </body> </html>
Iako je u pitanju prilino komplikovana HTML konstrukcija, ona se ponaa kao applet tag. Crvenom bojom su naznaeni elementi koji su promenljivi to su naziv aplet klase, irina i visina apleta. Sve ostale parametre ne treba menjati. Ostali parametri, izmeu ostalog, specificiraju Web itau kako automatski instalirati plug-in ako on nije ve instaliran, i navode adesu na JavaSoft Web sajtu gde se plug-in moe nai. Dokle god savremene verzije itaa ne budu posedovale svoje JVM u verziji 1.2, primorani smo da koristimo Java 1.2 plug-in na ovakav nain. Svi naredni primeri apleta koriste ovaj plug-in u svojim HTML stranicama. 3.9.3 Apleti i komponente korisnikog interfejsa Naredni primer prikazuje aplet koji na svojoj povrini sadri jedno dugme.
import java.awt.*;
59
import javax.swing.*; public class JButtonTest extends JApplet { JButton b; public void init() { b = new JButton("Pritisni me"); Container cp = getContentPane(); cp.add(b); } }
Iz primera vidimo da se postavljanje komponenti na povrinu apleta ni po emu ne razlikuje od postavljanja komponenti na prozor klasine aplikacije. Za rasporeivanje komponenti koristi se mehanizam layout manager-a. Sledei primer predstavlja proirenje prethodnog primera, u smislu da je dugmetu dodat oslukiva dogaaja za klik miem (ActionEvent). Aplet sadri i jednu labelu iji se tekst menja kada se klikne na dugme.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JButtonTest2 extends JApplet { JButton b; JLabel l; public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); l = new JLabel("Tekst u labeli"); cp.add(l); b = new JButton("Pritisni me"); b.addActionListener(new Reakcija()); cp.add(b); } class Reakcija implements ActionListener { public void actionPerformed(ActionEvent e) { l.setText("Pritisnuo dugme"); } } }
60
public void init() { getContentPane().add(new JLabel("Aplet i aplikacija!")); } static class WL extends WindowAdapter { AppletApplicationTest a; public WL(AppletApplicationTest a) { super(); this.a = a; } public void windowClosing(WindowEvent e) { a.stop(); a.destroy(); System.exit(0); } } public static void main(String args[]) { JFrame f = new JFrame("AppletApplicationTest"); AppletApplicationTest a = new AppletApplicationTest(); f.addWindowListener(new WL(a)); a.init(); a.start(); f.getContentPane().add(a, BorderLayout.CENTER); f.setSize(300, 200); f.setVisible(true); }
Posmatrajmo metodu main ove aplikacije. Vidimo da se u njoj kreira novi prozor klase JFrame, a zatim se kreira i aplet objekat. Prozoru se doda oslukiva dogaaja za zatvaranje prozora klikom na dugme close ( ). Dalje, poziva se metoda init apleta, a zatim i metoda start. Na ovaj nain aplikacija simulira Web ita prilikom pokretanja apleta. Aplet se postavlja na sredinu prozora, postave se dimenzije prozora i on se prikae. Sutina se ovde nalazi u inicijalizaciji apleta (metode init i start) i njegovom postavljanju na prozor aplikacije. Na taj nain aplet e biti prikazan u osnovnom prozoru aplikacije i imaemo utisak da je u pitanju klasina GUI aplikacija. Klikom na dugme close zatvara se aplikacija. Pri tome, potrebno je ponovo simulirati Web ita: pozivaju se metode stop i destroy naeg apleta. Dakle, moemo da zakljuimo da je omoguavanje apletu da se koristi i kao aplikacija vrlo jednostavno: svodi se na dodavanje metode main i jednog oslukivaa dogaaja. Sadraj metode main i obrada dogaaja se praktino i ne menjaju, tako da se ovaj segment koda moe kopirati u sve sline programe.
metode. Ako nijedna konkretna komponenta nije adekvatna za ovakvo nasleivanje, moe se koristiti generika komponenta JComponent. Sledi primer jedne korisniki definisane komponente koja predstavlja labelu uokvirenu tankom linijom.
import java.awt.*; import javax.swing.*; public class UserDefined extends JComponent { public UserDefined(String text) { this.text = text; } /** Iscrtava komponentu */ public void paint(Graphics g) { Dimension s = getSize(); g.setColor(Color.black); g.drawRect(0, 0, s.width - 1, s.height - 1); g.drawString(text, 0, 10); } /** Vraa poeljnu veliinu komponente */ public Dimension getPreferredSize() { int width = 70, height = 20; Graphics g = getGraphics(); FontMetrics fm = null; if (g != null) fm = g.getFontMetrics(); if (fm != null) { width = fm.stringWidth(text); height = fm.getHeight(); } return new Dimension(width, height); } /** Tekst koji se ispisuje */ private String text; }
U primeru se vidi da je nasleena klasa JComponent. Redefinisane su dve njene metode, paint i getPreferredSize. Metoda paint se poziva kad god je potrebno da se komponenta iscrta (prilikom pomeranja prozora, preklapanja prozora, itd). Nju poziva Swing okruenje. Samo crtanje se obavlja koristei objekat klase Graphics, koji ima veliki broj metoda za crtanje. U okviru ove metode postavlja se crna boja kao tekua boja za crtanje, iscrtava se pravougaonik oko ivica komponente, i unutar njega se ispisuje dati tekst. Metoda getPreferredSize vraa dimenzije koje bi komponenta volela da ima. Ovu metodu e pozivati layout manager prilikom rasporeivanja komponenti na prozor. Izraunavanje ove poeljne veliine komponente se vri na osnovu dimenzija koje e zauzimati tekst kada se ispie. Ovako definisana komponenta koristi se u aplikacijama i apletima na isti nain kao i komponente iz biblioteke. Sledi primer apleta koji koristi prethodno
62
3.12 JavaBeans
JavaBeans je standard za kreiranje softverskih komponenti koje imaju svoje osobine i ponaanje i koje se mogu koristiti u okviru RAD (Rapid Application Development) alata kao to su Borland JBuilder, Symantec Visual Caf, itd. Svaka JavaBean komponenta ima svoja svojstva (properties) i reaguje na neke dogaaje (events). Formalno posmatrano, JavaBean je svaka Java klasa za koju vai: 1. Ima podrazumevani konstruktor (konstruktor bez parametara koji je public). 2. Za svako svojstvo koji se zove xxx moraju da postoje public metode setXxx i getXxx (obratite panju na odnos velikih i malih slova!). Atribut klase koji bi sadrao vrednost tog svojstva nije obavezan! 3. Za svaki dogaaj predstavljen klasom xxxEvent na koji komponenta moe da reaguje, moraju da postoje metode addxxxListener(XxxListener) i removeXxxListener(XxxListener).
63
Modifikujmo sada komponentu UserDefined prikazanu u prethodnom primeru tako da postane JavaBean. Umesto konstruktora koji prima parametre, ovde je sada podrazumevani konstruktor. Tekst koji se ispisuje u okviru komponente opisaemo property-jem text. To znai da klasa mora posedovati metode setText i getText. Kako naa komponenta ve nasleuje JComponent, nasleuje vie addXxxListener/removeXxxListener metoda. Meu njima su i addMouseListener i removeXxxListener koje omoguavaju reagovanje na dogaaje izazvane miem.
import import import public java.awt.*; java.awt.event.*; javax.swing.*; class UserDefinedBean extends JComponent {
public UserDefinedBean() { } public void setText(String s) { text = s; } public String getText() { return text; } public void paint(Graphics g) { Dimension s = getSize(); g.setColor(Color.black); g.drawRect(0, 0, s.width - 1, s.height - 1); g.drawString(text, 2, 12); } public Dimension getPreferredSize() { int width = 70, height = 20; Graphics g = getGraphics(); FontMetrics fm = null; if (g != null) fm = g.getFontMetrics(); if (fm != null) { width = fm.stringWidth(text); height = fm.getHeight(); } return new Dimension(width + 5, height + 2); } /** Ovde uvamo property text */ private String text = "text1"; }
Kada ovakvu komponentu upotrebimo u okviru RAD alata kakav je JBuilder, on e biti u stanju da prepozna svojstva i dogaaje ove komponente i omogui nam da ih podeavamo u vreme pisanja aplikacije (design-time). Slika 3.6a prikazuje svojstva koja je JBuilder otkrio u naoj komponenti, a slika 3.6b prikazuje dogaaje na koje naa komponenta moe da reaguje. Vidimo da se u spisuku property-ja nalazi i text, koji smo sami definisali. Ostali property-ji su nasleeni iz klase JComponent.
64
a)
b)
JBuilder ne koristi nikakve posebne tehnike za otkrivanje svojstava i dogaaja u komponentama. Naprotiv, koristi standardan introspection mehanizam da otkrije te informacije. Ovaj mehanizam implementiran je u klasi java.beans. Introspector. Na kraju, sam JBuilder je pisan u Javi, tako da je prirodno da koristi mehanizme koje obezbeuje Java platforma!
65
Poglavlje 4
66
Kada se govori o vezi, govori se o vezi izmeu dva programa, a ne o vezi izmeu dva raunara. Dva programa koji uestvuju u vezi mogu se izvravati i na istom raunaru. Sa druge strane, jedan raunar moe istovremeno izvravati vie programa koji pristupaju mrei. Koncept porta je upravo nain da se omogui razlikovanje vie programa koji su pokrenuti na istom raunaru (tj. na istoj IP adresi) i istovremeno pristupaju mrei. Slika 4.2 ilustruje situaciju kada se na jednom voru mree izvravaju dva programa, i jedan od njih ima vezu sa dva programa istovremeno. Program A (na voru X sa IP adresom 147.91.177.196) ima uspostavljenu vezu sa programom B (na voru Y). Port koji koristi program A za ovu vezu je 7534, a port koji koristi program B je 9000. Program A ima jo jednu uspostavljenu vezu, sa programom C, preko svog porta 7826, ka portu 8080 vora Y.
vor X (147.91.177.196) 7534 program A 7826 8080 vor Y (204.1.177.96) 9000 program B program C
9864
Statika metoda getLocalHost generie InetAddress objekat koji predstavlja adresu maine na kojoj se program izvrava:
InetAddress c = InetAddress.getLocalHost();
Kreiranje Socket objekta, tj. otvaranje konekcije, omoguava da se preuzmu reference na stream objekte koji se koriste za slanje i primanje poruka. Jedna mogua inicijalizacija stream-ova je prikazana na sledeem primeru:
// inicijalizuj ulazni stream BufferedReader in = new BufferedReader( new InputStreamReader( sock.getInputStream())); // inicijalizuj izlazni stream PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( sock.getOutputStream())), true);
Konstrukcija i upotreba Stream, Reader i Writer objekata je detaljnije opisana u knjizi Thinking in Java. Treba primetiti da se odgovarajui Reader/Writer objekti generiu na osnovu stream-ova koje obezbeuje Socket objekat, metodama getInputStream i getOutputStream. Komunikaca sa programom sa kojim je uspostavljena konekcija sada se moe odvijati putem poziva odgovarajuih metoda Reader i Writer klasa. Na primer:
out.writeln("Hello"); String response = in.readLine();
Prekid komunikacije treba zavriti propisnim zatvaranjem konekcije. Zatvaranje konekcije se nejee svodi na zatvaranje ulaznog i izlaznog stream-a i zatvaranje socket-a. Sledi primer:
out.close(); in.close(); sock.close();
68
// komunikacija out.println(zahtev); // aljem zahtev String response = in.readLine(); // itam odgovor // i tako potreban broj puta... // prekid veze in.close(); out.close(); s.close();
Ovim je konstruisan ServerSocket objekat pomou koga e se oekivati klijenti na portu 9000. Samo oslukivanje na datom portu inicira se pozivom metode accept. Ve je reeno da ova metoda blokira izvravanje programa sve dok neki klijent ne uspostavi vezu. Rezultat metode je inicijalizovani Socket objekat koga serverski program dalje koristi za komunikaciju sa klijentom koji je uspostavio vezu. Tipino poziv accept metode izgleda ovako:
Socket s = ss.accept();
69
Pored niti za komunikaciju sa pojedinim klijentima, potrebna je i posebna nit koja oslukuje serverski port i, po uspostavljanju veze, pokree nit za komunikaciju sa klijentom, a sama se vraa u stanje ekanja na novog klijenta. Ukupno se, dakle, serverski program sastoji od n+1 niti prilikom obrade n istovremenih klijentskih zahteva.
// Serverska petlja ServerSocket ss = new ServerSocket(port); while (true) { Socket s = ss.accept(); ServerThread st = new ServerThread(s); }
Zadatak serverskog programa je sledei: 1. eka klijente u beskonanoj petlji. 2. Za svakog klijenta koji je uspostavio vezu pokree posebnu nit koja radi sledee: a. ita zahtev klijenta (tekst HELLO).
71
b. alje odgovor redni broj obraenog zahteva. Osnovna nit servera u kojoj se oekuju klijenti nalazi se u klasi Server1:
import java.io.*; import java.net.*; public class Server1 { public static final int TCP_PORT = 9000; public static void main(String[] args) { try { int clientCounter = 0; // sluaj zahteve na datom portu ServerSocket ss = new ServerSocket(TCP_PORT); System.out.println("Server running..."); while (true) { Socket sock = ss.accept(); System.out.println("Client accepted: " + (++clientCounter)); ServerThread st = new ServerThread(sock, clientCounter); } } catch (Exception ex) { ex.printStackTrace(); } } }
Vidimo da se u okviru osnovne niti nalazi beskonana while petlja u okviru koje se oekuju klijenti i pokree nit za komunikaciju sa klijentom. Konstruktor ove niti prima kao argumente Socket objekat koji e koristiti u komunikaciji (sock) i redni broj klijenta koji se prijavio (clientCounter). Nit za komunikaciju predstavljena je klasom ServerThread:
import java.io.*; import java.net.*; public class ServerThread extends Thread { public ServerThread(Socket sock, int value) { this.sock = sock; this.value = value; try { // inicijalizuj ulazni stream in = new BufferedReader( new InputStreamReader( sock.getInputStream())); // inicijalizuj izlazni stream out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( sock.getOutputStream())), true); } catch (Exception ex) { ex.printStackTrace(); } start();
72
public void run() { try { // procitaj zahtev String request = in.readLine(); // odgovori na zahtev out.println("(" + value + ")"); // zatvori konekciju in.close(); out.close(); sock.close(); } catch (Exception ex) { ex.printStackTrace(); } } private private private private } Socket sock; int value; BufferedReader in; PrintWriter out;
Komunikacija izmeu, recimo, Web servera i klijenta (Web itaa), podsea na komunikaciju prikazanu u gornjem primeru. Zahtev Web itaa tipino sadri naziv datoteke koju ita trai. Odgovor servera je poruka u kojoj se nalazi traena datoteka. U objanjenju prethodnog primera bilo je malo rei o formatu poruka koje se razmenjuju izmeu klijenta i servera. Zahtev klijenta je tekst HELLO koji se zavrava znakom za novi red (linefeed, LF). Slanje ovakve poruke postie se pozivom
out.println("HELLO");
u okviru klijentskog programa. Sa druge strane, server oitava zahtev klijenta pomou poziva metode readLine:
String request = in.readLine();
Ova metoda e blokirati izvravanje programa sve dok se na ulazu ne pojavi znak za novi red (LF), i tada e vratiti tekst koji je sa mree pristigao pre tog znaka. Korienje znaka LF kao oznake kraja poruke (ili kraja jednog dela poruke) je relativno esto u specifikaciji raznih Internet protokola. Sa druge strane, nije obavezno koristiti ba LF kao oznaku kraja poruke. Pre svega, komunikacioni protokol moe biti tako specificiran da je duina poruke koja se oekuje unapred poznata, tako da takvu poruku moemo proitati pozivom
in.read(buffer, 0, length);
U sluaju da je duina poruke nije unapred poznata, korienje karaktera LF je zgodno jer postoji metoda readLine koja blokira izvravanje sve dok taj karakter ne pristigne sa mree. U sluaju da odluimo da ne koristimo karakter LF nego neki drugi, morali bismo sami da implementiramo funkcionalnost ove metode.
73
74
Poglavlje 5
75
76
posredstvom GUI interfejsa. Kako e pisaka nit znati da treba da poalje poruku? Tako to e sinhronizovano pristupati deljenom objektu koji predstavlja kontejner za slanje poruka. Poruke e u ovaj objekat upisivati osnovna nit programa (nit koja se bavi reakcijama na dogaaje korisnikog interfejsa). Slika 5.4. predstavlja dijagram sekvenci koji opisuje situaciju kada korisnik otkuca poruku i klikne na dugme Send da bi je poslao. Slika 5.5 predstavlja dijagram sekvenci koji opisuje prijem poruke kada je server poalje.
bSend.doClick() eventDispatchThread actionListener chatData writerThread outputStream
readerThread
inputStream
textArea
readLine()
append()
Slika 5.6 predstavlja dijagram klasa koje uestvuju u komunikaciji sa serverom. Klasa ChatClient je osnovna klasa programa koja predstavlja i glavni prozor aplikacije. U okviru nje uspostavlja se veza sa serverom kreiranjem Socket objekta. Inicijalizovani Socket objekat poseduje stream-ove za itanje (InputStream) i pisanje (OutputStream). Na osnovu njih kreiraju se BufferedReader i PrintWriter objekti za jednostavnije rukovanje stream-ovima. Konano, kreiraju se ReaderThread i WriterThread objekti koji implementiraju itaku i pisaku nit. Ovi objekti koriste odgovarajue Reader/Writer objekte za komunikaciju. ChatData objekat je namenjen za sinhronizovanu komunikaciju sa WriterThread niti. Slika 5.7 prikazuje dijagram klasa koje uestvuju u radu korisnikog interfejsa. LoginDlg je dijalog za prijavljivanje na server prilikom pokretanja klijenta. JTextArea je vielinijsko tekstualno polje u kome se hronoloki ispisuju poruke.
77
JButton je dugme za iniciranje slanja poruke. JTextField je jednolinijsko tekstualno polje u kome se unosi nova poruka.
Socket
InputStream
OutputStream
BufferedReader
PrintWriter
ChatClient
ReaderThread
ChatData
WriterThread
ChatClient
JTextField
LoginDlg JButton
JTextArea
ReaderThread
78
readerThread
inputStream
ClientUtils
activeClient
writerThread
outputStream
readLine()
Slika 5.8. itaka nit prima poruku i prosleuje je svim pisakim nitima
serverListener
serverSocket
accept()
create() inputStream create() create() create() create() outputStream activeClient readerThread writerThread
Slika 5.9. Uspeno prijavljivanje klijenta i kreiranje posveene itake i pisake niti
Klasa ChatServer je osnovna klasa serverskog programa, koja predstavlja i osnovnu prozorsku klasu. Prilikom inicijalizacije ChatServer objekat kreira ServerListener nit koja eka klijente. Po uspostavljanju veze sa klijentom, ServerListener nit kreira PrintWriter i BufferedReader objekte za komunikaciju, kreira ActiveClient objekat koji je namenjen za razmenu poruka izmeu itakih i pisakih niti i, na posletku, kreira itaku i pisaku nit posveenu novom
79
klijentu. Klasa ClientUtils implementira operacije nad kolekcijom prijavljenih klijenata (registracija, slanje poruke svima, itd). Slika 5.10. prikazuje klase koje uestvuju u komunikaciji sa klijentima.
ChatServer
WriterThread
ActiveClient
ReaderThread
80
Poglavlje 6
81
programa. Tipino se JAR fajl u kome se nalazi JDBC drajver/biblioteka smeta u CLASSPATH i time postaje dostupan svim programima na datom raunaru. Nema prepreke da se vie razliitih drajvera smesti u CLASSPATH istovremeno. Takoe, nema prepreke da jedan program komunicira sa vie baza podataka istovremeno. Na primer, za pristup Oracle serveru potrebno je u program ukljuiti odgovarajui drajver. U pitanju je biblioteka koju kompanija Oracle distribuira besplatno i nalazi se u fajlu sa nazivom classes111.zip (starija verzija) ili classes12.jar (novija verzija). Dovoljno je ovu datoteku ukljuiti u CLASSPATH da bi se JDBC drajver za Oracle instalirao. Slino drajveru za Oracle koriste se i drajveri drugih proizvoaa sistema za upravljanje bazama podataka. Obino proizvoa odreenog SUBP nudi i JDBC drajver za svoje sisteme. Najupadljiviji izuzetak je Microsoft, koji nema drajver za svoj SQL Server, ali se takav drajver moe nabaviti od third-party firmi.
Prvi red predstavlja uitavanje odgovarajue drajver klase; proizvoa drajvera obavezno navodi naziv ove klase za svoj drajver. ta, zapravo, znai uitavanje klase? Odeljak 6.9 opisuje detaljnije ovu temu i druge pojedinosti vezane za korienje JDBC drajvera koje nisu od velikog znaaja za samo korienje drajvera. Drugi red primera predstavlja otvaranje konekcije sa SUBP nakon to je drajver uitan. Prvi parametar metode getConnection je string koji sadri podatke potrebne drajveru da bi se povezao sa SUBP. Format ovog stringa propisuje proizvoa drajvera. Tipino ovaj string sadri adresu raunara na kome se nalazi SUBP (u primeru to je branko.tmd.ns.ac.yu), TCP port na kome SUBP oekuje klijente (u primeru 1526) i naziv baze kojoj se pristupa (VTA). Druga dva parametra metode getConnection su korisniko ime i lozinka kojima se vri prijavljivanje na SUBP. Sledi primer povezivanja na SQL Server SUBP pomou JDBC drajvera TaveConn24C kompanije Atinav.
// uitavanje SQL Server drajvera Class.forName("net.avenir.jdbc2.Driver"); // otvaranje konekcije Connection conn = DriverManager.getConnection( "jdbc:AvenirDriver://branko.tmd.ns.ac.yu:1526/VTA",
82
"vta", "vta");
Rezultat uspene uspostave veze sa SUBP je, sa stanovita Java programa, inicijalizovani Connection objekat. Metoda forName moe da izazove izuzetak ClassNotFoundException, a metoda getConnection izuzetak SQLException. Pozivi ovih metoda moraju biti smeteni u odgovarajui try/catch blok. Sve ostale JDBC metode mogu da izazovu izuzetak SQLException. Prilikom zavretka komunikacije sa bazom podataka potrebno je zatvoriti otvorenu konekciju. To se postie pozivom metode close klase Connection:
conn.close();
PREDMET_ID = PREDMET_ID
Sve operacije nad bazom podataka, pa tako i postavljanje upita, definiu se odgovarajuim SQL naredbama koje se alju serveru. SQL naredba je, u okviru JDBC interfejsa, definisana Statement objektom. Ovakav objekat se moe kreirati nakon uspostavljene veze sledeim iskazom:
Statement stmt = conn.createStatement();
gde je conn inicijalizovani Connection objekat iz prethodnih primera. Za slanje upita serveru koristi se metoda executeQuery, iji je parametar string koji sadri tekst SQL upita. Sledi primer:
ResultSet rset = stmt.executeQuery( "SELECT ime, prezime FROM nastavnici");
Rezultat ove metode je inicijalizovani objekat klase ResultSet, koji je namenjen za skladitenje rezultata upita. Rezultat upita se moe proitati pomou ovog objekta. itanje rezultata je operacija koja se odvija red-po-red u okviru tabele koja predstavlja rezultat. Za kretanje kroz tabelu rezultata koristi se koncept tekueg reda. Tekui red se moe pomerati iskljuivo od poetka ka kraju tabele, bez preskakanja i bez vie prolaza (u JDBC verziji 1.22; JDBC 2.0 omoguava prolaz kroz tabelu rezultata u oba smera i mnogo veu fleksibilnost u korienju ResultSet objekata). Inicijalno, nijedan red nije tekui red. Prvim
83
pozivom metode next klase ResultSet tekui red e biti prvi red tabele rezultata (ukoliko tabela sadri bar jedan red). Metoda next vraa boolean vrednost koja oznaava da li novi tekui red postoji ili ne. Tipino se rezultat upita ita u petlji kao u sledeoj primeru:
while (rset.next()) { // ovde itamo red po red rezultata }
Za tekui red rezultata pojedine vrednosti polja oitavaju se metodama getString, getInt, getDate, itd. (za svaki tip podatka u bazi postoji odgovarajua metoda). Konverziju izmeu tipova podataka baze i jezika Java obavlja JDBC drajver. Parametar getXXX metoda je redni broj kolone koja se oitava; redni brojevi poinju od 1, a ne od nula kako bi se oekivalo. Nakon prestanka korienja ResultSet objekta potrebno je pozvati njegovu metodu close radi oslobaanja resursa koje je taj rezultat upita zauzimao. Slino vai i za Statement objekat. Sledi primer programa koji alje upit Oracle serveru i ispisuje rezultat upita na konzolu.
import java.sql.*; public class Demo1 { public static void main(String args[]) { try { // uitavanje Oracle drajvera Class.forName("oracle.jdbc.driver.OracleDriver"); // konekcija Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta"); // slanje upita String query = "SELECT ime, prezime FROM nastavnici"; Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery(query); // itanje rezultata upita while (rset.next()) { System.out.println(rset.getString(1) + " " + rset.getString(2)); } // oslobaanje resursa i zatvaranje veze rset.close(); stmt.close(); conn.close();
} }
84
Iz primera se vidi da je jedini deo programa koji zavisi od upotrebljenog SUBP blok koji obavlja inicijalizaciju: uitavanje odgovarajueg drajvera i otvaranje konekcije. SQL naredba koja se alje serveru je, u ovom primeru, elementarna: svaki server bi trebalo da ume da je interpretira. U tom smislu, programski kod koji koristi JDBC je prenosiv na razliite SUBP, jer se u celom programu menjaju samo iskazi za uitavanje drajvera i otvaranje konekcije; ostatak programa bi, u idealnom sluaju, funkcionisao i sa novim serverom.
Statement objekat nakon upotrebe i u ovom sluaju treba da oslobodi resurse koje je zauzimao pozivom metode close.
Vidimo da je u pitanju praktino ista naredba koja se ponavlja vie puta: njena struktura je ista, a razlikuju se samo podaci koji se u njoj pojavljuju. Slanje ovakvih naredbi pomou Statement objekta i metode executeUpdate e eljeni posao obaviti potpuno korektno. Svaki poziv executeUpdate e jednu ovakvu SQL naredbu slati serveru. Server e, po prijemu naredbe, nju parsirati, analizirati i formirati nekakav plan njenog izvravanja. Nakon toga e tu
85
naredbu i izvriti. U ovakvim sluajevima mogue je popraviti performanse programa korienjem klase PreparedStatement. Ideja iza ove klase je sledea: ako se serveru alje vie identinih SQL naredbi (kao u prethodnom primeru), bilo bi zgodno da server samo jednom izvri parsiranje i analizu SQL naredbe i formira plan izvravanja. Takav plan izvravanja moe da se koristi vie puta, za identine naredbe koje se razlikuju samo u podacima koje nose u sebi. To bi znailo da se za n identinih naredbi takva naredba alje samo jednom. Ona se tamo jednom analizira i n puta izvri na osnovu plana izvravanja. U sluaju da koristimo Statement objekte, server bi n puta vrio analizu i n puta izvrio naredbu. Objekat klase PreparedStatement se konstruie kao u sledeem primeru:
PreparedStatement pstmt = conn.prepareStatement( "INSERT INTO nastavnici (nastavnik_id, ime, prezime, zvanje)"+ "VALUES (?, ?, ?, ?)");
Ovakva inicijalizacija obuhvata i slanje naredbe serveru na analizu. Vidimo da je u okviru naredbe njen promenljivi deo (mesto gde se nalaze konkretni podaci za svaku naredbu) predstavljen upitnicima. Pre slanja konkretne naredbe serveru potrebno je definisati vrednost svakog od upitnika u ovoj naredbi. Ovo ilustruje sledei primer:
pstmt.setInt(1, 4); pstmt.setString(2, "Sima"); pstmt.setString(3, "Simi"); pstmt.setString(4, "docent");
Metode setXXX su namenjene za postavljanje vrednosti parametara SQL naredbe. Postoji odgovarajua setXXX metoda za svaki tip podatka, analogno getXXX metodama klase ResultSet. Prvi parametar setXXX metoda je redni broj upitnika u SQL naredbi (poevi brojanje od 1). Drugi parametar je konkretna vrednost koju prima dati parametar. Sada je mogue izvriti konkretnu naredbu iskazom:
pstmt.executeUpdate();
Sekvenca postavljanja vrednosti parametara (setXXX) i izvravanja naredbe (executeUpdate) se moe ponoviti vie puta, bez ponovne inicijalizacije PreparedStatement objekta to je i bila ideja analize SQL naredbi unapred. PreparedStatement je mogue koristiti i za slanje upita, samo to se tada ne poziva metoda executeUpdate, ve metoda executeQuery, iji rezultat je ponovo ResultSet objekat koji se koristi na ranije opisani nain. Nakon korienja PreparedStatement objekta potrebno je osloboditi resurse koje je zauzimao pozivom metode close. Sada sledi primer programa koji koristi PreparedStatement za dodavanje vie redova u tabelu nastavnici.
import java.sql.*; public class Demo2 { public static void main(String args[]) {
86
try { // uitavanje Oracle drajvera Class.forName("oracle.jdbc.driver.OracleDriver"); // konekcija Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta"); // dodavanje novih nastavnika PreparedStatement stmt = conn.prepareStatement( "insert into nastavnici "+ "(nastavnik_id, ime, prezime, zvanje) "+ "values (?, ?, ?, ?)"); stmt.setInt(1, 4); stmt.setString(2, "Sima"); stmt.setString(3, "Simi"); stmt.setString(4, "docent"); stmt.executeUpdate(); stmt.setInt(1, 5); stmt.setString(2, "Vasa"); stmt.setString(3, "Vasi"); stmt.setString(4, "docent"); stmt.executeUpdate(); stmt.close(); conn.close(); } catch (Exception ex) { ex.printStackTrace(); }
} }
87
strane, uskladitena procedura nalazi se kompletno na serveru. Tamo je smetena u obliku koji je unapred pripremljen za efikasno izvravanje, tako da njen poziv ne obuhvata i parsiranje, analizu naredbi, itd. Jedan poziv ovakve uskladitene procedure generie daleko manji saobraaj na mrei, a takoe je i izvravanje procedure u okviru servera bre nego to bi to bio sluaj da se ona izvrava na klijentu. Slika 6.2 ilustruje razliku izmeu pisanja Java koda koji operie nad bazom i korienja uskladitenih procedura.
klijent procedura server
SQL naredbe a)
klijent
procedura
server
b)
Slika 6.2. a) procedura koja operie nad bazom pisana u jeziku klijenta b) poziv ekvivalentne uskladitene procedure na serveru
Sledi primer jedne uskladitene funkcije pisane u Oracle-ovom jeziku PL/SQL. Namena ove procedure je da povee nastavnika datog svojim imenom i prezimenom i predmet koji on predaje datog svojim nazivom. Funkcija vraa 1 ako je operacija uspeno izvrena ili 0 ukoliko ne postoji dati nastavnik ili predmet.
create or replace function povezi (ime_ in varchar2, prezime_ in varchar2, naziv_ in varchar2) return integer as nasID integer; predID integer; begin select nastavnik_id into nasID from nastavnici where ime = ime_ and prezime = prezime_; select predmet_id into predID from predmeti where naziv = naziv_; insert into predaje (nastavnik_id, predmet_id) values (nasID, predID); return 1; exception when no_data_found then return 0; end povezi;
Preostaje jo da vidimo kako se ovakva procedura moe pozvati iz Java programa. Za tu namenu koristi se CallableStatement objekat koji se konstruie kao u sledeem primeru:
88
Format stringa koji predstavlja poziv procedure definisan je JDBC-om; ne definie ga svaki proizvoa SUBP posebno. Ovakav string obavezno sadri vitiaste zagrade. Obavezna je i kljuna re call iza koje sledi naziv procedure ili funkcije koja se poziva. Zatim sledi lista parametara datih upitnicima u obinim zagradama. Ukoliko je u pitanju poziv funkcije, pre kljune rei call treba da se nae jedan upitnik i znak za jednako (kao u ovom primeru), to predstavlja preuzimanje rezultata funkcije. Parametri ovakvih procedura ili funkcija mogu biti ulazni, izlazni, ili ulaznoizlazni. Svi ulazni parametri moraju biti definisani pre samog poziva procedure. Svim izlaznim parametrima se mora definisati tip podatka, takoe pre poziva procedure. Evo i primera kako se to radi, koristei objekat cstmt inicijalizovan u prethodnom primeru:
cstmt.setString(2, "Sima"); cstmt.setString(3, "Simi"); cstmt.setString(4, "Osnovi raunarstva"); cstmt.registerOutParameter(1, Types.INTEGER);
Metode setXXX se koriste na isti nain kao i kod PreparedStatement objekata. Metoda registerOutParameter je namenjena za definisanje tipa izlaznih argumenata uskladitene procedure/funkcije i za definisanje tipa rezultata funkcije. U ovom primeru je rezultat funkcije oznaen kao vrednost celobrojnog tipa. Oznake tipova nalaze se u klasi java.sql.Types kao konstante. Treba obratiti panju na to da se kao redni broj parametra navodi redni broj upitnika koji se javlja u pozivu funkcije, ukljuujui i upitnik koji predstavlja rezultat funkcije. Sada je mogue i uputiti poziv funkcije, na sledei nain:
cstmt.executeQuery();
Sledi primer programa koji poziva uskladitenu funkciju povezi prikazanu u prethodnim primerima.
import java.sql.*; public class Demo3 { public static void main(String args[]) { try { // uitavanje Oracle drajvera Class.forName("oracle.jdbc.driver.OracleDriver"); // konekcija Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta"); // povezivanje novih nastavnika sa predmetima CallableStatement stmt = conn.prepareCall(
89
"{? = call povezi (?, ?, ?)}"); stmt.setString(2, "Sima"); stmt.setString(3, "Simic"); stmt.setString(4, "Osnovi racunarstva"); stmt.registerOutParameter(1, Types.INTEGER); stmt.executeQuery(); System.out.println("Status: " + stmt.getInt(1)); stmt.close(); conn.close();
} }
Najee se konekcije ne koriste u auto-commit reimu. Tipian blok programskog koda koji vri izmene u bazi podataka bi izgledao ovako:
try { Statement stmt = conn.createStatement(); // izvri neke izmene... stmt.close(); conn.commit(); } catch (SQLException ex) { try { conn.rollback(); } catch (Exception e) { } }
Dakle, na kraju svake transakcije koja se tipino nalazi u try/catch bloku mora se pozvati commit metoda. Ukoliko se dogodilo neto nepredvieno u toku izvravanja operacije to je rezultovalo izuzetkom, poziva se metoda rollback u okviru catch sekcije. Metoda rollback takoe moe da izazove izuzetak, tako da je i ona morala biti smetena u poseban try/catch blok.
90
U pitanju je poziv statike metode forName klase Class. Instance klase Class predstavljaju klase i interfejse pokrenute Java aplikacije. Dakle, za svaku klasu i interfejs koja se koristi u programu postoji po jedna instanca klase Class koja je opisuje. Klasa Class sadri metode pomou kojih se mogu, u toku izvravanja programa, dobiti informacije o metodama, atributima i ostalim karakteristikama neke konkretne klase koja se koristi u programu. Statika metoda forName vraa inicijalizovan objekat klase Class koji odgovara klasi iji je naziv dat parametrom metode. Inicijalizacija ovakvog Class objekta obuhvata i inicijalizaciju statikih atributa klase koja se uitava u JVM. Inicijalizacija statikih atributa klase moe se smestiti u poseban static blok u okviru klase koji ne pripada nijednoj metodi. Sledi primer jedne klase koja sadri takav blok:
class Test { static int attr; static { // ovde se vri inicijalizacija statikih atributa attr = 0; } }
Vratimo se sada JDBC drajveru: klasa OracleDriver je osnovna klasa Oracle-ovog JDBC drajvera. Njeno uitavanje pozivom metode forName e izvriti i static blok ove klase u kome se poziva metoda registerDriver klase DriverManager ime se konkretan drajver registruje na jednom jedinstvenom mestu. Poziv metode getConnection klase DriverManager vraa inicijalizovanu vezu sa bazom podataka predstavljenu Connection objektom. Za uspostavljanje veze koristie se onaj drajver koji moe da interpretira adresu servera navedenu kao parametar metode getConnection. Dakle, ako se metoda getConnection pozove sa sledeim parametrima:
Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta");
klasa DriverManager e upotrebiti ba Oracle JDBC drajver za uspostavljanje konekcije zato to se Oracle drajver registrovao da moe da uspostavlja konekcije iji opis poinje sa jdbc:oracle. Zapravo, jdbc je obavezni poetak ovakvog stringa, iji se segmenti razdvajaju dvotakom. Sledei segment stringa (oracle) oznaava drajver koji e obezbediti povezivanje sa bazom podataka. U primeru povezivanja sa SQL Server SUBP, gde je povezivanje izvreno sa
Connection conn = DriverManager.getConnection( "jdbc:AvenirDriver://branko.tmd.ns.ac.yu:1526/VTA", "vta", "vta");
91
vidi se da je vrednost drugog segmenta AvenirDriver to je string kojim se registrovao odgovarajui drajver. Rezultat getConnection metode je inicijalizovani Connection objekat. Connection je sam po sebi interfejs i ne moe imati svoje instance. Ovde se radi o objektu koji je instanca klase koja implementira Connection interfejs. Koja je klasa u pitanju zavisi od drajvera koji je upotrebljen; Oracle drajver e vratiti instancu klase OracleConnection, neki drugi drajver e vratiti instancu neke druge klase, itd. Klasa OracleConnection sadri kod koji je specifian za komunikaciju sa Oracle serverima, itd. Nadalje, kreiranje Statement objekta je rezultat poziva createStatement metode Connection interfejsa. U konkretnom sluaju, kada se metoda createStatement pozove nad instancom klase OracleConnection rezultat e biti instanca klase OracleStatement koja implementira Statement interfejs. Slino vai i za ResultSet interfejs. Konani rezultat ovakvog koncepta je program koji operie nad objektima kojima pristupa preko njihovih standardnih (JDBC-om definisanih) interfejsa. Konkretne klase koje implementiraju te interfejse se nigde ne spominju u okviru programa. Jedino mesto gde se specificira koja familija klasa e biti upotrebljena je uitavanje drajvera i otvaranje konekcije sa SUBP. Ovakva arhitektura je zasluna to se isti JDBC programski kod moe koristiti sa razliitim SUBP prostom zamenom drajvera.
92
Poglavlje 7
Klijent
Mrea
Klijent
Server
Klijent
Jedna od osnovnih karakteristika klijent/server sistema je distribuirana obrada podataka logika aplikacije je podeljena izmeu klijenta i servera tako da obezbedi optimalno korienje resursa. Na primer, prezentacija podataka i provera ulaznih podataka su sastavni deo klijent-aplikacije, dok se rukovanje podacima, u smislu njihovog fizikog smetaja i kontrole pristupa, vri na serveru. Neke od prednosti ovakvog modela obrade podataka su centralizovano upravljanje resursima sistema i jednostavnije obezbeivanje sigurnosti podataka.
93
U ovakvim sistemima se najee nalazi samo jedan server. Sa aspekta raunarskog hardvera to moe biti vie raunara povezanih na odgovarajui nain radi poveanja pouzdanosti sistema (otkaz jednog server-raunara ne izaziva otkaz celog sistema) ili brzine odziva (kroz paralelizovan rad vie serverskih raunara). Osnovni problem koji se ovde javlja je nedostatak skalabilnosti. Pod skalabilnou se podrazumeva osobina sistema da omogui efikasan rad velikom broju korisnika, i da dalje poveavanje broja korisnika ne izaziva drastian pad performansi sistema. Poveavanje propusne moi servera u pogledu broja korisnika koji mogu efikasno da rade ili koliine podataka koja se obrauje je izuzetno skupo jer zahteva velika ulaganja u serverske raunare visokih performansi. Klijent-aplikacije u ovakvim sistemima su programi pisani za konkretnu raunarsku platformu klijenta. U heterogenim sistemima to podrazumeva programe pisane posebno za svaku platformu (Windows, Macintosh, razne Unix radne stanice itd.). Pored toga, klijent-aplikacije je potrebno instalirati i odravati na svakom klijent-raunaru, to u velikim mreama predstavlja mnogo vei izdatak od inicijalne nabavke opreme, na primer.
Slika 7.2 predstavlja skicu klijent/server sistema zasnovanog na WWW i Java tehnologijama.
94
Web ita
Mrea
Web ita
Web ita
Klijent aplikacija
Aplikacioni server
SUBP
Za razliku od dvoslojnog modela obrade podataka, gde je logika aplikacije bila podeljena izmeu klijenta i servera, u ovom modelu ona se nalazi koncentrisana u tzv. aplikacionom serveru serveru ija je namena da izvrava programski kod koji implementira logiku aplikacije. Klijent aplikacija je namenjena samo za implementaciju korisnikog interfejsa, a funkcija sistema za upravljanje bazom podataka je iskljuivo fiziko rukovanje podacima (u prethodnom sluaju je, pored toga, izvravao i deo logike aplikacije).
95
Ovakav koncept je doveo do podele programskog koda na segmente koji implementiraju tano odreene funkcije sistema. Tako organizovan sistem je jednostavniji za odravanje, jer je mogue nezavisno razvijati korisniki interfejs, i logiku aplikacije. Za potrebe fizikog rukovanja podacima najee se koristi neki od komercijalno dostupnih servera za tu namenu. Troslojne arhitekture informacionih sistema podrazumevaju oslanjanje na standarde u odgovarajuom oblastima. Najee su u pitanju sistemi zasnovani na Internet tehnologijama. Oslanjanje na standarde omoguava integraciju informacionih sistema heterogenih u pogledu koriene hardverske i softverske opreme. Na primer, raunarska mrea ovakvog sistema moe biti zasnovana na TCP/IP familiji protokola. Serveri u mrei mogu biti od razliitih proizvoaa, sve dok obezbeuju standardne servise predviene protokolom. Druga vana karakteristika troslojnih sistema je skalabilnost. Pre svega, poveavanje broja klijenata je jednostavno. Poveavanje propusne moi servera srednjeg sloja je mogue kroz dodavanje novih serverskih maina. Analogno tome mogue je poveati i propusnu mo zadnjeg sloja. Slika 7.4 prikazuje jednu od moguih konfiguracija ovakvog sistema. Ovde je vano primetiti da se poveanje brzine odziva serverskog sloja moe postii dodavanjem novih serverskih maina uz korienje postojeih. Na taj nain moe se iskoristiti i oprema koja ne mora imati vrhunske performanse. Sistem sa vie servera karakterie i poveana pouzdanost i fleksibilnost. Logika aplikacije se moe menjati i u toku rada sistema. Pored toga, mogue je efikasno vriti balansiranje optereenja serverskog podsistema. Daljim proirivanjem koncepta troslojnih sistema dolazi se do pojma vieslojnih sistema (multitier architecture), gde se vri dalja podela na komponente u okviru srednjeg sloja sa ciljem jo veeg poveanja skalabilnosti, odnosno performansi.
Klijent
Klijent
Aplikacioni server
SUBP
Klijent
Aplikacioni server
SUBP
Klijent
96
Jedna mogua arhitektura vieslojnih sistema je prikazana na slici 7.5. Srednji sloj je podeljen na dva sloja: jedan je namenjen za opsluivanje Web klijenata, a drugi sadri komponente koje implementiraju poslovnu logiku sistema.
Web browser
Za ovakve etvoroslojne sisteme postoje odgovarajue tehnologije koje omoguavanju njihovu izgradnju. Slika 7.6 prikazuje skup ovakvih tehnologija definisanih oko programskog jezika Java.
EJB / CORBA HTML Servlets / JSP
HTTP
RMI / IIOP
JDBC RDBMS
RDBMS
Interakciju sa korisnikom u ovakvom sistemu obavljaju klijenti koji imaju standardan Web interfejs. U pitanju su Web itai koji prikazuju HTML stranice. Komunikacija izmeu Web itaa i Web servera se odvija putem standardnog HTTP protokola, uz dodatak cookie podataka kojima se prati korisnika sesija dok se on kree po Web sajtu. Stranice koje prikazuju klijenti su najee generisane dinamiki, tj. po prijemu zahteva za nekom stranicom. Dinamiko generisanje Web sadraja na osnovu podataka iz ostatka sistema vre servleti ili se za tu namenu koriste JSP (Java Server Pages) stranice. Za potrebe manipulacije podacima u sistemu servleti ili JSP stranice pristupaju objektima u okviru aplikacionih servera koji su dostupni kao CORBA ili EJB (Enterprise JavaBeans) komponente. Protokol za komunikaciju izmeu ova dva
97
sloja je JRMP (Java Remote Method Protocol), protokol za komunikaciju izmeu distribuiranih Java objekata, ili IIOP (Internet Inter-ORB Protocol) ekvivalentan protokol vezan za CORBA tehnologiju. CORBA/EJB komponente za potrebe skladitenja podataka u bazi podataka pristupaju serveru za upravljanje bazama podataka preko standardnog JDBC interfejsa. Naredna poglavlja e opisati ovde pomenute tehnologije.
98
Poglavlje 8
Zahtev koji klijent upuuje serveru je tekstualna poruka koja sadri vie redova. Prvi red poruke je najvaniji: on sadri komandu koju klijent upuuje serveru (u ovom sluaju to je GET komanda kojom se zahteva odreena datoteka sa Web servera), putanju datoteke u okviru Web sajta servera (/docs.html) i oznaku verzije protokola (HTTP/1.0). Naredni redovi u poruci predstavljaju dodatne informacije koje server moe da iskoristi za svoje potrebe. U ovom primeru, polje User-Agent predstavlja opis klijentskog softvera (tip i verzija Web itaa i operativnog sistema), polje Host predstavlja simboliku adresu klijenta, itd. GET je samo jedna od komandi koje moe da uputi klijent. Ujedno je to i daleko najee upotrebljavana komanda. U nastavku teksta bie rei i o nekim drugim komandama. Zadatak servera je da po prijemu ovakvog zahteva odgovori na njega. U ovom sluaju odgovor servera treba da sadri traenu datoteku (docs.html), pri emu je format odgovora takoe definisan HTTP protokolom. Slika 8.2 prikazuje situaciju kada server alje odgovor klijentu.
99
HTTP server
Prvi red odgovora sadri oznaku protokola, trocifreni broj koji predstavlja status izvrene operacije (u ovom sluaju to je 200), i tekstualni opis tog statusa (OK). Konstanta 200 oznaava da je zahtev uspeno izvren i da se traena datoteka nalazi u nastavku poruke. Druge konstante koje se ee sreu su 404 (traena datoteka nije pronaena), 407 (pristup datoteci nije dozvoljen), i 302 (datoteka premetena na drugo mesto). Konstante su definisane HTTP protokolom. Sledei red odgovora (Content-Type) je oznaka tipa sadraja koji se vraa. U pitanju su standardizovane oznake propisane u odgovarajuim RFC dokumentima. Na primer, HTML datoteke imaju oznaku text/html, datoteke sa ASCII tekstom bez formatiranja imaju oznaku text/plain, GIF slike image/gif, JPEG slike image/jpeg, itd. Neposredno pre sadraja datoteke koja se alje nalazi se jedan prazan red koji razdvaja zaglavlje odgovora od samog sadraja datoteke. Ukupna sekvenca aktivnosti klijenta i servera u HTTP komunikaciji je sledea: 1. 2. 3. 4. klijent otvara konekciju sa serverom klijent alje zahtev serveru server vraa odgovor zatvara se konekcija.
Vidimo da je komunikacija izmeu klijenta i servera zasnovana na zahtev/odgovor principu. Svaki par zahtev/odgovor smatra se nezavisnim od ostalih. Recimo, u sluaju da prvi klijent poalje zahtev serveru i dobije odgovor, zatim drugi klijent poalje zahtev i dobije odgovor, pa potom ponovo prvi klijent poalje novi zahtev, nema naina da se ustanovi da je prvi klijent poslao dva zahteva (prvi i trei). Server svaki zahtev opsluuje nezavisno od ostalih zahteva. U tom smislu, HTTP je stateless protokol: ne omoguava praenje stanja korisnike sesije izmeu slanja vie razliitih zahteva. Vano je primetiti jo neto: jedino to klijent moe da zatrai od servera je datoteka. Na serveru je da tu datoteku pronae (eventualno i modifikuje!) i poalje klijentu. Web sadraji koji se smetaju na server vidljivi su klijentima kao pojedine datoteke. Te datoteke mogu biti unapred pripremljene (npr. u editoru kakav je Microsoft FrontPage) i smetene u fajl-sistem Web servera. Mogu biti i generisane u letu po prijemu zahteva klijenta na neki poseban nain; klijent ne zna da li je datoteka koju je traio generisana statiki ili dinamiki. U tom smislu, Web sadraje (zapravo, datoteke) moemo podeliti na statike i dinamike.
100
Sa slike se vidi da, po prijemu zahteva klijenta, server uitava traenu datoteku iz svog fajl-sistema i alje je nazad klijentu preko mree. Dinamiki sadraji nisu uskladiteni unapred ve se generiu za svaki zahtev klijenta posebno. Sekvenca dogaaja kada klijent zatrai neki dinamiki generisanu datoteku je prikazana na slici 8.4.
1) klijent zahteva fajl HTTP klijent 2) server generie fajl i alje ga klijentu; ne snima ga u svoj fajl-sistem HTTP server
U ovom sluaju server nee traiti datoteku u okviru fajl-sistema; na neki nain server zna da je u pitanju dinamiki generisana datoteka i poziva odgovarajui potprogram koji e je generisati. Najee nema potrebe ovako generisanu datoteku uvati na serveru; ona se zato nee uvati u okviru fajlsistema servera.
8.3 Servleti
Servleti su jedna od tehnologija za generisanje dinamikih Web sadraja. Da bi se servleti mogli koristiti, Web server mora da ima odgovarajuu podrku za servlete. Pisanje servleta je mogue samo u programskom jeziku Java, tako da je za njihovo izvravanje potrebna i JVM (koju najee obezbeuje Web server). Servlet je, zapravo, Java klasa koja nasleuje standardnu klasu HttpServlet. Klase i interfejsi koji se koriste u pisanju servleta nalaze se u paketima javax.servlet i javax.servlet.http. Mogli bismo da napiemo klasu koja nasleuje HttpServlet i ne dodaje ili redefinie nita; takav servlet bio bi funkcionalan ali ne bi radio nita korisno. Dodavanje funkcionalnosti u servlet postie se redefinisanjem sledeih metoda (spisak nije potpun, ovde su pobrojane samo najee koriene metode): init destroy doGet doPost
101
Vano je imati na umu da se ove metode, u principu, nikada ne pozivaju direktno. Njih e pozivati Web server u odgovarajuim trenucima. 8.3.1 Metoda init Metoda init je namenjena za inicijalizaciju servleta pre njegove prve upotrebe. Poziva se tano jednom. Nema prepreke da servlet klasa sadri i svoj konstruktor u kome e obaviti deo inicijalizacije, ali je na raspolaganju i ova metoda. 8.3.2 Metoda destroy Metoda destroy se poziva prilikom unitavanja servleta. Namenjena je za obavljanje zadataka koje je neophodno obaviti pre nego to se zavri sa radom (oslobaanje resursa koje je servlet zauzimao: otvorenih datoteka, konekcija sa bazom podataka, itd). To se najee deava prilikom zaustavljanja Web servera ili u sluaju da Web server iz nekog razloga mora da uniti servlet objekat pre nego to nastavi sa radom. 8.3.3 Metoda doGet Metoda doGet se poziva za svaki GET zahtev klijenta koji je traio datoteku za ije generisanje je zaduen dati servlet. Metoda ima dva parametra:
public void doGet(HttpServletRequest request, HttpServletResponse response);
prvi parametar (request) sadri informacije o zahtevu klijenta na koji se odgovara, a drugi parametar (response) je namenjen da prihvati generisani odgovor servleta. Dakle, tipina sekvenca poziva metoda servleta je sledea: 1. poziv metode init prilikom pokretanja Web servera 2. viestruki pozivi metode doGet prilikom obrade zahteva klijenata 3. poziv metode destroy prilikom zaustavljanja Web servera
throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<HTML>"); out.println("<HEAD>"); out.println("<TITLE>Hello World Servlet</TITLE>"); out.println("</HEAD>"); out.println("<BODY>Hello World!</BODY>"); out.println("</HTML>");
} }
Pozivom metode setContentType nad objektom response definisan je tip sadraja koji e se vratiti klijentu. U ovom sluaju to je text/html. Ova vrednost e se direktno ugraditi u zaglavlje odgovora HTTP protokola, tako da je poziv ove metode obavezan. Sledei je poziv metode getWriter. Rezultat ove metode je inicijalizovan izlazni stream (objekat klase PrintWriter) u koji se upisuje generisani sadraj. U toku formiranja rezultujue HTML stranice najee se poziva metoda println koja dodaje fragmente stranice u izlazni stream. Redosled pozivanja ovih metoda je vaan: setContentType je neophodno pozvati pre metode getWriter. Drugo, setContentType se obino poziva na samom vrhu doGet metode, pre bilo kog drugog poziva. Prevoenjem ovakvog servleta dobija se .class fajl koji se smeta na odgovarajue mesto u okviru instalacije Web servera. Nain kako pristupiti servletu iz Web itaa zavisi od korienog Web servera. Najee se takvom servletu moe pristupiti pomou URL-a koji ima sledei oblik:
http://ime-hosta/servlet/HelloWorld
Poslednji segment URL-a predstavlja naziv servlet klase. Ovo se, kod veine Web servera, moe konfigurisati na odreeni nain. Upuivanjem Web itaa na ovakvu adresu dobie se rezultat kao na slici 8.5.
103
/** Servlet koji ispisuje sadraj zaglavlja zahteva koje je * primio od browsera. */ public class DisplayHeader extends HttpServlet { // Obrada GET zahteva public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=utf-8"); PrintWriter out = response.getWriter(); String[] headers = getHeaders(request); for (int i = 0; i < headers.length; i++) { out.println(headers[i]); out.println("<BR>"); } } private String[] getHeaders(HttpServletRequest request) { Vector temp = new Vector(); Enumeration enum = request.getHeaderNames(); while (enum.hasMoreElements()) { String headerName = (String)enum.nextElement(); String headerValue = request.getHeader(headerName); temp.addElement(headerName + ": " + headerValue); } String[] retVal = new String[temp.size()]; temp.copyInto(retVal); return retVal; } }
Metoda getHeaders je namenjena da na osnovu datog request objekta formira niz stringova iji je sadraj [naziv zaglavlja: vrednost]. Metoda getHeaderNames objekta request je vraa listu naziva zaglavlja koje se nalaze u okviru zahteva. Za svaki takav naziv zaglavlja moe se dobiti sadraj pozivom metode getHeader. Rezultujua HTML stranica sadri redove teksta koji prikazuju sadraje zaglavlja zahteva.
104
Server e, tipino, kreirati posebnu programsku nit za obradu svakog pojedinog zahteva klijenta. (Ovakva struktura servera je opisana u poglavlju 4). U okviru tih niti e se pozivati metoda doGet unapred inicijalizovanog servlet objekta. To znai da se moe desiti da se metoda doGet pozove nad jednim istim servlet objektom istovremeno. Prilikom pisanja servleta to se mora imati u vidu tako da se obezbedi sinhronizovani pristup resursima koji to zahtevaju. Ovaj problem ilustruje sledei primer. Servlet klasa AccessTest sadri atribut hitCount koji sadri broj pristupa datom servletu. Brojanje pristupa se obavlja inkrementiranjem ovog brojaa u okviru metode doGet. Samo inkrementiranje mora biti sinhronizovana operacija, u ovom primeru definisana metodom inc. Sinhronizacija se obavlja nad samim servlet objektom, pozivom metode inc. Svi zahtevi klijenata bie upuivani na isti servlet objekat, pa e se samim tim koristiti i isti atribut koji slui kao broja. Konaan efekat je da e svaki korisnik u stranici koju je traio dobiti izvetaj o ukupnom broju pristupa toj stranici.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; /** Servlet koji demonstrira sinhronizovani pristup * deljenim resursima. */ public class AccessTest extends HttpServlet { // Obrada GET zahteva public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<HTML><BODY>"); out.println("Ovoj stranici je pristupano "+inc()+" puta."); out.println("</BODY></HTML>"); } private synchronized int inc() { return ++hitCount; } private int hitCount; }
105
Princip rada cookie mehanizma prikazan je na slici 8.6. Prilikom slanja prvog zahteva server e ustanoviti da mu klijent nije poslao cookie kao jednu stavku u zaglavlju zahteva. U odgovor na taj zahtev server e dodati cookie. Ukoliko je Web ita podeen tako da radi sa cookie-ima, on e u svim sledeim zahtevima koje bude slao tom serveru ukljuiti i cookie, tako da e server moi da prepozna klijenta koga je ve ranije opsluivao. ta je jedan cookie zapravo? Moemo ga shvatiti kao string od tipino 20-30 nerazumljivih znakova koji je namenjen za jednoznano identifikovanje korisnika na serveru.
1) zahtev HTTP klijent a) 2) odgovor + cookie HTTP server
Slika 8.6. a) slanje prvog zahteva i prijem odgovora koji ukljuuje cookie b) svi sledei zahtevi sadre cookie
Iako je mogue pristupiti cookie informacijama iz servleta, to nije potrebno initi radi praenja sesije korisnika. Za tu svrhu na raspolaganju su funkcije vieg nivoa koje e biti ilustrovane na sledeem primeru. Posmatrajmo servlet iji je zadatak da generie stranicu sa izvetajem o broju pristupa toj stranici od strane svakog korisnika. Dakle, potrebno je uoiti razliku izmeu vie korisnika, to primer u odeljku 8.6 nije inio. Realizacija je jednostavna kada se koristi klasa HttpSession, koja opisuje korisniku sesiju. Ona slui kao kontejner za objekte koji opisuju datog korisnika. U taj kontejner se mogu smestiti objekti pod nekim imenom, a kasnije se tim objektima moe pristupiti na osnovu tog imena. Za potrebe brojanja pristupa nekoj stranici koristiemo objekte klase SessionCounter.
class SessionCounter { public int getCount() { return count; } public void setCount(int c) { count = c; } public void inc() { count++; } private int count = 0; }
106
public class SessionTest extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // pokupimo session objekat HttpSession session = req.getSession(true); // probamo da pokupimo broja pristupa u ovoj sesiji SessionCounter sc = (SessionCounter)session.getValue("brojac"); res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<html><body>"); // ispii ID sesije out.println("<b>Sesija ID:" + session.getId() + "</b>"); // ako je getValue vratio null, onda je ovo prvi // pristup toj stranici, inae je u pitanju neki sledei if (sc != null) { sc.inc(); out.println(", ukupno pristupa:" + sc.getCount() + ".<br>"); } else { out.println(", prvi pristup.<br>"); sc = new SessionCounter(); sc.inc(); session.putValue("brojac", sc); } out.println("</body></html>");
} }
Posmatrajmo ta se deava prilikom prvog pristupa servletu. Prva stvar koja se uradi unutar doGet metode je preuzimanje HttpSession objekta. Preporuka je da se ovo radi kao prva stvar u okviru doGet metode, ak i pre poziva setContentType. Dobijeni objekat odgovara sesiji korisika iji zahtev upravo obraujemo za to se pobrinuo Web server pomou cookie mehanizma. Zatim se nad tim objektom poziva metoda getValue, to predstavlja pokuaj da se pristupi objektu koji je ranije smeten u kontejner pod nazivom brojac. U pitanju je objekat klase SessionCounter. Ukoliko poziv metode getValue ima za rezultat null, to znai da niko ranije nije ve smestio objekat u kontejner pod datim imenom. Drugim reima, u pitanju je prvi pristup servletu. U tom sluaju se kreira novi SessionCounter objekat, koji se smesti u kontejner pod imenom brojac. U suprotnom sluaju koristi se postojei SessionCounter objekat, izvri se njegovo inkrementiranje i u stranicu se ugradi izvetaj o broju pristupa. Slino ovom primeru, svi servleti kod kojih je potrebno pristupati informacijama o korisnikoj sesiji imali bi rukovanje HttpSession objektom kao to je ovde prikazano. Ista korisnika sesija bi delila isti kontejner za objekte u razliitim servletima.
107
a)
b)
Slika 8.7. a) stranica sa HTML formom b) stranica na koju se Web ita upuuje nakon klika na dugme Poalji
Prva stranica je statika HTML stranica, iji sadraj sledi (elementi forme su naglaeni, ostatak predstavlja formatiranje):
<HTML> <HEAD> <TITLE>Primer sa formom</TITLE> </HEAD> <BODY> <h1>Unesite podatke</h1> <form action="FormTest"> <table cellspacing=0 cellpadding=3 border=0> <tr> <td align=right>Ime:</td> <td><input type="text" name="ime"></td> </tr> <tr> <td align=right>Prezime:</td> <td><input type="text" name="prezime"></td> </tr> <tr> <td align=right> </td> <td><input type="submit" value=" Posalji "></td> </tr> </table> </form> </body> </html>
Atribut action taga form koji obuhvata elemente forme (polja za unos i dugme) navodi stranicu na koju e se prei kada korisnik klikne na dugme Poalji. U pitanju je naziv servleta koji e prikazati narednu stranicu. Zahtev koga e Web
108
ita poslati u tom trenutku imae ugraene i podatke iz forme. Podaci e biti navedeni u sledeem obliku:
ime=Branko&prezime=Milosavljevic
Dakle, vrednosti u pojedinim poljima se navode kao parovi ime=vrednost, gde je ime naziv polja, a vrednost sadraj koji je korisnik uneo. Vie ovakvih parova se razdvaja ampersand znakom (&). Moe se rei da stranica na koju se upuuje Web ita prilikom klika na dugme ima parametre; ima ih onoliko koliko ima polja u HTML formi prethodne stranice. 8.8.1 GET i POST zahtevi Postoji vie naina da se parametri, tj. sadraj popunjene forme, prenese novoj stranici. Ukoliko Web ita novu stranicu zahvata GET komandom, tada se parametri dodaju na kraj adrese nove stranice iza jednog upitnika. Sledei primer predstavlja zahtev itaa u tom sluaju.
GET /kurs/FormTest?ime=Branko&prezime=Milosavljevic HTTP/1.0 ...
Druga mogunost je da se parametri poalju kao deo zahteva koji sadri POST komandu:
POST /kurs/FormTest HTTP/1.0 ... ime=Branko&prezime=Milosavljevic
Razlika je, sa stanovita korisnika, vidljiva u adresi stranice koja se vidi u okviru itaa. Slika 8.8 prikazuje varijante kada se koristi GET i POST komanda.
a)
b) Slika 8.8. a) stranica dobijena GET zahtevom b) stranica dobijena POST zahtevom
109
Podaci uneti u formi su vidljivi u adresi stranice kada se koristi GET zahtev, to moe biti neprikladno u sluaju da su podaci poverljivi. Iz tog razloga se najee i koristi POST metod za prenos parametara. Mesto gde se navodi metod prenosa parametara je polazna HTML stranica. Tag form ima atribut method za tu namenu. Atribut moe imati vrednosti get i post, a ako se izostavi podrazumeva se vrednost get. Sledi primer:
<form action="FormTest" method="post">
Sadraj popunjenih polja forme se alje, kroz parametre, novoj stranici. Ta stranica moe biti statika HTML stranica, ali ona tada ne moe imati nikakve koristi od tih parametara; da bi se parametri na neki nain interpretirali potrebna je dinamika stranica koju moemo generisati odgovarajuim servletom. Stranica sa slike 8.7.b je upravo takva stranica koju je generisao sledei servlet.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class FormTest extends HttpServlet { // obrada POST zahteva public void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // preuzimamo vrednost parametra "ime" String ime = request.getParameter("ime"); // preuzimamo vrednost parametra "prezime" String prezime = request.getParameter("prezime"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<HTML><BODY><H1>Evo sta ste uneli:</H1>"); out.println("Ime: "+ime+"<BR>"); out.println("Prezime: "+prezime+"<BR>"); out.println("</BODY></HTML>"); out.close(); } // obrada GET zahteva public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
Vidimo da se parametrima stranice moe pristupiti pomou metode getParameter objekta request. Vrednosti tih parametara mogu se koristiti kasnije
110
na proizvoljan nain. Ovde su upotrebljene tako da su ugraene u tekst rezultujue HTML stranice; na primer, mogue je smestiti ove podatke u tekuu sesiju pomou HttpSession objekta, koji je opisan u odeljku 8.7. Ovaj servlet redefinie metode doGet i doPost, kako bi mogao da odgovori na obe vrste zahteva. Metoda doGet samo poziva metodu doPost, ime se postie da servlet jednako reaguje na POST i GET zahteve klijenta.
public class DBServlet extends HttpServlet { /** Inicijalizacija servleta: otvaranje veze sa bazom */ public void init(ServletConfig config) throws ServletException { super.init(config); try { Class.forName("oracle.jdbc.driver.OracleDriver"); conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta"); } catch (Exception ex) { ex.printStackTrace(); } } /** Unitavanje servleta: zatvaranje veze sa bazom */ public void destroy() { try { conn.close(); } catch (SQLException ex) { ex.printStackTrace(); } } public void doGet(HttpServletRequest req,
111
HttpServletResponse res) throws ServletException, IOException { try { // postavljanje upita String query = "SELECT ime, prezime FROM nastavnici"; Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery(query); Vector imena = new Vector(); Vector prezimena = new Vector(); while (rset.next()) { imena.addElement(rset.getString(1)); prezimena.addElement(rset.getString(2)); } rset.close(); stmt.close(); // generisanje HTML stranice res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<html><body>"); out.println("<h3>Nastavnici u bazi:</h3>"); out.println("<pre>"); for (int i = 0; i < imena.size(); i++) out.println( (String)(imena.elementAt(i)) + " " + (String)(prezimena.elementAt(i))); out.println("</pre></body></html>"); out.flush(); } catch (Exception ex) { // generisanje HTML stranice sa opisom greke res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<html><body><pre>"); out.println("Dogodila se greska:<br> " + ex.toString() + "</pre></body></html>"); out.flush(); } } /** Konekcija sa bazom podataka */ private Connection conn;
Ovaj servlet ima redefinisanu metodu init, u okviru koje se uitava JDBC drajver i otvara konekcija sa bazom podataka. Ova konekcija je atribut klase, tako da je dostupna iz svih metoda, ukljuujui i doGet. U okviru metode destroy konekcija se zatvara. Ovakav koncept znai da e jedan servlet koristiti tano jednu konekciju za sve zahteve. To moe biti reenje koje je zgodno naroito kada se softver za upravljanje bazom podataka licencira po aktivnoj konekciji u ovom sluaju neogranien broj korisnika Web sajta sa ovim servletom imao bi
112
pristup bazi podataka preko samo jedne konekcije. Meutim, treba imati u vidu da su mogue situacije kada se ista konekcija koristi za operacije nad bazom podataka iz vie programskih niti. Neki JDBC drajveri, kao to je Oracle, ne doputaju izvravanje DML operacija iz vie niti istovremeno preko iste konekcije, tako da ovo reenje ima smisla samo kada se postavljaju upiti (oni se mogu paralelno izvravati iz vie niti). Alternativa ovom reenju je da se prilikom svake obrade GET zahteva otvara nova konekcija, koja se odmah zatim i zatvara. Ova varijanta je izuzetno neefikasna, jer je otvaranje konekcije sa bazom podataka dugotrajna operacija. Jo jedno reenje ovog problema bie prikazano u okviru poglavlja 11.
113
Poglavlje 9
Na mestu gde se nalazi specijalni tag <%=username%> bie ugraeno korisniko ime tekue prijavljenog korisnika, na primer. Realizacija JSP tehnologije je posebno zanimljiva: ovako napisana JSP stranica e posluiti da se na osnovu nje generie servlet koji e proizvesti upravo onakvu HTML stranicu kako je to specificirano u JSP stranici. Takav servlet se
114
generie kao klasina datoteka sa izvornim Java kodom. Nakon generisanja servleta, vri se prevoenje servlet klase u Java byte-code. Tako prevedeni servlet se moe koristiti na poznati nain da proizvede rezultujuu HTML stranicu. Sekvenca dogaaja je, tanije, sledea: autor JSP stranice smeta datu stranicu u odgovarajui direktorijum Web servera. Kada neki Web klijent prvi put zatrai datu stranicu, Web server e na osnovu nje automatski generisati odgovarajui servlet, prevesti ga i zatim pokrenuti. Prilikom narednih zahteva za tom JSP stranicom bie dovoljno samo pozvati odgovarajuu metodu servleta. Da li e izvorni kod generisanog servleta biti dostupan ili ne zavisi od Web servera.
Izraz new java.util.Date() predstavlja kreiranje novog objekta klase Date. Takav objekat se inicijalizuje na tekui datum i vreme u trenutku kreiranja. Kako je u pitanju izraz koji nije tipa String, prilikom ugradnje ove vrednosti u stranicu bie pozvana metoda toString klase Date koja e obezbediti odgovarajuu string reprezentaciju ovog objekta. Konaan rezultat je stranica koja e prilikom svakog pristupa prikazati datum i vreme kada je pristup izvren. 9.2.2 Skriptleti JSP skriptleti su elementi koji se pojavljuju izmeu znakova <% i %>. Njihov sadraj je proizvoljan segment Java programskog koda. Ovako ugraen programski kod direktno se ugrauje u kod generisanog servleta na odgovarajue mesto! Sledi primer jedne JSP stranice:
<html> ... <% if (Math.random() < 0.5) { %> Dobar dan! <% } else { %>
115
Iz primera se vidi da segment Java koda koji je sadraj skiptleta ne mora predstavljati validan Java iskaz, ve samo njegov fragment. Evo kako bi se ovakva JSP stranica prevela u servlet (ugraeni fragmenti su naglaeni):
class GenerisaniServlet extends HttpServlet { public void doGet(...) { ... out.println("<html>"); ... if (Math.random() < 0.5) { out.println("Dobar dan!"); } else { out.println("Dobro vee!"); } ... out.println("</html>"); } }
Moe se rei da se svaki red JSP stranice koji sadri samo klasine HTML oznake konvertuje u jedan out.println(...) iskaz u servletu. JSP skriptleti se ugrauju u servlet na odgovarajuem mestu direktnim kopiranjem datog teksta. Jedna od posledica ovakvog tretiranja skriptleta je i da opseg vaenja promenljivih definisanih u skriptletima moe da se protee kroz vie skriptleta. Sledi primer koji ilustruje ovu osobinu:
<table border=1> <% String names[] = { "Bata", "Pera", "Mika", "Laza", "Sima" }; for (int i = 0; i < names.length; i++) { %> <tr> <td><%= i %></td> <td><%= names[i] %></td> </tr> <% } %> </table>
Gornji primer proizvodi HTML tabelu koja ima onoliko redova koliko je elemenata niza names. Promenljiva names i broja petlje i dostupni su i u narednim skriptletima. Generisani HTML kod koga e primiti Web ita izgledao bi ovako:
<table border=1> <tr> <td>0</td> <td>Bata</td> </tr> <tr> <td>1</td>
116
<td>Pera</td> </tr> <tr> <td>2</td> <td>Mika</td> </tr> <tr> <td>3</td> <td>Laza</td> </tr> <tr> <td>4</td> <td>Sima</td> </tr> </table>
9.2.3 Deklaracije JSP deklaracije su elementi koji se pojavljuju izmeu znakova <%! i %>. Deklaracije sadre tekst koji e u rezultujui servlet biti smeten na mestu atributa ili metode servlet klase. Na primer, deklaracija
<%! int hitCount = 0; %>
Ovakav atribut servlet klase je dostupan u viestrukim pozivima doGet ili doPost metode servleta. Primer u odeljku 8.6 ilustrovao je upotrebu ovakvog atributa. Ovde navodimo primer koji slian efekat postie u JSP okruenju.
<%! int hitCount = 0; %> <html> <body> <h3>Primer JSP deklaracije</h3> Ovoj stranici je ukupno pristupano <%= ++hitCount %> puta. </body> </html>
Ovaj primer ne koristi sinhronizovan pristup atributu hitCount zbog jednostavnosti. Metoda inc primera iz odeljka 8.6 se takoe moe dodati kao JSP deklaracija. Poloaj JSP deklaracije u okviru JSP stranice nije bitan. 9.2.4 Direktive JSP direktive su elementi smeteni izmeu znakova <%@ i %>. Namenjene su za obavljanje nekih specifinih zadataka u okviru JSP stranice. Ovde emo navesti neke primere.
<%@ page import="java.util.*" %>
117
Dodavanje odgovarajue import deklaracije na poetak generisanog servleta. Nakon ovakve direktive ne mora se vie pisati, na primer, java.util.Date nego samo Date za naziv klase.
<%@ page contentType="text/plain" %>
Ukljuuje kompletan sadraj stranice included.jsp u tekuu stranicu. Ukljuena stranica moe biti JSP stranica, pri emu e se ona posebno interpretirati i takav sadaj biti ukljuen u tekuu stranicu. Moe biti i statika HTML stranica.
Najee se koristi request promenljiva koju smo esto sretali i u prethodnom poglavlju. Sledi jedan primer upotrebe ove promenljive:
<html> ... <% if (request.getParameter("username") != null) { %> Vrednost parametra: <%= request.getParameter("username") %> <% } %> ... </html>
U primeru se pristupa parametrima stranice na isti nain kako se to ini i unutar klasinog servleta.
118
Forma sadri dva polja iji su nazivi, recimo, username i password. Sadraj ovih polja bie prenet kroz parametre novoj stranici nakon klika na dugme Poalji. U okviru te nove stranice moemo smestiti ove podatke u sledeu JavaBean komponentu:
public class User { public void setUsername(String x) { username = x; } public void setPassword(String x) { password = x; } public String getUsername() { return username; } public String getPassword() { return password; } private String username; private String password; }
Evo kako bi mogla da izgleda JSP stranica na koju se prelazi klikom na dugme Poalji:
<jsp:useBean id="user" class="somepackage.User"/> <jsp:setProperty name="user" property="username" param="username"/> <jsp:setProperty name="user" property="password" param="password"/> <html> ... </html>
Prvi specijalni tag, jsp:useBean, deklarie objekat sa nazivom user klase somepackage.User. Sledei specijalni tag, jsp:setProperty, postavlja vrednost svojstva username objekta user na vrednost parametra stranice koji se zove username. Trei specijalni tag radi istu stvar za svojstvo password. Objekat user je dostupan u ostatku stranice (slino kao da je u pitanju JSP deklaracija). Na primer:
<html> ... <% if (user.login()) { %> Uspeno ste se prijavili! <% } else { %> Niste se uspeno prijavili!
119
Primer pretpostavlja da klasa User ima definisanu metodu login koja vraa logiku vrednost. Metoda login, na primer, moe da proveri u bazi podataka da li korisnik sa datim imenom i lozinkom postoji. Na osnovu takve provere u stranici moe da se ispie odgovarajui tekst.
Vrednost session atributa scope znai da je komponenta user vezana za sesiju korisnika. Tada ovakvu deklaraciju moemo da postavimo u svaku stranicu gde se koristi komponenta user. Prilikom izvravanja stranice izvrie se provera da li postoji komponenta user vezana za tekuu sesiju korisnika. Ako ne postoji, kreirae se nova instanca klase i vezati za sesiju. U suprotnom koristie se postojea instanca. Ovakvo ponaanje je opisano i u odeljku 8.7, gde je u primeru koriena klasa SessionCounter na isti nain kako se ovde koristi komponenta user. Tabela 9.2 navodi mogue vrednosti atributa scope.
Opis istu instancu dele svi korisnici sajta svaki korisnik sajta ima svoju instancu svaki zahtev za stranicom ima svoju instancu svaka stranica ima svoju instancu, bez obzira na korisnike koji joj pristupaju Tabela 9.2. Mogue vrednosti atributa scope taga jsp:useBean.
esta upotreba session-scoped komponenti je upravo za uvanje informacija o korisniku kome pripada tekua sesija. Na primer, instanca klase User iz prethodnog primera se mogla definisati kao session-scoped komponenta. Tada se u vrhu svake JSP stranice moe nai sledei segment:
<jsp:useBean id="user" class="somepackage.User" scope="session"/> <% if (!user.isLoggedIn()) response.sendRedirect(response.encodeURL("login.jsp")); %>
Prethodni segment e, u sluaju da je korisnik pokuao da pristupi stranici bez prethodnog prijavljivanja (to se proverava pomou metode isLoggedIn), Web itau biti vraena poruka da se preusmeri na stranicu login.jsp. U suprotnom stranica e biti regularno prikazana.
120
Da bi se dodatni tagovi mogli koristiti, moraju se ukljuiti u stranicu odgovarajuom taglib direktivom. Atribut uri ove direktive je jedinstveni identifikator biblioteke koji se biblioteci dodeljuje prilikom njenog instaliranja (taj postupak zavisi od Web servera koji se koristi). Atribut prefix navodi prefiks kojim e tagovi iz te biblioteke poinjati u okviru tekue stranice u primeru je to string mytags. Tag if predstavlja okvir celokupne konstrukcije. On obuhvata tri taga: condition, then i else. Sadraj condition taga je Java logiki izraz. Sadraj then taga e biti ukljuen u stranicu ako je vrednost datog logikog izraza true. U suprotnom e u stranicu biti ukljuen sadraj taga else. Drugi primer korienja dodatnih tagova sadri tagove koji omoguavaju postavljanje upita u bazi podataka i preuzimanje rezultata. Ispis rezultata se moe formatirati na proizvoljan nain.
<%@ taglib uri="sqltags" prefix="sql" %> <sql:SQLQuery sqlQuery="SELECT ime, prezime FROM nastavnici"> <sql:empty> Nema nastavnika u bazi! </sql:empty> <sql:outputStart> <table border=1> </sql:outputStart> <sql:output> <% java.util.Hashtable row = (java.util.Hashtable) pageContext.getAttribute("resultRow"); %> <tr> <td><%= row.get("ime")%>"></td> <td><%= row.get("category_name") %></td> </tr> </sql:output> <sql:outputEnd> </table> </sql:outputEnd> </sql:SQLQuery>
121
Biblioteka u prethodnom primeru ima identifikator sqltags, a njeni tagovi u ovoj stranici bie korieni sa prefiksom sql. Tag SQLQuery obuhvata celu konstrukciju. Njegov atribut sqlQuery sadri SQL naredbu kojom se postavlja upit. Ovaj tag sadri podtagove empty, outputStart, outputEnd i output. Sadraj taga empty bie ukljuen u stranicu u sluaju da rezultat upita ne sadri nijedan red. Sadraj taga outputStart bie ukljuen u stranicu ako rezultat sadri bar jedan red, i to pre svih ostalih tagova. Analogno njemu, outputEnd bie ukljuen nakon svih ostalih tagova. Tag output bie ukljuen onoliko puta koliko ima redova u rezultatu upita. U okviru njega moe se pristupiti parametru resultRow (poziv pageContext.getAttribute) koji sadri tekui red rezultata upita. U pitanju je objekat klase Hashtable koji sadri mapiranje [naziv kolone rezultata, njena vrednost]. Pisanje biblioteka dodatnih tagova je, u principu, najkomplikovaniji posao vezan za razvoj JSP stranica. Dodatni tagovi se definiu pomou odgovarajuih Java klasa. Prilog na kraju praktikuma sadri programski kod klasa koje implementiraju ovde prikazane tagove. Za detalje o pisanju novih JSP tagova treba konsultovati referentnu literaturu.
122
Poglavlje 10
3) rezultat
Ovakva komunikacija moe se posmatrati kao komunikacija dva objekta: klijentski objekat je deo klijentske aplikacije, a serverski objekat je deo serverske aplikacije. Serverska aplikacija se esto naziva kontejner za objekte jer je njena osnovna funkcija da obezbedi mrene i druge servise koji su neophodni za ovakav serverski objekat. Slika 10.2 prikazuje odnos klijentskog i serverskog objekta.
123
Posmatrano sa stanovita autora klijentske aplikacije, i klijentski i serverski objekat su sastavni deo jednog programa. Osobina tog programa je da se izvrava na vie raunara u mrei. Serverski program, logiki posmatrano, i ne postoji; postoji samo serverski objekat koji se ponaa/koristi kao da je deo klijentskog (jedinog) programa.
10.2 RMI
Remote Method Invocation (RMI) je osnovna Java tehnologija za pisanje programa sa distribuiranim objektima. RMI polazi od toga da klijentski objekat ne vidi serverski objekat, nego samo interfejs koga ovaj implementira (slika 10.3). Ovaj interfejs se esto naziva RMI interfejs.
klijent objekat interfejs server objekat
Komunikacija izmeu klijentskog i serverskog programa odvija se po JRMP (Java Remote Method Protocol) protokolu. Za implementaciju ovog protokola zaduene su posebne, automatski generisane, klase: tzv. stub na klijentu i skeleton na serveru. Slika 10.4 prikazuje stvarne uesnike u RMI komunikaciji.
klijent objekat
RMI interfejs
client stub
RMI objekat
JVM 1
Klijent objekat pristupa serverskom preko RMI interfejsa. Sa strane klijenta, taj interfejs implementira stub klasa. Osnovni zadatak te klase je da pozive metoda serverskog objekta koje upuuje klijentski objekat prosledi preko mree. Na serverskoj strani skeleton klasa ima ekvivalentnu funkciju: da pozive pristigle preko mree pretvori u pozive stvarnog serverskog objekta. Stub i skeleton klase se generiu posebnim alatom iz JDK paketa, rmic kompajlerom. 10.2.1 Faze u pisanju RMI programa Pisanje RMI programa se moe podeliti u nekoliko faza: 1. pisanje RMI interfejsa 2. pisanje RMI serverskog objekta 3. pisanje RMI klijenta
124
4. generisanje stub i skeleton klasa Slika 10.5 prikazuje meusobni odnos pojedinih faza u pisanju RMI programa.
Definisanje RMI interfejsa
(.java) Implementiranje RMI interfejsa - definisanje RMI klase (.java) Implementiranje klijentske klase (.java) javac (.class) klijent (.class) RMI interfejs (.class) (.class) client stub (.class) rmic (.class) server skeleton (.class) RMI objekat (.class) javac (.class)
10.2.2 RMI interfejs Interfejs preko koga se pristupa serverskom objektu je Java interfejs koji ispunjava dva uslova: nasleuje interfejs java.rmi.Remote sve njegove metode mogu da izazovu izuzetak java.rmi.RemoteException
Iako se serverskom objektu pristupa na isti nain kao da se nalazi na istom raunaru, postoji jedna vrlo vana razlika izmeu pozivanja metoda lokalnih i RMI objekata: kod lokalnih objekata parametri metoda koji su objekti (a ne primitivni tipovi) se ponaaju kao da se prenose po adresi, dok se kod poziva metoda RMI objekata svi parametri prenose po vrednosti. U prethodnom primeru to znai i da se parametar x prenosi po vrednosti. Takoe se i rezultat metode prenosi po vrednosti. Da bi se objekat-parametar mogao preneti po vrednosti, mora postojati nain da se njegov sadraj prosledi serverskom programu. U pitanju je postupak serijalizacije objekta: na osnovu njegovog sadraja generie se stream bajtova koji se moe preneti preko mree i tamo ponovo restaurirati u identinu kopiju objekta. Slika 10.6 prikazuje proces serijalizacije i deserijalizacije objekta.
125
objekat
serijalizacija JVM 1
byte stream
byte stream
deserijalizacija JVM 2
kopija objekta
Da bi objekat mogao biti serijalizovan, njegova klasa mora da implementira interfejs java.io.Serializable. Taj interfejs ne definie nijednu metodu! Sam proces serijalizacije e, u ovom sluaju, obaviti stub klasa. Proces deserijalizacije e, analogno tome, obaviti skeleton klasa. Klasa SomeClass iz prethodnog primera bi morala biti definisana kao:
public class SomeClass implements java.io.Serializable { ... }
10.2.3 RMI serverski objekat RMI serverski objekat je instanca klase koja mora da zadovolji sledee uslove: implementira svoj RMI interfejs nasleuje java.rmi.UnicastRemoteObject
Sledi primer klase koja predstavlja implementaciju RMI interfejsa prikazanog u prethodnom odeljku.
public class ServerImpl extends java.rmi.UnicastRemoteObject implements Server { ... }
10.2.4 RMI registry Zadatak serverskog objekta je da implementira metode svog RMI interfejsa. Meutim, da bi se serverski objekat mogao koristiti, potrebno ga je registrovati pod nekim imenom kod servera koji slui kao katalog ovakvih objekata. Klijenti koji ele da pristupaju nekom serverskom objektu e od ovog servera zahtevati referencu na odgovarajui objekat putem njegovog imena pod kojim je registrovan. Serverski program koji obezbeuje usluge registrovanja serverskih objekata naziva se RMI registry. Ovaj program mora biti aktivan na nekom raunaru da bi pristup serverskom objektu bio mogu. Program se pokree komandom
rmiregistry
sa opcionim parametrom koji predstavlja TCP port na kome e oekivati klijente. Podrazumevani port koji se koristi je 1099. Zadatak serverskog objekta je da se registruje na odgovarajui nain kod RMI registry-ja. To se tipino obavi unutar metode main serverskog objekta. Sledi primer takve metode za klasu ServerImpl iz prethodnog primera:
public static void main(String[] args) {
126
U metodi main kreira se jedna instanca serverskog objekta, i ona se registruje pod imenom ServerskiObjekat kod RMI registry-ja koji se nalazi na adresi branko. tmd.ns.ac.yu i slua na portu 1099. RMI registry se moe pokrenuti, umesto iz komandne linije, i iz Java programa, sledeim iskazom:
LocateRegistry.createRegistry(1099);
Parametar metode createRegistry je port na kome e sluati server. 10.2.5 RMI klijent RMI klijent, da bi pristupio serverskom objektu mora da uradi sledee dve pripremne radnje: 1. postavi novi security manager za tekui Java program:
System.setSecurityManager(new RMISecurityManager());
Sada se preko reference server moe obraati serverskom objektu na uobiajen nain: server.metoda(...). 10.2.6 Primer RMI programa Ovaj odeljak sadri primer jednog RMI programa koji se sastoji od jednog serverskog i jednog klijentskog objekta. Serverski objekat nudi samo jednu metodu, count, koja broji svoje pozive i tekui broj vraa kao rezultat. Klijent, nakon to dobije referencu na serverski objekat, poziva metodu count i ispisuje rezultat.
ServerI.java import java.rmi.*; interface ServerI extends Remote { int count() throws RemoteException; } Server.java import import import import java.rmi.*; java.rmi.registry.*; java.rmi.server.*; java.net.*;
127
public Server() throws RemoteException { } public int count() throws RemoteException { return ++count; } public static void main(String[] args) { System.setSecurityManager(new RMISecurityManager()); try { Server server = new Server(); LocateRegistry.createRegistry(1099); Naming.rebind( "//branko.tmd.ns.ac.yu:1099/Server", server); System.out.println("Server started."); } catch (Exception ex) { ex.printStackTrace(); } } private int count = 0; } Client.java import java.rmi.*; import java.rmi.registry.*; import java.net.*; public class Client { public static void main(String[] args) { System.setSecurityManager(new RMISecurityManager()); try { System.out.println("Connecting to server..."); ServerI server = (ServerI)Naming.lookup( "//branko.tmd.ns.ac.yu:1099/Server"); System.out.println("Count: "+server.count()); } catch (Exception ex) { ex.printStackTrace(); } } }
Ovaj primer se pokree na sledei nain: 1. prevedu se svi izvorni Java fajlovi:
javac *.java rmic Server
2. generiu se stub i skeleton klase: 3. startuje se RMI registry: start rmiregistry (Win32) rmiregistry & (Unix)
128
4. startuje se server:
java Server java Client
5. startuje se klijent:
10.2.7 RMI i multithreading Pokretanjem klijenta iz prethodnog vie puta vidi se da e vie klijenata deliti istu instancu serverskog objekta (svaki novi klijent e ispisati novu inkrementiranu vrednost rezultata). Ovo ponaanje postaje jasnije kada se podsetimo da se, prilikom registracije serverskog objekta, pod jednim imenom registruje jedna instanca serverske klase. Nakon restartovanja servera svi pozivi klijenata e rezultovati vrednostima brojaa koje ponovo poinju od 1. U pitanju je nova instanca serverskog objekta koja se registrovala pod starim imenom. Kako svi klijenti dele istu instancu serverskog objekta (kada zatrae objekat registrovan pod istim imenom), istovremeni pozivi metoda serverskog objekta bie, u okviru serverskog programa, obraeni kroz vie niti, po jedna za svaki takav poziv metode. Pozivanje metoda serverskog objekta iz vie niti istovremeno obavezuje propisnu sinhronizaciju procesa tamo gde je to neophodno.
10.3 CORBA
10.3.1 Osnovne odrednice Common Object Request Broker Architecture (CORBA) predstavlja arhitekturu i tehnologiju distribuiranih objekata koja je zamiljena tako da bude nezavisna od korienog programskog jezika (mogua je komunikacija izmeu objekata pisanih u razliitim jezicima). Pored toga, CORBA je nezavisna i od konkretnog proizvoaa softvera: ona zapravo predstavlja standard OMG (Object Management Group) grupe. Proizvoai softvera mogu da nude svoje implementacije zajednikog standarda. Ujedno je i najsloenija tehnologija koja se bavi distribuiranim objektima, jer podrava najiri spektar zahteva u okviru ovog domena. Neto manja dobra osobina CORBA je to je u pitanju standard, koji se sporo usvaja. Zbog sloenosti i velikog broja mogunosti relativno je komplikovana za uenje. U ranijim verzijama CORBA standarda mogua je bila i nekompatibilnost izmeu proizvoda razliitih proizvoaa. Polazna ideja je identina kao i kod RMI tehnologije: klijentski objekat poziva metode serverskog objekta na isti nain kao da je u pitanju lokalni objekat. (U CORBA terminologiji serverski objekat se naziva servant). Klijent i servant meusobno ne komuniciraju direktno, ve preko posrednika nazvanih ORB (Object Request Broker). ORB je softverski modul koji je namenjen za mrenu komunikaciju. ORB-ovi izmeu sebe komuniciraju po IIOP (Internet
129
Inter-ORB Protocol). Slika 10.7 prikazuje komunikaciju izmeu klijenta i servanta posredstvom ORB-ova.
klijent servant
ORB
IIOP
ORB
10.3.2 IDL Kako CORBA omoguava korienje objekata pisanih u razliitim programskim jezicima, mora postojati univerzalan nain za specificiranje interfejsa serverskih objekata kojima klijenti mogu da pristupe. Za tu namenu CORBA definie svoj Interface Definition Language (IDL) jezik. Na osnovu specifikacije interfejsa u IDL jeziku odgovarajuim alatom se generie implementacija servanta u konkretnom programskom jeziku. IDL mapiranja su definisana za mnoge jezike (Java, C++, Smalltalk, COBOL, itd) i predstavljaju deo OMG standarda. Mapiranje IDL-a na programski jezik Java je relativno jednostavno: koncept IDL modula se mapira na Java paket; IDL interfejs se mapira na Java interfejs; slino vai i za metode i atribute. Sledi primer jednog IDL interfejsa i odgovarajueg Java interfejsa:
module counter { interface Count { attribute long sum; long increment(); }; }; package counter; interface Count { int sum (); void sum (int newSum); int increment (); }
Generisanje Java interfejsa na osnovu IDL interfejsa obavlja idlj, alat iz JDK paketa. Rezultat rada idlj alata nije samo Java interfejs, nego i stub i skeleton klase. Ove klase koriste ORB za komunikaciju sa objektom sa druge strane. Slika 10.8 prikazuje meusobni odnos klijent, servant, stub, skeleton i ORB modula.
klijent servant
client stub
server skeleton
ORB
IIOP
ORB
130
10.3.3 CORBA Naming Service CORBA Naming Service je poseban CORBA servis koga definie standard. Namenjen je za katalogiziranje servant objekata dakle, ima funkciju slinu RMI registry-ju. Registrovani objekti su organizovani u stablo, nalik direktorijumima i datotekama. U tom smislu, ovaj servis je nalik DNS servisu u TCP/IP mreama. JDK paket sadi poseban program koji implementira ovaj servis (tnameserv). CORBA klijenti koriste ovaj servis za pronalaenje servant objekata. Program tnameserv se pokree kao u sledeem primeru:
tnameserv ORBInitialPort 900
Parametrom ORBInitialPort definie se port na kome e ovaj server oekivati klijente. Za pristup ovom servisu iz programskog jezika Java koristi se JNDI (Java Naming and Directory Interface) biblioteka, koja je sastavni deo standardne biblioteke od Java verzije 1.3 (u ranijim verzijama Jave ovo nije bilo standardizovano). JNDI je generika biblioteka za pristup razliitim servisima ove vrste. U okviru standardne biblioteke nalaze se JNDI moduli za pristup LDAP serverima, RMI registry-ju i CORBA Naming Service serverima. 10.3.4 Proces pisanja CORBA programa Pisanje CORBA programa moe se podeliti u nekoliko faza (slino kao kod RMI tehnologije): 1. 2. 3. 4. 5. pisanje IDL interfejsa generisanje Java koda (idlj) implementiranje servera implementiranje servanta implementiranje klijenta
Ovu proceduru emo prikazati na primeru. Definiimo prvo IDL interfejs (naziv fajla u kome se nalazi definicija nije vaan, ekstenzija je obino .idl):
counter.idl module counter { interface Count { attribute long sum; long increment(); }; };
Interfejs sadri jedan atribut, sum, i jednu metodu, increment. Sada treba generisati odgovarajui Java kod za dati IDL interfejs. Komanda kojom se to postie glasi:
idlj fall counter.idl
Parametar fall naglaava da se generiu klase i za klijent i za server. Rezultat izvravanja je skup od est Java izvornih fajlova koji se nalaze u paketu counter (zato to IDL specifikacija navodi modul counter). Njihova namena je sledea:
131
Count.java: predstavlja Java verziju datog interfejsa. Koristi se u okviru klijent aplikacije za pristup objektu. CountOperations.java: interfejs koji sadri samo operacije navedene u IDL interfejsu. Njega nasleuje Count interfejs i dodaje mu CORBA mogunosti. _CountStub.java: klijentski stub koji implementira Count interfejs i definie njegove CORBA funkcije. CountHelper.java: implementira dodatne funkcije, pre svega metodu narrow koja se koristi umesto klasinog kastovanja. CountHolder.java: obezbeuje manipulaciju parametrima metoda, u sluajevima za out i inout argumente. _CountImplBase.java: Klasa koju treba da nasledi servant klasa. Implementira CORBA funkcionalnost koju autor servanta ne mora da poznaje.
Slika 10.9 prikazuje proces generisanja ovih datoteka, kao i da li datoteka pripada klijentu ili serveru.
counter.idl
idlj
_Count _stub.java
Count Holder.java
Count.java
server
132
CounterServer.java import org.omg.CORBA.*; import org.omg.CosNaming.*; import counter.*; public class CounterServer { public static void main(String[] args) { try { // inicijalizuj ORB, prosledi mu command-line parametre ORB orb = ORB.init(args, null); // Kreiraj servanta i registruj ga kod ORB-a CounterServant countRef = new CounterServant(); orb.connect(countRef); // pokupi osnovni Naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); // umesto klasinog kastovanja, ovde se poziva narrow NamingContext ncRef = NamingContextHelper.narrow(objRef); // Registruj servant objekat kod naming servisa NameComponent nc = new NameComponent("Counter", ""); NameComponent path[] = {nc}; ncRef.rebind(path, countRef); // ekaj na pozive klijenata; program je ovde // blokiran beskonano dugo System.out.println("Waiting for clients"); java.lang.Object sync = new java.lang.Object(); synchronized(sync){ sync.wait(); } } catch (Exception ex) { ex.printStackTrace(); } } }
CounterClient.java import org.omg.CORBA.*; import org.omg.CosNaming.*; import counter.*; public class CounterClient { public static void main(String[] args) { try { // inicijalizuj ORB, prosledi mu command-line parametre ORB orb = ORB.init(args, null); // pokupi osnovni Naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); // umesto klasinog kastovanja, ovde se poziva narrow NamingContext ncRef = NamingContextHelper.narrow(objRef);
133
// definii name component NameComponent nc = new NameComponent("Counter", ""); NameComponent path[] = {nc}; // pronai objekat pod nazivom "counter" i // kastuj ga pomou narrow() Count countRef = CountHelper.narrow(ncRef.resolve(path)); // koristi CORBA servant objekat countRef.increment(); countRef.increment(); System.out.println("Sum value: " + countRef.sum()); } catch (Exception ex) { ex.printStackTrace(); } } }
Parametri koji se navode u komandnoj liniji se zapravo prosleuju ORB-u prilikom njegove inicijalizacije. Parametri predstavljaju adresu raunara i port na kome se izvrava Naming Service. 10.3.5 CORBA izuzeci IDL interfejs moe da sadri i definicije izuzetaka koje neke od metoda interfejsa mogu da izazovu. Sledi primer jednog takvog interfejsa.
module reader { interface Reader { exception badRead{}; string read(in string location) raises (badRead); }; };
Interfejs Reader definie novi tip izuzetka badRead. Metoda read ovog interfejsa moe da izazove taj izuzetak. IDL izuzetak badRead je predstavljen klasom reader.ReaderPackage.badRead. Klase iz ovog paketa su takoe dobijene upotrebom idlj alata. Slika 10.10 prikazuje lokaciju dodatnik klasa vezanih za izuzetak badRead.
134
Klasa ReaderServant predstavlja servanta za dati interfejs. U primeru vidimo da se izuzetak badRead koristi kao i svaki drugi Java izuzetak.
import reader.*; public class ReaderServant extends _ReaderImplBase { public String read(String location) throws reader.ReaderPackage.badRead { if (Math.random() > 0.5) return "Read from location: " + location; else throw new reader.ReaderPackage.badRead(); } }
Klasa ReaderServer ima potpuno istu funkcionalnost kao i klasa CounterServer iz prethodnog primera, pa je ovde neemo navoditi. Klasa ReaderClient pristupa servant objektu na isti nain kao to to ini klasa CounterClient iz prethodnog primera, uz razliku to pozivi metode read interfejsa Reader mogu da izazovu izuzetak badRead, pa se moraju smestiti unutar odgovarajueg try/catch bloka.
import org.omg.CORBA.*; import org.omg.CosNaming.*; import reader.*; public class ReaderClient { public static void main(String[] args) { try { // inicijalizuj ORB, prosledi mu command-line parametre ORB orb = ORB.init(args, null); // pokupi osnovni Naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // pokupi reader NameComponent nc = new NameComponent("Reader", ""); NameComponent path[] = {nc}; Reader reader = ReaderHelper.narrow(ncRef.resolve(path)); // koristi CORBA servant objekat System.out.println("Reader returned: " + reader.read("D:\\")); } catch (reader.ReaderPackage.badRead ex) { System.out.println("badRead thrown!"); } catch (Exception ex) { ex.printStackTrace(); } } }
135
10.3.6 Pozivi unatrag Poziv unatrag (callback) se koristi kada je potrebno da server poalje poruku klijentu. Da bi to bilo mogue, i klijent i server moraju imati servant objekte koji e biti pozivani. To bi znailo da bi i klijent i server bili jednaki po svojoj konstrukciji. Meutim, razlika je u tome to nema potrebe da klijent registruje svog servanta kod naming service-a. U tom smislu, klijent aplikacija izgleda kao i do sada, osim to jedna od njenih komponenti predstavlja CORBA servanta generisanog na poznati nain. Pogledajmo primer sledeeg IDL interfejsa:
callback.idl module callback { interface CBClient { string callback(in string message); }; interface CBServer { void sendMessage(in CBClient objRef, in string message); }; };
U primeru se vidi da modul callback sadri dva interfejsa; interfejs CBClient predstavlja interfejs servant objekta klijenta, a CBServer je interfejs servant objekta servera. Treba primetiti da metoda sendMessage interfejsa CBServer kao prvi parametar ima zapravo referencu na klijentski servant objekat. Server koji je deo primera se sastoji iz sledee dve klase: CallbackServer je osnovna serverska klasa registruje servanta i eka na pozive. Njen kod se sutinski ne razlikuje od slinih klasa iz prethodnih primera, pa ga ovde neemo navoditi. ServerServant implementira serverskog servanta: definie metodu sendMessage u okviru koje se poziva metoda callback klijentskog servanta.
ServerServant.java import callback.*; public class ServerServant extends _CBServerImplBase { public void sendMessage(CBClient client, String message) { System.out.println("Got message: " + message); System.out.println("Calling back: " + client.callback("What's your time zone?")); System.out.println("End."); } }
Klijentski program se takoe sastoji od dve klase: ClientServant implementira klijentskog servanta, odnosno metodu callback.
ClientServant.java import callback.*; public class ClientServant extends _CBClientImplBase { public String callback(String message) { return "My time zone is GMT+01";
136
CallbackClient je klasa koja na uobiajen nain pronalazi serverskog servanta koristei naming service i poziva njegovu metodu sendMessage. Nema potrebe ovde je navoditi jer je skoro jednaka odgovarajuim klasama iz prethodnih primera. Sekvenca dogaaja prilikom pokretanja ovog primera je sledea: 1. 2. 3. 4. server registruje svog servanta klijent trai serverskog servanta po imenu klijent poziva serverskog servanta i alje mu referencu na svog servanta serverski servant poziva klijentskog servanta
2)
naming service
1)
10.3.7 RMI i CORBA RMI i CORBA su dve tehnologije koje imaju, u osnovi, istu namenu. Postavlja se pitanje mogunosti kooperacije ovih dvaju tehnologija, odnosno da li je mogue iz RMI klijenata pristupati CORBA serverima i obrnuto. Ovako kako su opisani, saradnja RMI i CORBA programa nije mogua. Naime, RMI i CORBA koriste razliite (naravno nekompatibilne) protokole za komunikaciju: RMI koristi JRMP, dok CORBA koristi IIOP. Definisana je RMI-over-IIOP specifikacija za pisanje RMI programa koji koriste IIOP za mrenu komunikaciju. Korisnik tu novinu vidi kao proireni RMI kompajler rmic koji sada moe da generie stub i skeleton klase za IIOP i IDL interfejs za dati RMI interfejs. Na taj nain je mogue pristupati CORBA serverima iz RMI klijenata. Pri tome se, umesto RMI registry-ja, mora koristiti CORBA Naming Service. Rad u ovakvom okruenju, zapravo, obezbeuje presek skupova mogunosti dvaju tehnologija.
137
logiju moe instalirati u razliita EJB okruenja bez modifikacije. EJB specifikacija je prola kroz nekoliko verzija (1.0, 1.1, 2.0), gde je svaka od verzija dodavala nove mogunosti. U pitanju je tehnologija koja se implementira iskljuivo u jeziku Java. Sama EJB specifikacija definie model serverskih komponenti za razvoj vieslojnih arhitektura sa distribuiranim objektima. Vano je primetiti da se EJB ne bavi klijentskom stranom. EJB definie okruenje u kome ive EJB komponente. Okruenje ine EJB server i EJB kontejner. U tekuoj verziji specifikacije funkcije EJB servera i EJB kontejnera nisu strogo razdvojene. U principu je zamiljeno da EJB server i kontejner budu nezavisni jedan od drugog to bi omoguilo izgradnju EJB okruenja kombinovanjem komponenti razliitih proizvoaa. Za sada to u praksi nije mogue, tako da svaki proizvoa nudi svoj par server/kontejner koji se ne moe razdvojiti. Slika 10.12 prikazuje meusobni odnos EJB servera, kontejnera i komponenti.
EJB komponenta EJB komponenta EJB kontejner EJB server
Autor EJB komponenti ne mora da poznaje nain funkcionisanja EJB servera i kontejnera u velikoj meri. Svi EJB serveri koji se dre specifikacije e raditi sa pravilno napisanim EJB komponentama. EJB komponente su klasifikovani u nekoliko grupa, o kojima e u nastavku biti vie rei: Session beans o stateless o stateful Entity beans o bean-managed persistence o container-managed persistence
10.4.1 Session Beans Session beans su EJB komponente koje se izvravaju za potrebe tano jednog klijenta. Treba uoiti razliku izmeu session beans i RMI serverskih objekata odnosno CORBA servanta, gde su svi klijenti koristili istu instancu serverskog objekta. ivotni vek session beans EJB komponenti je vezan za ivotni vek klijenta koji ih koristi. Naime, kada klijent uputi zahtev za odreenom session bean komponentom, EJB server/kontejner e kreirati novu instancu komponente i dodeliti je na upotrebu samo tom klijentu. Osnovna namena ovakvih komponenti je da implementiraju operacije koje operiu nad podacima sistema. U pitanju su, najee, operacije nad podacima u bazi podataka celokupnog sistema.
138
Session beans su podeljeni u dve podvrste: Stateful session beans: komponente koje uvaju svoje stanje izmeu viestrukih poziva klijenta koji ih koristi. Moemo ih shvatiti kao objekte koji imaju svoje atribute ija vrednost se uva izmeu viestrukih poziva njihovih metoda. Stateless session beans: komponente koje ne uvaju svoje stanje. U tom smislu, komponenta je omoguava klijentima da pozivaju njene metode, ali izvravanje tih metoda ne utie na stanje komponente, te su pozivi njenih metoda meusobno nezavisni.
Definisanje neke komponente kao stateful ili stateless je deklarativno: dovoljno je u konfiguraciji komponente navesti da li pripada jednoj ili drugoj vrsti. 10.4.2 Entity Beans Entity beans komponente podravaju istovremeni pristup od strane vie klijenata. Namenjene su, pre svega za predstavljanje podataka u sistemu. Moe se rei da su namenjene za implementaciju modela podataka objektno-orijentisanog sistema. ivotni vek ovih komponenti je vezan za ivotni vek podataka koje predstavljaju. U tom smislu, ove komponente moraju imati mogunost da preive i prestanak rada servera iz bilo kog razloga, odnosno mogunost da restauriraju svoje stanje iz neke baze podataka sa trajnim skladitenjem podataka. Entity beans komponente se dele u dve podvrste, zavisno od naina kako se obezbeuje njihova trajnost: Bean-managed persistence: komponenta sama implementira operacije koje obezbeuju trajno uvanje podataka koje ona sadri. Najee se za tu namenu koristi baza podataka kojoj se pristupa preko JDBC interfejsa. Ove operacije inicira EJB kontejner u odgovarajuim trenucima. Container-managed persistence: operacije koje obezbeuju uvanje podataka koje komponenta sadri implementira EJB kontejner, koji se sam brine o njihovom iniciranju u odgovarajuim trenucima. Ovo je (za sada) retka osobina meu postojeim EJB serverima, a postala je obavezna u verziji 2.0 EJB specifikacije.
S obzirom na ove osobine session i entity beans komponenti, generalna preporuka je da klijenti direktno komuniciraju samo sa session beans komponentama, koje implementiraju potrebne operacije. Podaci kojima te operacije rukuju predstavljeni su odgovarajuim entity beans komponentama, koje za potrebe trajnog uvanja svog stanja koriste bazu podataka. Slika 10.12 prikazuje ovaj meusobni odnos klijenata, session i entity bean komponenti i baze podataka u okviru EJB okruenja prema njihovoj zamiljenoj nameni.
139
klijent
Slika 10.12. Meusobni odnos klijenata, session i entity bean komponenti i baze podataka
10.4.3 Komunikacija klijenata sa EJB komponentama EJB specifikacija ne insistira na korienju jedne tehnologije za komunikaciju klijenata sa EJB serverom. Specifikacija navodi kao obavezne podrku za komunikaciju putem RMI i RMI-over-IIOP tehnologija. Dakle, u startu je obezbeena podrka za komunkaciju sa RMI i CORBA klijentima. Proizvoai konkretnih EJB servera mogu da dodaju podrku za dodatne protokole za komunikaciju, na primer Microsoft-ovu COM+. Slika 10.13 ilustruje mogunost pristupa EJB komponentama od strane klijenata koji koriste razliite tehnologije distribuiranih objekata.
RMI klijent
CORBA klijent
COM+ klijent
10.4.4 Struktura EJB komponente Pisanje EJB komponente obuhvata definisanje tri stvari: Bean klasa: implementira odgovarajuu komponentu u uem smislu Home interfejs: interfejs koji definie metode za kreiranje, pronalaenje i uklanjanje komponente iz svog kontejnera Remote interfejs: definie metode koje su dostupne klijentima
Klijent komunicira sa komponentom iskljuivo preko njenih home i remote interfejsa. Meusobni odnos ovih delova EJB komponente prikazuje slika 10.14.
140
home interfejs remote interfejs klijent EJB kontejner EJB server bean klasa
Remote interfejs je Java interfejs za koga vai: nasleuje javax.ejb.EJBObject interfejs sve metode koje definie interfejs mogu da izazovu izuzetak java.rmi. RemoteException (isto kao i kod RMI interfejsa)
Home interfejs je Java interfejs za koga vai: nasleuje javax.ejb.EJBHome interfejs definie create metode za kreiranje komponente (tip rezultata koji vraaju te metode mora biti odgovarajui remote interfejs)
Bean klasa je Java klasa za koju vai: implementira javax.ejb.SessionBean ili javax.ejb.EntityBean interfejs, zavisno od vrste komponente ne implementira ni svoj home ni svoj remote interfejs! mora da sadri implementacije svih metoda iz svog remote interfejsa (mada ne postoji formalno jeziko ogranienje koje bi autora komponente primoralo na to) za svaku metodu create svog home interfejsa definie metodu ejbCreate koja se slae po parametrima sa odgovarajuom metodom create
141
public void ejbRemove() { try { conn.close(); } catch (SQLException ex) { } } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { this.ctx = ctx; props = ctx.getEnvironment(); } // inicijalizacija session bean-a public void ejbCreate() { try { Class.forName("oracle.jdbc.driver.OracleDriver"); conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1521:VTA", "vta", "vta"); } catch (Exception ex) { ex.printStackTrace(); } } // dostupna metoda public String demoSelect() throws RemoteException { String tmp = ""; try { Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery( "SELECT prezime FROM nastavnici"); while (rset.next()) tmp += "\n"+rset.getString(1); rset.close(); stmt.close(); } catch (Exception ex) { tmp = "Error"; } return tmp; } private transient SessionContext ctx; private transient Properties props; private Connection conn; }
Klijent koji koristi ovakvu EJB komponentu putem RMI tehnologije se, praktino ne razlikuje od klasinog RMI klijenta. Jedino ostaje izbor naina dobijanja reference na komponentu: putem RMI klase java.rmi.Naming ili putem JNDI biblioteke. Ovde navodimo primere za obe varijante.
import javax.ejb.*; import java.rmi.*; import demo.*; public class DemoClient1 { public static void main(String[] args) { try { System.setSecurityManager(new RMISecurityManager()); DemoHome demoHome = (DemoHome)Naming.lookup( "//branko.tmd.ns.ac.yu:1099/demo");
142
Demo demo = demoHome.create(); System.out.println("EJB returned: " + demo.demoSelect()); } catch (Exception ex) { ex.printStackTrace(); } } } import java.rmi.*; import javax.ejb.*; import javax.naming.*; import javax.rmi.*; public class DemoClient2 { public static void main(String[] args) { try { Context context = new InitialContext(); Object homeObject = context.lookup("java:comp/env/demo"); DemoHome home = (DemoHome) PortableRemoteObject.narrow(homeObject, DemoHome.class); Demo demo = (Demo) PortableRemoteObject.narrow(home.create(), Demo.class); System.out.println("EJB returned: "+demo.demoSelect()); } catch (Exception ex) { ex.printStackTrace(); } } }
143
Poglavlje 11
Arhitektura sistema je prikazana na slici 11.1. Klijenti u sistemu su, zapravo, Web itai. Dinamiki sadraj kojeg oni prikazuju generiu Web serveri pomou servleta i JSP stranica. Za potrebe trajnog skladitenja podataka koristi se baza podataka.
DB HTML Servlets JSP
144
odgovara Oracle SUBP je dat na slici 11.3. Ovaj model podataka dele front office i back office aplikacije.
Admin Admin ID Username Pasword Supplier Supplier ID Supplier Name Supplier Address
Supplies
Is Parent To
User User ID Username Pasword First Name Last Name User Address Email Receive news
Places
Contains Items
Product
Is Ordered
Contains Products
Has Images
Conceptual Data Model Project : Web Shop Model : Web Shop Author : Branko Milosavljevic Version 1.0 11.4.2001
Product Image Image ID Image Title Image Height Image Width Content Type Image Data
SUPPLIERS SUPPLIER_ID SUPPLIER_NAME SUPPLIER_ADDRESS INTEGER VARCHAR2(100) VARCHAR2(100) not null not null not null
CATEGORY_ID = PARENT_CATEGORY_ID
SUPPLIER_ID = SUPPLIER_ID
ORDERS ORDER_ID USER_ID ORDER_DATE INTEGER not null INTEGER not null DATE not null
ORDER_ID = ORDER_ID
ORDERED_ITEMS ORDER_ID PRODUCT_ID QUANTITY INTEGER not null INTEGER not null INTEGER not null
PRODUCT_ID = PRODUCT_ID
PRODUCTS PRODUCT_ID CATEGORY_ID SUPPLIER_ID PRODUCT_NAME VENDOR DESCRIPTION PRICE INTEGER INTEGER INTEGER VARCHAR2(100) VARCHAR2(100) VARCHAR2(1000) DECIMAL(9,2) not null not null not null not null not null not null not null
CATEGORIES CATEGORY_ID PARENT_CATEGORY_ID CATEGORY_NAME CATEGORY_DESC INTEGER INTEGER VARCHAR2(50) VARCHAR2(500) not null null not null null
CATEGORY_ID = CATEGORY_ID
PRODUCT_IMAGES PRODUCT_ID IMAGE_ID IMAGE_TITLE IMAGE_HEIGHT IMAGE_WIDTH CONTENT_TYPE IMAGE_DATA INTEGER INTEGER VARCHAR2(50) INTEGER INTEGER VARCHAR2(30) LONG RAW not null not null null not null not null not null null Physical Data Model Project : Web Shop Model : Web Shop Author : Branko Milosavljevic Version 1.0 11.4.2001
USERS USER_ID USERNAME PASWORD FIRST_NAME LAST_NAME USER_ADDRESS EMAIL RECEIVE_NEWS INTEGER VARCHAR2(20) VARCHAR2(20) VARCHAR2(25) VARCHAR2(35) VARCHAR2(100) VARCHAR2(50) NUMBER(1) not null not null not null not null not null not null not null not null ADMIN_ID USERNAME PASWORD
ADMINS INTEGER VARCHAR2(20) VARCHAR2(20) not null not null not null
Entitet User predstavlja krajnjeg korisnika u sistemu. Njegovi atributi su podaci koje korisnik unosi prilikom sopstvene registracije. Kategorije proizvoda su predstavljene entitetom Category. Ovaj entitet ima vezu isParentTo koja predstavlja vezu izmeu kategorije-roditelja i potkategorije (deteta). Entitet Product predstavlja proizvod, koji pripada odreenoj kategoriji, ima svog dobavljaa (Supplier), i ima nula ili vie svojih slika (Product Image). Korisnik moe da kreira narudbe (Order) koje se sastoje iz vie stavki (Ordered Item). Svaka stavka predstavlja odreeni proizvod u nekoj koliini. Entitet Admin predstavlja administratora (korisnika back office aplikacije).
145
predstavljaju strukturu front office i back office sajta. (Pod strukturom sajta podrazumevamo graf koji opisuje mogue kretanje izmeu stranica sajta).
login successful
login
index
submit submit
register
category
product
submit
fromPurchase
purchase
oldPurchase
Stranica index.jsp je poetna stranica kojoj se pristupa. Stranica register.jsp omoguava registraciju novih korisnika (koju oni obavljaju samostalno). Na njoj se nalazi forma za unos podataka o novom korisniku. Nakon registracije korisnik se upuuje na osnovnu stranicu i automatski je prijavljen za rad. Stranica login.jsp je namenjena za prijavljivanje postojeih korisnika. Na nju se moe stii klikom na odgovarajui link na osnovnoj stranici. Po uspenom prijavljivanju korisnik se upuuje na osnovnu stranicu. Ukoliko je prijavljivanje neuspeno, korisnik ostaje na login.jsp stranici. U okviru index.jsp stranice postoje linkovi na kategorije prvog nivoa, za ije prikazivanje je zaduena stranica category.jsp. Stranica product.jsp je namenjena za prikazivanje podataka o konkretnom proizvodu. Pored toga, ova stranica omoguava dodavanje trenutno prikazanog proizvoda u potroaku korpu (u datoj koliini). Ova funkcija se obavlja pomou forme koja upuuje korisnika na istu product.jsp stranicu. Stranica purchase.jsp omoguava potvrdu ili opoziv narudbe, kao i linkove na oldPurchase.jsp stranicu namenjenu za pregled starih narudbi. U okviru back office sajta, stranica index.jsp je poetna stranica kojoj se pristupa. Za sve stranice, pa i za ovu, vai da e preusmeriti korisnika na stranicu login.jsp ukoliko se korisnik nije ve prijavio (te veze nisu predstavljene na dijagramu zbog preglednosti). Stranica register.jsp je namenjena za registraciju novog administratora. Stranice suppliers.jsp, categories.jsp, products.jsp i addPicture.jsp su namenjene za dodavanje novog dobavljaa, nove kategorije, novog proizvoda i nove slike (vezane za postojei proizvod) u bazu podataka. Stranica savePicture.jsp e samo prihvatiti podatke primljene iz forme na stranici addPicture.jsp, dodati novu sliku u bazu podataka i preusmeriti korisnika nazad na stranicu addPicture.jsp.
146
redirect
login
addPicture
submit
savePicture
register
index
products
submit
suppliers
categories
submit
submit
Tabela 11.1 sadri listu stranica front office sajta sa njihovim parametrima.
Stranica index.jsp category.jsp product.jsp Parametar catID catID prodID quantity Znaenje
ID kategorije u bazi podataka ID kategorije kojoj proizvod pripada ID proizvoda u bazi podataka koliina naruenog proizvoda; definisan je prilikom ulaska u stranicu putem forme za dodavanje proizvoda u korpu purchase definisan kada je korisnik potvrdio porudbinu cancel definisan kada je korisnik opozvao porudbinu orderID ID porudbine koju treba prikazati username uneto korisniko ime password uneta lozinka fromPurchase ima vrednost yes kada se u stranicu ulazi sa purchase.jsp username korisniko ime novog korisnika password lozinka firstName ime korisnika lastName prezime korisnika address adresa korisnika email email adresa korisnika receiveNews oznaka da korisnik eli da prima obavetenja sa sajta Tabela 11.1. Parametri stranica front office sajta
147
login.jsp register.jsp
visina slike u pikselima irina slike u pikselima naziv slike slika koja se dodaje korisniko ime administratora koji se prijavljuje lozinka administratora koji se prijavljuje korisniko ime administratora koji se registruje lozinka administratora koji se registruje Tabela 11.2. Parametri stranica back office sajta
* * Item
AdminUser
Supplier -supplierID : int = 0 -name : String = "" -address : String = "" +add() : void
Product -productID : int = 0 -name : String = "" -vendor : String = "" -desc : String = "" +add() : void 1 1 *
-adminID : int = 0 -username : String = "" -password : String = "" +register() : void +login() : void
* ProdImage -imageID : int = 0 -title : String = "" -height : int = 0 -width : int = 0 -contentType : String = "image/gif" -data : Byte +add() : void
Druga mogunost je da se prilikom obrade svakog HTTP zahteva otvara i zatvara posebna konekcija. Ovakvo reenje nije zadovoljavajue sa stanovita performansi, jer je otvaranje konekcije sa bazom podataka dugotrajna operacija. Uobiajen nain za reavanje ovog problema je korienje skupa raspoloivih konekcija (connection pool). Zadatak ovog skupa je da upravlja konekcijama tako da njegovi korisnici u najveem broju sluajeva dobijaju unapred otvorenu konekciju kada je zatrae. Nad skupom konekcija definisane su dve operacije preuzimanje konekcije iz skupa (radi njene upotrebe) i vraanje konekcije u skup (nakon korienja). Ovde je predstavljen pool koji rukuje konekcijama na sledei nain: Prilikom svoje inicijalizacije unapred otvori odreen broj konekcija i smesti ih u skup slobodnih konekcija Na zahtev za dobijanje konekcije, izdvaja prvu konekciju iz skupa slobodnih konekcija, smeta je u skup zauzetih konekcija i vraa korisniku; ukoliko nema slobodnih konekcija otvara novu konekciju, osim u sluaju da je dostignuta granica maksimalnog broja otvorenih konekcija kada eka na oslobaanje neke konekcije Na zahtev za vraanje konekcije, premeta konekciju iz skupa zauzetih konekcija u skup slobodnh konekcija. Ukoliko je broj slobodnih konekcija vei od nekog unapred odreenog broja, zatvara potreban broj konekcija i uklanja ih iz skupa.
Pored toga potrebno je obezbediti da svi korisnici pristupaju istoj instanci poola. To se moe obezbediti na taj nain to e klasa ConnectionPool koja predstavlja pool sadrati statiki atribut klase ConnectionPool. Taj atribut je, zapravo, jedina instanca klase koja e biti kreirana. Ova instanca se inicijalizuje u okviru odgovarajueg static bloka. Klasa ne sadri nijedan javni konstruktor, kako bi se onemoguilo kreiranje objekata ove klase. Posebna metoda getConnectionPool vraa statiki atribut klase. Jedini nain da korisnici dobiju objekat ove klase je pomou poziva ove metode. U nastavku je dat programski kod klase ConnectionPool.
import java.sql.*; import java.util.*; /** * Handles a pool of JDBC connections. Use * <code>checkOut</code> to get a connection from * the pool, and <code>checkIn</code> to put it * back in the pool when finished.<p> * * The class is implemented as a singleton, so * one must use <code>getConnectionPool()</code> * to obtain a connection pool reference. * * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * * @version 1.1 */ public class ConnectionPool {
149
/** Return a singleton connection pool reference. */ public static ConnectionPool getConnectionPool() { return connectionPool; } /** singleton reference */ private static ConnectionPool connectionPool; /** Initialize connection pool when loading this class */ static { ResourceBundle bundle = PropertyResourceBundle.getBundle( "webshop.ConnectionPool"); String driver = bundle.getString("driver"); String jdbcURL = bundle.getString("jdbcURL"); String username = bundle.getString("username"); String password = bundle.getString("password"); int preconnectCount = 0; int maxIdleConnections = 10; int maxConnections = 10; try { preconnectCount = Integer.parseInt( bundle.getString("preconnectCount")); maxIdleConnections = Integer.parseInt( bundle.getString("maxIdleConnections")); maxConnections = Integer.parseInt( bundle.getString("maxConnections")); } catch (Exception ex) { ex.printStackTrace(); } try { connectionPool = new ConnectionPool(driver, jdbcURL, username, password, preconnectCount, maxIdleConnections, maxConnections); } catch (Exception ex) { ex.printStackTrace(); } } /** * * * * * * * * * * * * */ Constructs a connection pool. @param aDriver JDBC driver class name @param aJdbcURL JDBC connection URL @param aUsername DB username @param aPassword DB password @param aPreconnectCount number of connections to open at startup @param aMaxIdleConnections maximum number of free connections to keep @param aMaxConnections maximum number of connections in the pool (both taken and free)
150
protected ConnectionPool(String aDriver, String aJdbcURL, String aUsername, String aPassword, int aPreconnectCount, int aMaxIdleConnections, int aMaxConnections) throws ClassNotFoundException, SQLException { freeConnections = new Vector(); usedConnections = new Vector(); driver = aDriver; jdbcURL = aJdbcURL; username = aUsername; password = aPassword; preconnectCount = aPreconnectCount; maxIdleConnections = aMaxIdleConnections; maxConnections = aMaxConnections; Class.forName(driver); for (int i = 0; i < preconnectCount; i++) { Connection conn = DriverManager.getConnection( jdbcURL, username, password); conn.setAutoCommit(false); freeConnections.addElement(conn); } connectCount = preconnectCount; } /** Retrieves a connection from the pool. * * @return A connection */ public synchronized Connection checkOut() throws SQLException { Connection conn = null; if (freeConnections.size() > 0) { conn = (Connection)freeConnections.elementAt(0); freeConnections.removeElementAt(0); usedConnections.addElement(conn); } else { if (connectCount < maxConnections) { conn = DriverManager.getConnection( jdbcURL, username, password); usedConnections.addElement(conn); connectCount++; } else { try { wait(); conn = (Connection)freeConnections.elementAt(0); freeConnections.removeElementAt(0); usedConnections.addElement(conn); } catch (InterruptedException ex) { ex.printStackTrace(); } } }
151
return conn;
/** Puts a connection back in the pool. * * @param aConn Connection to be put back */ public synchronized void checkIn(Connection aConn) { if (aConn == null) return; if (usedConnections.removeElement(aConn)) { freeConnections.addElement(aConn); while (freeConnections.size() > maxIdleConnections) { int lastOne = freeConnections.size() - 1; Connection conn = (Connection) freeConnections.elementAt(lastOne); try { conn.close(); } catch (SQLException ex) { } freeConnections.removeElementAt(lastOne); } notify(); } } /** JDBC driver class name */ private String driver; /** JDBC URL */ private String jdbcURL; /** username for JDBC connection */ private String username; /** password for JDBC connection */ private String password; /** the number of connections to open at startup */ private int preconnectCount; /** the number of currently open connections in the pool */ private int connectCount; /** the maximum number of idle connections */ private int maxIdleConnections; /** the maximum number of connections */ private int maxConnections; /** used connections */ private Vector usedConnections; /** free connections */ private Vector freeConnections; }
152
Literatura
Koriena literatura u okviru ovog praktikuma nije posebno referencirana. U ovom odeljku je dat spisak koriene literature grupisan po tematskim celinama. Pored toga, Web sajt koji prati kurs sadri vei deo ovih materijala.
153
WWW
HTML 4.0 Reference, WWW Consortium 2000. http://www.w3.org Guide to Cascading Style Sheets, Level 1, WWW Consortium 2000, http://www.w3.org Netscape HTML Tag Reference, Netscape Corporation 1999. http://developer.netscape.com Brown, M., Special Edition Using HTML, 2nd Edition, QUE Publishing, Indianapolis 1999.
SQL
Stephens R. K., Plew R. R., Morgan B., Perkins J., Teach Yourself SQL in 21 Days, 2nd Edition, SAMS Publishing, Indianapolis 1999.
154
Prilozi
Veba: chat aplikacija Reenje je detaljno opisano u poglavlju 5. Ovde se daje samo programski kod klijentskog i serverskog programa. Sledi programski kod klijenta:
chat\client\ChatClient.java package chat.client; import import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.io.*; java.net.*;
/** Osnovna klasa klijentske aplikacije. */ public class ChatClient extends JFrame { public static final int TCP_PORT = 9000; public ChatClient() { setSize(500, 300); setTitle("Chat Client"); pEntryLine.setLayout(new FlowLayout()); pEntryLine.add(tfEntryLine); pEntryLine.add(bSend); pEntryLine.add(bClose); taMessages.setText("Starting chat session...\n"); spMessages.setPreferredSize(new Dimension(490, 230)); spMessages.getViewport().setView(taMessages); pMessages.add(spMessages, BorderLayout.NORTH); getContentPane().add(pMessages, BorderLayout.NORTH); getContentPane().add(pEntryLine, BorderLayout.SOUTH); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent ev) { cd.setMessage("QUIT!"); } }); bClose.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { cd.setMessage("QUIT!"); } }); bSend.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { sendMessage(); } }); tfEntryLine.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent ev) { if (ev.getKeyCode() == KeyEvent.VK_ENTER) sendMessage();
155
} }); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((d.width - getSize().width) / 2, (d.height - getSize().height) / 2); } /** alje poruku serveru. */ public void sendMessage() { String message = tfEntryLine.getText().trim(); taMessages.append(">> " + message + "\n"); tfEntryLine.setText(""); cd.setMessage(message); } /** Vri prijavljivanje korisnika. */ public boolean login() { try { LoginDlg loginDlg = new LoginDlg(); loginDlg.setVisible(true); InetAddress addr = InetAddress.getByName(loginDlg.getServer()); Socket sock = new Socket(addr, TCP_PORT); BufferedReader in = new BufferedReader( new InputStreamReader( sock.getInputStream())); PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( sock.getOutputStream())), true); out.println(loginDlg.getUsername()); String response = in.readLine(); if (!response.equals("OK")) throw new Exception("Invalid user"); cd = new ChatData(); ReaderThread rt = new ReaderThread(sock, in, cd, taMessages); WriterThread wt = new WriterThread(out, cd); setTitle("Chat Client [" + loginDlg.getUsername() + "]"); } catch (Exception ex) { ex.printStackTrace(); return false; } return true; } /** Prikazuje login dijalog i, ako je prijavljivanje uspeno, * otvara osnovni prozor aplikacije. */ public static void main(String[] args) { ChatClient cc = new ChatClient(); if (cc.login()) cc.setVisible(true); else System.exit(0); } JPanel pMessages = new JPanel(); JPanel pEntryLine = new JPanel(); JButton bSend = new JButton("Send"); JButton bClose = new JButton("Close"); JTextField tfEntryLine = new JTextField(25); JScrollPane spMessages = new JScrollPane(); JTextArea taMessages = new JTextArea(); ChatData cd; } chat\client\ChatData.java package chat.client; /** Predstavlja bafer za poruke koje se alju serveru. */ public class ChatData { public synchronized void setMessage(String message) { this.message = message; notify();
156
} public synchronized String getMessage() { try { wait(); } catch (Exception ex) { } return message; } private String message; } chat\client\LoginDlg.java package chat.client; import import import import java.awt.*; java.awt.event.*; javax.swing.*; com.borland.jbcl.layout.*;
/** Predstavlja dijalog za prijavljivanje korisnika. */ public class LoginDlg extends JDialog { public LoginDlg(Frame parent, String title, boolean modal) { super(parent, title, modal); setSize(200, 125); getContentPane().setLayout(new XYLayout()); getContentPane().add(lUsername, new XYConstraints(10, 10, -1, -1)); getContentPane().add(tfUsername, new XYConstraints(75, 10, -1, -1)); getContentPane().add(lServer, new XYConstraints(10, 30, -1, -1)); getContentPane().add(tfServer, new XYConstraints(75, 30, -1, -1)); getContentPane().add(bLogin, new XYConstraints(60, 60, -1, -1)); getRootPane().setDefaultButton(bLogin); bLogin.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { setVisible(false); } }); tfServer.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent ev) { if (ev.getKeyCode() == KeyEvent.VK_ENTER) bLogin.doClick(); } }); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((d.width - getSize().width) / 2, (d.height - getSize().height) / 2); } public LoginDlg() { this(null, "Connect", true); } /** Vraa uneto korisniko ime. */ public String getUsername() { return tfUsername.getText().trim(); } /** Vraa unetu adresu servera. */ public String getServer() { return tfServer.getText().trim(); } JLabel lUsername = new JLabel("Username:"); JLabel lServer = new JLabel("Server:"); JTextField tfUsername = new JTextField(10); JTextField tfServer = new JTextField(10); JButton bLogin = new JButton(" Login "); } chat\client\ReaderThread.java package chat.client;
157
import java.io.*; import java.net.*; import javax.swing.*; /** Nit za itanje poruka sa servera. */ public class ReaderThread extends Thread { public ReaderThread(Socket sock, BufferedReader in, ChatData chatData, JTextArea ta) { this.sock = sock; this.in = in; this.chatData = chatData; this.ta = ta; start(); } public void run() { try { String msg; while (true) { msg = in.readLine(); if (msg != null) ta.append(msg + "\n"); } } catch (Exception ex) { ex.printStackTrace(); } } private private private private } chat\client\WriterThread.java package chat.client; import java.io.*; import java.net.*; /** Nit za slanje poruka serveru. */ public class WriterThread extends Thread { public WriterThread(PrintWriter out, ChatData chatData) { this.out = out; this.chatData = chatData; start(); } public void run() { try { String msg; while (true) { msg = chatData.getMessage(); out.println(msg); if (msg.equals("QUIT!")) System.exit(0); } } catch (Exception ex) { ex.printStackTrace(); } } private Socket sock; private PrintWriter out; private ChatData chatData; } Socket sock; BufferedReader in; ChatData chatData; JTextArea ta;
158
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** Osnovna klasa servera: pokree listener i prikazuje prozor. */ public class ChatServer extends JFrame { public ChatServer() { setSize(400, 300); setTitle("Chat Server"); // the buttons panel pButtons.setLayout(new FlowLayout()); pButtons.add(bClose); pButtons.add(bAbout); // text area for the list of connected clients spClients.setPreferredSize(new Dimension(390, 200)); spClients.getViewport().setView(taClients); pClients.add(spClients, BorderLayout.CENTER); // tabbed pane tpTabs.add(pClients, "Clients"); tpTabs.add(pStats, "Statistics"); getContentPane().add(tpTabs, BorderLayout.CENTER); getContentPane().add(pButtons, BorderLayout.SOUTH); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent ev) { System.exit(0); } }); bClose.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } }); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((d.width - getSize().width) / 2, (d.height - getSize().height) / 2); } public static void main(String[] args) { ChatServer cs = new ChatServer(); ServerListener sl = new ServerListener(cs.taClients); cs.setVisible(true); } JButton bClose = new JButton(" Close "); JButton bAbout = new JButton(" About... "); JPanel pButtons = new JPanel(); JPanel pClients = new JPanel(); JPanel pStats = new JPanel(); JTabbedPane tpTabs = new JTabbedPane(); JScrollPane spClients = new JScrollPane(); JTextArea taClients = new JTextArea(); } chat\server\ServerListener.java package chat.server; import java.io.*; import java.net.*; import javax.swing.*; /** Osnovna serverska nit koja eka klijente. */ public class ServerListener extends Thread { public static final int TCP_PORT = 9000; public ServerListener(JTextArea ta) { this.ta = ta; start();
159
} public void run() { try { ServerSocket ss = new ServerSocket(TCP_PORT); while (true) { Socket sock = ss.accept(); BufferedReader in = new BufferedReader( new InputStreamReader( sock.getInputStream())); PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( sock.getOutputStream())), true); String username = in.readLine(); String address = sock.getInetAddress().getHostAddress(); ActiveClient client = ClientUtils.addClient(username, address); if (client == null) { out.println("Bad user"); in.close(); out.close(); sock.close(); continue; } out.println("OK"); ReaderThread rt = new ReaderThread(sock, in, client, ta); WriterThread wt = new WriterThread(out, client); ta.setText(ClientUtils.getClientList()); } } catch (Exception ex) { ex.printStackTrace(); } } private JTextArea ta; } chat\server\ActiveClient.java package chat.server; /** Predstavlja jednog aktivnog klijenta. */ public class ActiveClient { public ActiveClient(String username, String address) { this.username = username; this.address = address; this.message = ""; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setAddress(String address) { this.address = address; } public String getAddress() { return address; } public synchronized void setMessage(String message) { this.message = message; notify(); } public synchronized String getMessage() { try { wait(); } catch (Exception ex) { } return message; }
160
private String username; private String address; private String message; } chat\server\ClientUtils.java package chat.server; import java.util.*; /** Implementira operacije nad kolekcijom aktivnih klijenata. */ public class ClientUtils { public static synchronized ActiveClient addClient(String username, String address) { ActiveClient test = (ActiveClient)clients.get(username); if (test == null) { ActiveClient client = new ActiveClient(username, address); clients.put(username, client); return client; } else return null; } public static synchronized boolean removeClient(String username) { ActiveClient test = (ActiveClient)clients.get(username); if (test == null) return false; else clients.remove(username); return true; } public static void sendMessageToAll(String sender, String message) { Enumeration enum = clients.elements(); while (enum.hasMoreElements()) { ActiveClient ac = (ActiveClient)enum.nextElement(); if (!ac.getUsername().equals(sender)) ac.setMessage(message); } } public static void sendMessageToSelf(String sender, String message) { Enumeration enum = clients.elements(); while (enum.hasMoreElements()) { ActiveClient ac = (ActiveClient)enum.nextElement(); if (ac.getUsername().equals(sender)) { ac.setMessage(message); break; } } } public static String getClientList() { StringBuffer retVal = new StringBuffer(500); Enumeration enum = clients.elements(); while (enum.hasMoreElements()) { ActiveClient ac = (ActiveClient)enum.nextElement(); retVal.append(ac.getUsername()); retVal.append("\n"); } return retVal.toString(); } private static Hashtable clients = new Hashtable(); } chat\server\WriterThread.java package chat.server; import java.io.*; import java.net.*; /** Nit za slanje poruka klijentu. */ public class WriterThread extends Thread {
161
public WriterThread(PrintWriter out, ActiveClient client) { this.out = out; this.client = client; start(); } public void run() { try { String msg; while (!(msg = client.getMessage()).equals("QUIT!")) out.println(msg); } catch (Exception ex) { ex.printStackTrace(); } } private PrintWriter out; private ActiveClient client; } chat\server\ReaderThread.java package chat.server; import java.io.*; import java.net.*; import javax.swing.*; /** Nit za slanje poruka klijentu. */ public class ReaderThread extends Thread { public ReaderThread(Socket sock,BufferedReader in,ActiveClient client,JTextArea ta) { this.sock = sock; this.in = in; this.client = client; this.ta = ta; start(); } public void run() { try { String msg; while (!(msg = in.readLine()).equals("QUIT!")) ClientUtils.sendMessageToAll(client.getUsername(), "["+client.getUsername()+"/"+client.getAddress()+"] "+msg); ClientUtils.sendMessageToSelf(client.getUsername(), msg); in.close(); sock.close(); ClientUtils.removeClient(client.getUsername()); ta.setText(ClientUtils.getClientList()); } catch (Exception ex) { ex.printStackTrace(); } } private private private private } Socket sock; BufferedReader in; ActiveClient client; JTextArea ta;
Dodatni JSP tagovi korieni u toku kursa Slede klase koje implementiraju if-then-else tagove ije korienje je prikazano u poglavlju 9.
tags\iftag\IfTag.java package tags.iftag; import import import import java.io.*; javax.servlet.*; javax.servlet.jsp.*; javax.servlet.jsp.tagext.*;
162
/** Osnovni tag koji slui za if-then-else konstrukcije */ public class IfTag extends TagSupport { public int doStartTag() { return EVAL_BODY_INCLUDE; } public void setCondition(boolean condition) { this.condition = condition; hasCondition = true; } public boolean getCondition() { return condition; } public void setHasCondition(boolean flag) { this.hasCondition = flag; } public boolean hasCondition() { return hasCondition; } private boolean condition; private boolean hasCondition = false; } tags\iftag\ConditionTag.java package tags.iftag; import import import import java.io.*; javax.servlet.*; javax.servlet.jsp.*; javax.servlet.jsp.tagext.*;
/** Tag koji slui za definisanje uslova if naredbe. */ public class ConditionTag extends BodyTagSupport { public int doStartTag() throws JspTagException { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); if (parent == null) throw new JspTagException("condition tag must be inside if tag!"); return EVAL_BODY_TAG; } public int doAfterBody() { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); String body = getBodyContent().getString(); if (body.trim().equals("true")) parent.setCondition(true); else parent.setCondition(false); return SKIP_BODY; } } tags\iftag\ThenTag.java package tags.iftag; import import import import java.io.*; javax.servlet.*; javax.servlet.jsp.*; javax.servlet.jsp.tagext.*;
/** Tag koji slui kao then deo if naredbe (ako je uslov zadovoljen) */ public class ThenTag extends BodyTagSupport { public int doStartTag() throws JspTagException { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); if (parent == null) throw new JspTagException("then tag must be inside if tag!"); else if (!parent.hasCondition()) throw new JspTagException("condition tag must be defined before then tag!"); return EVAL_BODY_TAG; }
163
public int doAfterBody() { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); if (parent.getCondition()) { try { BodyContent body = getBodyContent(); JspWriter out = body.getEnclosingWriter(); out.print(body.getString()); } catch (IOException ex) { System.out.println("Error in ThenTag: " + ex.toString()); } } return SKIP_BODY; } } tags\iftag\ElseTag.java package tags.iftag; import import import import java.io.*; javax.servlet.*; javax.servlet.jsp.*; javax.servlet.jsp.tagext.*;
/** Tag koji slui kao else deo if naredbe (ako uslov nije zadovoljen) */ public class ElseTag extends BodyTagSupport { public int doStartTag() throws JspTagException { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); if (parent == null) throw new JspTagException("else tag must be inside if tag!"); else if (!parent.hasCondition()) throw new JspTagException("condition tag must be defined before else tag!"); return EVAL_BODY_TAG; } public int doAfterBody() { IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class); if (!parent.getCondition()) { try { BodyContent body = getBodyContent(); JspWriter out = body.getEnclosingWriter(); out.print(body.getString()); } catch (IOException ex) { System.out.println("Error in ElseTag: " + ex.toString()); } } return SKIP_BODY; } }
Slede klase koje implementiraju SQLQuery tag i njegove podtagove, ije korienje je prikazano u poglavlju 9. Ovi tagovi se koriste i okviru primera Web aplikacije iz poglavlja 11.
webshop\sqltags\SQLQueryTag.java package webshop.sqltags; import java.io.*; import java.sql.*; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import webshop.*; /** * Main JSP tag for displaying SQL query results. Used with * <code>OutputStartTag</code>, <code>OutputEndTag</code>, * <code>OutputTag</code>, and <code>EmptyTag</code> subtags.<p> * The sequence of calls is as follows:
164
* <ul> * <li><code>OutputStartTag</code> is rendered once for the first row of * the result set, if row set is not empty. * <li><code>OutputEndTag</code> is rendered once for the last row of * the result set, if row set is not empty. * <li><code>OutputTag</code> is rendered once for each row of * the result set, if row set is not empty. * <li><code>EmptyTag</code> is rendered once if row set is empty. * </ul> * This tag defines an attribute called <code>resultRow</code>, an * instance of <code>java.util.Hashtable</code> for storing a single * row of the row set. The columns in a row are accessed by * <code>Hashtable.get()</code> method, with the parameter being the * name of the column (or alias if defined) in lowercase. * <p> * The body of the tag is rendered once for each row in the result set. * * @see OutputStartTag * @see OutputEndTag * @see OutputTag * @see EmptyTag * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * @version 1.0 */ public class SQLQueryTag extends BodyTagSupport { /** called at the beginning of the tag */ public int doStartTag() throws JspException { if (connectionPool == null || sqlQuery == null) return SKIP_BODY; int retVal = EVAL_BODY_TAG; Connection conn = null; try { conn = connectionPool.checkOut(); Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery(sqlQuery); ResultSetMetaData meta = rset.getMetaData(); int columnCount = meta.getColumnCount(); String[] columnNames = new String[columnCount]; for (int i = 0; i < columnCount; i++) columnNames[i] = meta.getColumnLabel(i+1).toLowerCase(); rows = new Vector(); while (rset.next()) { Hashtable table = new Hashtable(); for (int i = 0; i < columnCount; i++) { String colValue = rset.getString(i+1); colValue = (colValue == null)?"":colValue; table.put(columnNames[i], colValue); } rows.addElement(table); } rset.close(); stmt.close(); } catch (SQLException ex) { ex.printStackTrace(); retVal = SKIP_BODY; } finally { connectionPool.checkIn(conn); } rowCounter = 0; if (rows.size() > 0) pageContext.setAttribute("resultRow", rows.elementAt(0), PageContext.PAGE_SCOPE); return retVal; } /** called at the end of the tag */ public int doEndTag() throws JspException { try { if (bodyContent != null) bodyContent.writeOut(bodyContent.getEnclosingWriter()); } catch(IOException ex) { throw new JspException("IO Error: " + ex.getMessage()); } return EVAL_PAGE; }
165
/** called at the end of the body of the tag */ public int doAfterBody() throws JspException { rowCounter++; if (rowCounter >= rows.size()) return SKIP_BODY; pageContext.setAttribute("resultRow", rows.elementAt(rowCounter), PageContext.PAGE_SCOPE); return EVAL_BODY_TAG; } /** rowCounter property getter method */ public int getRowCounter() { return rowCounter; } /** maxRows property getter method */ public int getMaxRows() { return rows.size(); } /** connectionPool property setter method */ public void setConnectionPool(ConnectionPool aConnectionPool) { connectionPool = aConnectionPool; } /** sqlQuery property setter method */ public void setSqlQuery(String aSqlQuery) { sqlQuery = aSqlQuery; } /** pageContext property setter method */ public void setPageContext(PageContext aPageContext) { pageContext = aPageContext; } /** parent property setter method */ public void setParent(Tag aParent) { parent = aParent; } /** parent property getter method */ public Tag getParent() { return parent; } /** clean up tasks */ public void release() { rows = null; } /** connection pool to use */ private ConnectionPool connectionPool; /** SQL query to be executed */ private String sqlQuery; /** current page context */ private PageContext pageContext; /** parent tag */ private Tag parent; /** vector of <code>Hashtable</code>s representing rows in * a result set */ private Vector rows; /** rowset counter */ private int rowCounter; } webshop\sqltags\OutputStartTag.java package webshop.sqltags; import java.io.*; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** JSP tag for displaying query results. Used as a subtag * of <code>SQLQueryTag</code>. Renders its body once, at the beginning * of a result set.
166
* * @see SQLQueryTag * @see OutputEndTag * @see OutputTag * @see EmptyTag * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * @version 1.0 */ public class OutputStartTag extends BodyTagSupport { /** called at the beginning of the tag */ public int doStartTag() throws JspException { if (parent == null) return SKIP_BODY; if (!(parent instanceof SQLQueryTag)) return SKIP_BODY; SQLQueryTag myParent = (SQLQueryTag)parent; if (myParent.getRowCounter() == 0 && myParent.getMaxRows() > 0) return EVAL_BODY_TAG; else return SKIP_BODY; } /** called at the end of the tag */ public int doEndTag() throws JspException { try { if (bodyContent != null) bodyContent.writeOut(bodyContent.getEnclosingWriter()); } catch(IOException ex) { throw new JspException("IO Error: " + ex.getMessage()); } return EVAL_PAGE; } /** called at the end of the body of the tag */ public int doAfterBody() throws JspException { return SKIP_BODY; } /** pageContext property setter method */ public void setPageContext(PageContext aPageContext) { pageContext = aPageContext; } /** parent property setter method */ public void setParent(Tag aParent) { parent = aParent; } /** parent property getter method */ public Tag getParent() { return parent; } /** clean up tasks */ public void release() { } /** current page context */ private PageContext pageContext; /** parent tag */ private Tag parent; } webshop\sqltags\OutputTag.java package webshop.sqltags; import java.io.*; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** JSP tag for displaying query results. Used as a subtag * of <code>SQLQueryTag</code>. Renders its body once for each * row of a result set. *
167
* @see SQLQueryTag * @see OutputStartTag * @see OutputEndTag * @see EmptyTag * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * @version 1.0 */ public class OutputTag extends BodyTagSupport { /** called at the beginning of the tag */ public int doStartTag() throws JspException { if (parent == null) return SKIP_BODY; if (!(parent instanceof SQLQueryTag)) return SKIP_BODY; SQLQueryTag myParent = (SQLQueryTag)parent; if (myParent.getMaxRows() == 0) return SKIP_BODY; return EVAL_BODY_TAG; } /** called at the end of the tag */ public int doEndTag() throws JspException { try { if (bodyContent != null) bodyContent.writeOut(bodyContent.getEnclosingWriter()); } catch(IOException ex) { throw new JspException("IO Error: " + ex.getMessage()); } return EVAL_PAGE; } /** called at the end of the body of the tag */ public int doAfterBody() throws JspException { return SKIP_BODY; } /** pageContext property setter method */ public void setPageContext(PageContext aPageContext) { pageContext = aPageContext; } /** parent property setter method */ public void setParent(Tag aParent) { parent = aParent; } /** parent property getter method */ public Tag getParent() { return parent; } /** clean up tasks */ public void release() { } /** current page context */ private PageContext pageContext; /** parent tag */ private Tag parent; } webshop\sqltags\OutputEndTag.java package webshop.sqltags; import java.io.*; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** JSP tag for displaying query results. Used as a subtag * of <code>SQLQueryTag</code>. Renders its body once, at the end * of a result set. * * @see OutputStartTag * @see SQLQueryTag
168
* @see OutputTag * @see EmptyTag * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * @version 1.0 */ public class OutputEndTag extends BodyTagSupport { /** called at the beginning of the tag */ public int doStartTag() throws JspException { if (parent == null) return SKIP_BODY; if (!(parent instanceof SQLQueryTag)) return SKIP_BODY; SQLQueryTag myParent = (SQLQueryTag)parent; if (myParent.getRowCounter()==myParent.getMaxRows()-1 && myParent.getMaxRows() > 0) return EVAL_BODY_TAG; else return SKIP_BODY; } /** called at the end of the tag */ public int doEndTag() throws JspException { try { if (bodyContent != null) bodyContent.writeOut(bodyContent.getEnclosingWriter()); } catch(IOException ex) { throw new JspException("IO Error: " + ex.getMessage()); } return EVAL_PAGE; } /** called at the end of the body of the tag */ public int doAfterBody() throws JspException { return SKIP_BODY; } /** pageContext property setter method */ public void setPageContext(PageContext aPageContext) { pageContext = aPageContext; } /** parent property setter method */ public void setParent(Tag aParent) { parent = aParent; } /** parent property getter method */ public Tag getParent() { return parent; } /** clean up tasks */ public void release() { } /** current page context */ private PageContext pageContext; /** parent tag */ private Tag parent; } webshop\sqltags\EmptyTag.java package webshop.sqltags; import java.io.*; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** JSP tag for displaying query results. Used as a subtag * of <code>SQLQueryTag</code>. Renders its body once, at the end * of a result set. * * @see OutputStartTag * @see OutputEndTag * @see OutputTag
169
* @see SQLQueryTag * @author Branko Milosavljevic mbranko@uns.ns.ac.yu * @version 1.0 */ public class EmptyTag extends BodyTagSupport { /** called at the beginning of the tag */ public int doStartTag() throws JspException { if (parent == null) return SKIP_BODY; if (!(parent instanceof SQLQueryTag)) return SKIP_BODY; SQLQueryTag myParent = (SQLQueryTag)parent; if (myParent.getMaxRows() == 0) return EVAL_BODY_TAG; else return SKIP_BODY; } /** called at the end of the tag */ public int doEndTag() throws JspException { try { if (bodyContent != null) bodyContent.writeOut(bodyContent.getEnclosingWriter()); } catch(IOException ex) { throw new JspException("IO Error: " + ex.getMessage()); } return EVAL_PAGE; } /** called at the end of the body of the tag */ public int doAfterBody() throws JspException { return SKIP_BODY; } /** pageContext property setter method */ public void setPageContext(PageContext aPageContext) { pageContext = aPageContext; } /** parent property setter method */ public void setParent(Tag aParent) { parent = aParent; } /** parent property getter method */ public Tag getParent() { return parent; } /** clean up tasks */ public void release() { } /** current page context */ private PageContext pageContext; /** parent tag */ private Tag parent; }
170