You are on page 1of 175

Univerzitet u Novom Sadu Fakultet tehnikih nauka Katedra za raunarske nauke i informatiku

Branko Milosavljevi Milan Vidakovi

Java i Internet programiranje


Materijal za predmet Sintetski praktikum iz raunarstva

Novi Sad, 2001.

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

3.8 Primeri korienja standardnih komponenti 3.9 Apleti


3.9.1 Pojam apleta 3.9.2 Web itai i Java Plug-In 3.9.3. Apleti i komponente korisnikog interfejsa

53 57
57 58 59

3.10. Aplet i aplikacija istovremeno 3.11. Korisniki definisane komponente 3.12. JavaBeans

60 61 63

4. Mreno programiranje u Javi ................................................................................66 4.1. Osnovne karakteristike 66


4.1.1. Pojam socket-a 66

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

4.9. Zadatak: klijent i server za listanje sadraja direktorijuma

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

102 103 104 105 108


109

8.9 Pristup bazama podataka iz servleta

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

118 119 120 121

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

10.4. Enterprise JavaBeans


10.4.1. Session Beans 10.4.2. Entity Beans 10.4.3. Komunikacija klijenata sa EJB komponentama 10.4.4. Struktura EJB komponente

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

Literatura .....................................................................................................................152 Prilozi ...........................................................................................................................154

Poglavlje 0

Namena i program kursa


Kurs Java i Internet programiranje ima za cilj upoznavanje polaznika sa programskim jezikom Java i arhitekturom vieslojnih Internet/intranet sistema i odgovarajuim Java tehnologijama za njihovu implementaciju. Nakon zavrenog kursa polaznici su osposobljeni da samostalno produbljuju znanja iz prikazanih oblasti i da uestvuju u razvoju softerskih sistema koji su predmet kursa. Kurs je predvien za izvoenje u laboratorijskim uslovima, na odgovarajuoj raunarskoj opremi. Materijal za kurs ine slajdovi koji se prikazuju u toku izlaganja, Web sajt koji sadri primere prikazane tokom izlaganja, zadatke za vebu, literaturu koja se preporuuje za detaljnije prouavanje materije i potreban softver koji je u javnom vlasnitvu. Ovaj praktikum je, takoe, sastavni deo tog materijala.

0.1 Potrebno predznanje


Za polaznike kursa je neophodno da poseduju znanja iz sledeih oblasti: objektno-orijentisano programiranje: poznavanje osnovnih pojmova i koncepata (klasa, objekat, apstrakcija, nasleivanje, polimorfizam); konkurentno programiranje: pojmovi procesa i niti; rasporeivanje procesa, sinhronizacija procesa, nedeljive operacije; relacione baze podataka i SQL: poznavanje relacionog modela podataka, njegova implementacija u okviru sistema za upravljanje relacionim bazama podataka, upotreba jezika SQL za operacije nad bazom podataka; HTML: osnovni elementi strukture HTML dokumenata, rukovanje Web itaima;

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.

0.2 Program kursa


Kurs se sastoji iz vie tema koje obuhvataju obradu nove materije i vebanja. U ovom odeljku dat je saet pregled sadraja kursa po odgovarajuim temama.
1

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

Uvod u programski jezik Java


1.1 Java virtuelna maina
Specifikacija Jave obuhvata dve relativno nezavisne celine: specifikaciju programskog jezika Java i specifikaciju Java virtuelne maine (JVM). Specifikacija programskog jezika Java se ne razlikuje mnogo od slinih specifikacija za druge jezike sline namene. Meutim, JVM specifikacija predstavlja novinu u odnosu na druge rairene objektno-orijentisane programske jezike opte namene. Naime, JVM specifikacija predstavlja, zapravo, specifikaciju platforme za izvravanje Java programa u ijoj osnovi se nalazi programski model izmiljenog procesora. Programi napisani u programskom jeziku Java se prevode za ovakvu platformu za izvravanje. Samim tim, prevedeni programi se ne mogu pokretati direktno na nekoj konkretnoj raunarskoj platformi; potreban je poseban softver koji e takav prevedeni program da prilagodi konkretnoj maini i operativnom sistemu. Zapravo, potreban je odgovarajui interpreter. Kompanija koja je vlasnik jezika Java, Sun Microsystems, je stavila u javno vlasnitvo JVM interpreter, kompajler i skup drugih razvojnih alata grupisanih u paket pod nazivom Java Development Kit (JDK). U pitanju su alati koji se pokreu iz komandne linije i nude samo osnovni set funkcija za razvoj softvera. Sun je izdao JDK paket za nekoliko razliitih platformi: Windows, Solaris/ SPARC, Solaris/Intel i Linux/Intel. Kako je Java specifikacija (i sam jezik i JVM) javno dostupna, drugi proizvoai su proizveli svoje implementacije Jave za razliite platforme. Na primer, IBM nudi svoje verzije implementacije za veinu svojih hardversko/softverskih platformi, ali i za Linux na Intel mainama. Iako se najee programski jezik Java i Java virtuelna maina pominju u paru, kao dve komplementarne specifikacije, nema prepreka da se Java kod prevodi i za izvravanje na nekoj drugoj platformi (na primer, TowerJ paket generie Windows izvrni kod). Takoe, nema prepreka da se neki drugi jezici prevode za izvravanje u okviru Java virtuelne maine.

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).

1.2 Programski jezik Java


Iako je Java virtuelna maina sastavni deo specifikacije, o njoj se govori veoma retko; praktino je koriste samo autori kompajlera i JVM interpretera iza konkretne raunarske platforme. Sa druge strane, veina Java programera govori o drugom delu Java specifikacije, samom programskom jeziku Java, koji je i tema preostalog teksta u ovom poglavlju. Moe se rei da je Java objektno-orijentisani programski jezik opte namene, posebno pogodan za pisanje konkurentnih, mrenih i distribuiranih programa. Sva referentna dokumentacija za Javu nalazi se na jednom mestu sajtu firme JavaSoft (ogranak firme Sun Microsystems) http://java.sun.com. Knjiga Thinking in Java, (autor Bruce Eckel) se smatra za jednu od najboljih knjiga o samom jeziku, a dostupna je osim u klasinoj tampanoj formi i u elektronskom obliku koji je besplatan na http://www.bruceeckel.com.

1.3 Osnovni koncepti


Sintaksa Jave izuzetno podsea na sintaksu jezika C++, mada nije jednaka njoj. Sintaksna pravila nee biti posebno obraena, jer smatramo da su dovoljno oigledna iz primera koji slede. 1.3.1 Tipovi podataka Java operie sa dve vrste tipova podataka: primitivnim tipovima i objektima. Primitivni tipovi su tipovi koji se sreu i u drugim jezicima, npr. celobrojni tip, karakter, itd. Tabela 1.1 sadri spisak svih primitivnih tipova sa njihovim osnovnim karakteristikama.
Primitivni tip boolean char byte short int long Veliina 1-bit 16-bit 8-bit 16-bit 32-bit 64-bit Minimum Unicode 0 -128 -215 -231 -263 Maksimum Unicode 216-1 +127 +215-1 +231-1 +263-1

Primitivni tip float double void

Veliina 32-bit 64-bit -

Minimum IEEE 754 IEEE 754 Tabela 1.1. Primitivni tipovi

Maksimum IEEE 754 IEEE 754 -

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++.

1.4 Klase i objekti


Druga vrsta podataka sa kojima operie Java program su objekti. Objekti predstavljaju osnovni koncept objektno-orijentisane paradigme u modelovanju sistema. Svaki objekat realnog sistema koga posmatramo predstavljamo odgovarajuim objektom koji je sastavni deo modela sistema. Objekte koji zajednike osobine (ne moraju imati iste vrednosti tih osobina) moemo da opiemo klasom. U tom smislu, objekat je jedna instanca (primerak) svoje klase. Klasa, dakle, predstavlja model objekta, koji obuhvata atribute i metode. Sledi primer jedne Java klase:
class Automobil { boolean radi; void upali() { radi = true; } void ugasi() { radi = false; }

(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

a poziv metoda upali i ugasi mogao bi da izgleda kao u sledeem primeru:


a.upali(); b.ugasi();

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.

1.5 Prevoenje i pokretanje programa


Svaka Java klasa se moe prevesti nezavisno od ostalih elemenata programa. Komanda kojom se klasa Hello iz prethodnog primera prevodi je
javac Hello.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.

1.6 Reference na objekte


Kada smo u prethodnom primeru, u metodi main, napisali:
Automobil a; a = new Automobil();

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.

objekat klase Automobil

a stek

heap

Slika 1.1. Referenca koja ukazuje na objekat

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();

Situacija koja se nakon ovoga nalazi u memoriji je prikazana na slici 1.2.

objekat klase Automobil

b a stek

objekat klase Automobil heap

Slika 1.2. Dve refence koje ukazuju na dva objekta

Ako se sada izvri naredba


b = a;

u memoriji e biti sledea situacija (slika 1.3).

objekat klase Automobil

b a stek

objekat klase Automobil heap

Slika 1.3. Situacija nakon kopiranja referenci

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.

1.8 Kontrola toka programa


Za kontrolu toka programa na raspolaganju su standardne konstrukcije, preuzete iz jezika C++. Treba primetiti da postoje izvesne razlike izmeu Jave i jezika C++ i u ovom sluaju, pa za detaljnije informacije treba konsultovati specifikaciju jezika. Dostupne konstrukcije su sledee:
9

if ... else switch for while do ... while break continue

1.9 Inicijalizacija objekata


Prilikom konstruisanja novog objekta, nakon alokacije potrebne memorije za smetaj objekta, bie pozvana specijalna metoda namenjena za inicijalizaciju objekta nazvana konstruktor. Konstruktor obavezno ima naziv jednak nazivu klase, i nema nikakav povratni tip, ak ni void. Sledi primer klase koja ima konstruktor.
class A { A() { System.out.println("konstruktor"); } }

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.

1.10 Unitavanje objekata


U odeljku 1.6 ve je bilo rei o problemu uklanjanja nedostupnih objekata iz memorije. Za taj posao zaduen je poseban pozadinski proces koji se naziva garbage collector (GC). Ovaj proces radi nezavisno od pokrenutog programa, u smislu da sam odluuje u kom trenutku e iz memorije osloboditi koji nedostupni objekat. Pored automatske dealokacije memorije, GC je zaduen i za automatsku defragmentaciju memorije. Za razliku od jezika C++, Java klase ne mogu imati destruktore. Destruktori su specijalne metode koje se pozivaju neposredno pre uklanjanja objekta iz memorije. Svu potrebnu dealokaciju memorije u Javi obavlja GC proces. U trenutku dealokacije podrazumeva se da je Java objekat oslobodio ostale resurse koje je koristio (otvorene datoteke, mrene konekcije, itd). Ukoliko je
10

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..."); } }

1.11 Metode i njihovi parametri


Sintaksa definisanja metoda (u smislu navoenja njihovog imena, liste parametara i tipa rezultata) je nalik sintaksi u jeziku C++. Sledi primer jedne metode koja prima tri parametra, sa tipovima String, int i boolean, a vraa vrednost tipa void (tj. ne vraa rezultat).
void metoda(String name, int value, boolean test) { ... }

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

x.radi = false; test(x); // da li je atribut radi ovde true ili false?

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

Slika 1.4. Promena stanja objekta koji je parametar metode

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).

a=0 a=0 a) a=0 b)

a=1 a=0 c) a=0 d)

Slika 1.5. Promena vrednosti parametra metode koji je primitivnog tipa

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) { ... }

Preklapanje metoda se odnosi kako na klasine metode, tako i na konstruktore.

1.12 Kljuna re final


Kljuna re final se moe nai ispred definicije atributa ili metode unutar definicije klase. Ako se nae ispred atributa, oznaava atribut kome nije mogue promeniti vrednost. Drugim reima, final atribut predstavlja konstantu. Inicijalizacija prilikom deklaracije atributa je obavezna. Primer definicije jednog final atributa bio bi:
final int size = 100;

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

1.13 Kljuna re static


Kljuna re static se moe nai ispred definicije atributa ili metode, nezavisno od pojave kljune rei final. Kada se nae ispred definicije atributa, oznaava atribut koji pripada klasi, a ne objektima (kao instancama klase). Drugim reima, moe se rei da svi objekti date klase dele istu vrednost statikog atributa. Na primer, klasa StaticTest poseduje jedan statiki atribut:
class StaticTest { static int i = 0; static void metoda() { i++; } }

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

Slika 1.6. Inicijalizovan niz

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

Slika 1.7. Niz objekata pri emu objekti nisu inicijalizovani

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

Slika 1.8. Niz inicijalizovanih objekata

1.15 Viedimenzionalni nizovi


Viedimenzionalni nizovi se predstavljaju kao nizovi nizova. Sintaksa je slina jeziku C++. Na primer, definicija
15

int[][] a = { {1, 2, 3}, {4, 5, 6} };

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

Slika 1.9. Dvodimenzionalni niz

Viedimenzionalni niz se moe kreirati na sledei nain:


int[][] a = new int[2][3];

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

Slika 1.10 Viedimenzionalni niz iji elementi su objekti

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

1.16 Paketi, CLASSPATH i JAR arhive


1.16.1 Paketi Java programi se sastoje iskljuivo iz klasa. Broj klasa koje ine program moe biti relativno velik, pa je uvoenje nekakve organizacije u takav skup klasa neophodno. Paketi su nain da se klase grupiu po nekom kriterijumu. Paketi mogu da sadre klase ili potpakete, analogno odnosu direktorijuma i datoteka u okviru fajl-sistema. Svaka klasa mora da pripada nekom paketu. Ako se ne navede kom paketu pripada data klasa, podrazumeva se da pripada tzv. korenskom ili implicitnom paketu. Taj korenski paket nema posebno ime. On moe da sadri klase i potpakete, koji sa svoje strane takoe mogu da sadre klase i potpakete. Slika 1.11 prikazuje strukturu paketa nekog programa.

Slika 1.11 Struktura paketa u programu

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:

Tekst klase Tocak obavezno mora poeti odgovarajuom deklaracijom:


package paket2.paket3; class Tocak { ... }

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:

e pokrenuti na program D:\temp\korenski paket.

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.

Slika 1.12. Arhiva rt.jar otvorena programom WinZip

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

JIKESPATH bi trebalo da ima sledei sadraj:


JIKESPATH=.;C:\jdk1.3\jre\lib\rt.jar;D:\nekamojabibl.zip

1.17 Zadatak: klasa Matrix


Zadatak 1. Napisati klasu Matrix datu na slici 1. Implementacija metoda je ostavljena kao zadatak. Namena svake metode je opisana odgovarajuim kometarom u tekstu klase. Napomena: u realizaciji izostaviti provere ispravnosti parametara (odgovarajue dimenzije matrica).
class Matrix { /* Postavlja sadraj matrice. */ void setData(double[][] x) {...} /* Vraa sadraj matrice. */ int[][] 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. */ Matrix multiply(Matrix b) {...} /* Mnoi sadraj dve date matrice i rezultat mnoenja vraa kao rezultat metode. Obratiti panju da je ovo statika metoda! */ static Matrix multiply(Matrix a, Matrix b){...} /* Mnoi sadraj matrice objekta koji je pozvan (this) sa sadrajem matrice b (objekta koji je prosleen kao parametar). Rezultat mnoenja se smesta u matricu objekta koji je pozvan. Metoda ne vraa nikakav rezultat! */ void multiply2(Matrix b) {...} /* Polazna taka programa. Slui za testiranje funkcionalnosti ostalih metoda klase. Za potrebe testiranja formirati nekoliko

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.

1.19 Modifikatori pristupa


U Javi postoje sledea tri modifikatora pristupa:
public: oznaava da su atribut ili metoda vidljivi za sve klase u

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() { ... } }

Na slian nain modifikatori pristupa se mogu primeniti i na celu klasu, na primer:


public class Avion { ... }

22

1.20 Redefinisanje metoda


Redefinisanje metoda (method overriding) je postupak kada klasa naslednica redefinie telo metode nasleene od roditeljske klase. U Javi se to specificira prostim navoenjem nove definicije metode u klasi naslednici. Sledi primer:
class A { int metoda1() { System.out.println("metoda1 klase A"); } int metoda2() { System.out.println("metoda2 klase A"); } } class B extends class A { int metoda1() { System.out.println("metoda1 klase B"); } }

U sluaju da se izvri sledei segment koda:


A varA = new A(); B varB = new B(); varA.metoda1(); varB.metoda1(); varA.metoda2(); varB.metoda2();

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).

1.21 Apstraktne klase


Apstraktne klase su klase koje ne mogu imati svoje instance (objekte). Razlog za to je to je implementacija neke od metoda izostavljena. U primeru
public abstract class A { public void metoda1() { ... } public abstract void metoda2(); private int i; }

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.

1.23 Unutranje klase


Od Java verzije 1.1 klasa moe, osim atributa i metoda, da poseduje i tzv. unutranje klase (inner classes). Sledi primer:
class Spoljasnja { void metoda() { ... } class Unutrasnja { int metoda2() { ... } } }

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!

1.26 Klasa Object


Klasa Object predstavlja osnovnu klasu u hijerarhiji Java klasa, u smislu da sve klase nasleuju klasu Object. Za klase za koje se navede da ne nasleuju nijednu klasu (izostavljanjem extends klauzule) podrazumeva se da nasleuju klasu Object. Klasa Object nije apstraktna, tako da je mogue kreirati objekat ove klase. Ona definie neke metode koje se relativno esto koriste, poput ovih koje su opisane u nastavku ovog odeljka.
public boolean equals(Object o);

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();

Izraunava hash vrednost za dati objekat. Koristi se najvie u hash-tabelama.


public String toString();

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.

1.27 Klasa String


Klasa String se nalazi u paketu java.lang i predstavlja string kao tip podatka (seamo se da stringovi nemaju odgovarajui primitivni tip u Javi). U tom smislu, klasa String je kao i svaka druga Java klasa osim to ima donekle specijalan tretman od strane kompajlera. 1. Vrednosti primitivnih tipova se mogu predstaviti u Java programu odgovarajuim konstantama. Na primer, 16 je vrednost tipa int, vrednost 16L oznaava vrednost tipa long, 'x' je vrednost tipa char, itd. Objektima se ne moe pridruiti vrednost koja se moe predstaviti literalom, osim u sluaju objekata klase String. U tom smislu, "tekst" u Java programu predstavlja objekat klase String ija je vrednost inicijalizovana na dati tekst. Zbog toga je sasvim ispravno pisati
String x = "tekst";

to ima isti efekat kao i


28

String x = new String("tekst");

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());

Ovaj poziv metode handleMessage bie, u stvari, preveden u sledei poziv:


handleMessage(new Automobil().toString());

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.

1.28 Primeri nekih klasa iz standardne biblioteke


Namena ovog odeljka je da ilustruje korienje nekih od klasa koje su sastavni deo standardne Java biblioteke, a koriste se esto u praksi. Prednost postojanja ovako bogate standardne biblioteke je i u tome to se sa sigurnou zna da su

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()); } } }

1.29 Konvencije davanja imena


Ovaj odeljak navodi neke od opteprihvaenih konvencija za davanje imena identifikatorima u Java programima. 1. Nazivi klasa se piu malim slovima, osim to poinju velikim slovom (npr. Automobil, Vector). Ne postoji nikakav prefiks (kao CVector ili TVector). Ukoliko se naziv klase sastoji iz vie rei, rei se spajaju, a svaka od njih poinje velikim slovom (npr. StringTokenizer, ArrayIndexOutOfBoundsException). 2. Nazivi metoda i atributa se piu malim slovima (npr. size, width). Ako se sastoje od vie rei, rei se spajaju, pri emu sve rei poevi od druge poinju velikim slovom (npr. setSize, handleMessage). 3. Nazivi paketa se piu iskljuivo malim slovima. Ukoliko se sastoje iz vie rei, rei se spajaju (npr. mojpaket, velikipaket.malipaket). Detaljan opis konvencija nalazi se na adresi http://java.sun.com/docs/codeconv/index.html.

1.30 Generisanje programske dokumentacije i javadoc


Standardna JDK distribucija sadri i alat javadoc namenjen generisanju programske dokumentacije. Kao izvor informacija za generisanje dokumen31

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

Opisuje parametar metode.


@param ime opis

Opisuje rezultat metode.


@return opis

Opisuje izuzetak koji moe da izazove metoda.


@throws puno-ime-klase opis

Referenca na neku drugu klasu, metod ili atribut.


@see ime-klase @see puno-ime-klase @see puno-ime-klase#ime-metode

@author @version @since

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;

1.31. Zadaci: modifikacije klase Matrix


Zadatak 2. Proiriti zadatak 1 tako da se u okviru metoda klase Matrix vri provera dimenzija matrice na odgovarajui nain. Signalizaciju greke realizovati pomou mehanizma izuzetaka. Napisati sopstvenu klasu MathException za obradu izuzetaka ove vrste.

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

Konkurentno programiranje u Javi


Java je programski jezik koji sadri koncepte potrebne za pisanje konkurentnih programa. Izvravanje ovakvih programa zavisi, naravno, od koriene Java virtuelne maine, operativnog sistema, i hardverske platforme, ali su te zavisnosti sklonjene od Java programera. Drugim reima, isti konkurentni program se moe izvravati na razliitim raunarskim platformama bez modifikacija. Pisanje konkurentnih programa obino zahteva korienje odgovarajuih funkcija operativnog sistema, to takve programe ini teko prenosivim na razliite operativne sisteme. Svaki Java program je, zapravo, konkurentan program: garbage collector, o kome je bilo rei u prethodnom poglavlju, se izvrava kao posebna nit programa. Na izvravanje garbage collector-a nemamo velikog uticaja, a u pisanju konkurentnih programa moemo zanemariti injenicu da je garbage collector aktivan u okviru posebne niti.

2.1 Kreiranje programskih niti


Programska nit (thread) se u Javi predstavlja objektom klase Thread ili njene naslednice. Klasa Thread je, u sutini, roditeljska klasa koju je potrebno naslediti prilikom definisanja nove programske niti. Sam programski kod koji definie rad niti je smeten unutar metode run ovakve klase. Sledi primer novodefinisane niti:
public class MojThread extends Thread { public void run() { // programski kod niti je ovde } }

(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

MojThread mt = new MojThread(); mt.start();

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

MojThread mt = new MojThread(); mt.start();

Slika 2.1 Pokretanje nove niti

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.

2.2 Daemon i non-daemon niti


Java programi razlikuju dve vrste programskih niti: tzv. daemon i non-daemon niti. Nit se proglaava za daemon-nit tako to se pozove metoda setDaemon(true), a non-daemon-nit tako to se pozove setDaemon(false). Sutinska razlika izmeu ove dve vrste niti je u tome to e se Java program zavriti kada se okonaju sve njegove non-daemon niti. Inicijalno program startuje sa jednom non-daemon niti koja poinje svoje izvravanje metodom main. Daemon-niti su namenjene za obavljanje zadataka u pozadini, ije kompletno izvravanje nije od znaaja za rad programa.
36

2.3 Primer programa sa vie niti


Posmatrajmo sledei program koji se sastoji iz klasa PrviThread i ThreadTest:
public class ThreadTest { /** Broj niti koje ce se pokrenuti */ public static final int THREAD_COUNT = 10; /** Pokrece sve niti i zavrsava sa radom */ public static void main(String[] args) { for (int i = 0; i < THREAD_COUNT; i++) new PrviThread(i).start(); System.out.println("Threads started."); }

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.

Slika 2.2. Izvravanje programa ThreadTest

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

2.4 Sinhronizacija niti


Prethodni primer predstavlja program u kome izvravanje jedne niti ne utie na izvravanje ostalih niti (osim to ta nit konkurie za zauzee procesora). Konkurentni programi ovakve vrste su relativno retki. Kada je potrebno da dve niti komuniciraju, komunikacija se mora obaviti putem zajednikog (deljenog) resursa. U Javi je u pitanju zajedniki objekat kojem obe niti mogu da pristupe. Kako niti dobijaju deo procesorskog vremena na osnovu odluke Java virtuelne maine i operativnog sistema, ne moemo biti sigurni da jedna nit u toku pristupa deljenom objektu nee biti prekinuta i kontrola biti predata drugoj niti koja isto tako moe poeti da pristupa deljenom objektu i izazvati greke prilikom nastavka izvravanja prve niti (koja u objektu zatie drugaije stanje u odnosu na trenutak kada je bila prekinuta). Zbog toga je neophodno koristiti mehanizam zakljuavanja objekata koji obezbeuje da najvie jedna nit moe da pristupa deljenom objektu u nekom periodu vremena. Ovaj mehanizam je u Javi implementiran pomou tzv. synchronized blokova. Synchronized blok izgleda kao u sledeem primeru:
synchronized (obj) { // obj je deljeni objekat; // imamo ekskluzivno pravo pristupa njemu // unutar ovog bloka }

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

3) yes 5) lock wait

sync {

}
6) unlock

Slika 2.4. Pristup deljenom objektu iz dve niti

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.

2.5 Dodatne metode za sinhronizaciju


Nekad je potrebno da nit saeka na neki dogaaj, iako se nalazi unutar synchronized bloka. To ekanje moe da traje proizvoljno dugo, pa bi u tom sluaju pristup zakljuanom objektu bio nemogu u proizvoljno dugakom intervalu vremena. Metoda wait (nasleena iz klase Object, tako da je dostupna u svim klasama) radi sledee: oslobaa zauzeti objekat i blokira izvravanje niti sve dok neka druga nit ne pozove metodu notify nad istim objektom. Metoda notify (takoe nasleena iz klase Object) obavetava nit koja je (hronoloki) prva pozvala wait da moe da nastavi sa radom. Nit koja je ekala u wait metodi nee odmah nastaviti izvravanje, nego tek nakon to nit koja je pozvala notify ne izae iz svog synchronized bloka (slika 2.5). Metoda notifyAll obavetava sve niti koje ekaju u wait da mogu da nastave sa radom. Nakon izlaska iz synchronized bloka sve te niti e konkurisati za procesorsko vreme.
nit A sync { wait();
waiting for notify notify waiting for lock

nit B

obj sync { notify();

} }

Slika 2.5. Mehanizam wait/notify

Ove tri metode mogu biti pozvane samo unutar synchronized bloka i to nad objektom nad kojim se vri sinhronizacija.

2.6 Primer programa sa sinhronizacijom niti


Posmatrajmo dve niti, jednu koja proizvodi podatke i drugu koja ih troi. One komuniciraju preko deljenog objekta koji je zapravo bafer za podatke. Potrebno je omoguiti da se punjenje i pranjenje bafera odvijaju u paralelnim nitima (slika 2.6). Bafer predstavlja klasinu implementaciju krunog bafera. Nit proizvoa e puniti bafer (osim ako ve nije pun, tada mora da eka), a nit potroa e
40

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

private int writePos;

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(); } }

2.7 Zadatak: problem pet filozofa


Zadatak 5. Napisati program koji simulira problem pet filozofa. Objanjenje: Posmatra se okrugli sto za kojim sedi pet filozofa. Izmeu njihovih tanjira nalazi se pet tapia za jelo. Kako su za obedovanje potrebna dva tapia, nije mogue obezbediti da svih pet filozofa obeduje istovremeno. Svaki od filozofa e zauzeti jedan od njemu potrebnih tapia im ovaj bude slobodan. Ovaj problem moe da ilustruje nastanak deadlock-a. Slika 2.7 prikazuje ovaj problem.

Slika 2.7. Ilustracija problema pet filozofa

44

Poglavlje 3

GUI aplikacije i JavaBeans


3.1 AWT i Swing
Programski jezik Java je, u svojoj inicijalnoj verziji, posedovao biblioteku komponenti za izgradnju grafikog korisnikog interfejsa (GUI) zvanu Abstract Window Toolkit (AWT). U pitanju je biblioteka koja se zasniva na korienju komponenti korisnikog interfejsa koje su dostupne na platformi na kojoj se program pokree (Windows, Motif, Macintosh, itd). To znai da je implementacija AWT komponenti razliita za svaki operativni sistem. Java klase koje predstavljaju AWT komponente koriste su u velikoj meri native programski kod koji je vrio interakciju sa operativnim sistemom. Na primer, AWT klase u Windows distribuciji Java virtuelne maine koriste awt.dll datoteku. Aplikacije koje koriste AWT komponente izgledaju kao da su pisane u bilo kom drugom jeziku na datom operativnom sistemu. Ovakav koncept ima za posledicu da je za skup GUI komponenti bilo neophodno izabrati samo one komponente koje postoje u svim operativnim sistemima na kojima e se Java programi izvravati. To dalje znai da je ovakav skup komponenti vrlo siromaan. Umesto da se postigne cilj da Java GUI aplikacije izgledaju jednako kao i sve druge aplikacije, postiglo se da one izgledaju jednako osrednje na svim platformama zbog siromanog skupa komponenti od kojih mogu biti sainjene. U vreme kada je bila aktuelna Java verzija 1.1, poet je razvoj na novoj biblioteci GUI koponenti koja je imala drugaiji koncept: kompletna biblioteka je napisana u Javi, to znai da se komponente samostalno iscrtavaju na ekranu umesto da ih iscrtava operativni sistem. Posledica toga je da GUI aplikacije izgledaju isto na svim operativnim sistemima i da nema ogranienja na broj i tip GUI komponenti koje e ui u biblioteku. Naziv biblioteke u toku njenog razvoja bio je Swing, i to ime se zadralo i kasnije. Biblioteka je zamiljena tako da izgled komponenti na ekranu bude promenljiv, zavisno od izabrane teme (look-and-feel). Tako je u startu implementirano tri look-and-feel modula: Windows (sve komponente izgledaju kao odgovarajue Windows komponente), Motif (GUI okruenje na UNIX-u) i Metal (izgled svojstven samo Java aplikacijama, i, mogue, nekom buduem Java opera-

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.

3.2 Event-driven model


Kae se da je kod GUI aplikacija korisniki interfejs upravljan dogaajima (event-driven). To znai da se program sastoji od relativno nezavisnih segmenata koji su namenjeni za obradu odgovarajueg dogaaja (koga je najee izazvao korisnik). Na primer, klik miem, kucanje na tastaturi, itd. su dogaaji korisnikog interfejsa na koje program reaguje u okviru svojih obraivaa dogaaja posebnih funkcija pisanih za tu namenu. Event-driven model ima za posledicu da se program sastoji iz odreenog inicijalizacionog bloka i raznih obraivaa dogaaja. Sam program se ne izvrava linearno od gore prema dole nego se izvrava samo u odreenim vremenskim intervalima. To su momenti pokretanja aplikacije (kada se vri inicijalizacija programa) i reakcije na dogaaje (kada se vri obrada dogaaja). Sve ostalo vreme je vreme koje koristi operativni sistem ili druge aplikacije koje rade po istom principu. Inicijalizacioni blok se u Java GUI programima izvrava poevi od metode main, dakle na nain koji smo i do sada koristili. Dogaaji se opisuju tzv. xxxEvent klasama. Za svaku vrstu dogaaja definisana je posebna klasa. Na primer, pritisku na taster odgovara klasa KeyEvent, pomeranju mia odgovara klasa MouseEvent, itd. Sve xxxEvent klase nasleuju klasu Event, slino kao kod raznih exception klasa o kojima je bilo rei u prvom poglavlju. Kada se dogodi neki dogaaj, kreira se objekat odgovarajue klase koji opisuje taj dogaaj, i zatim se taj objekat prosleuje onima koji su se registrovali da oslukuju taj dogaaj. Oslukivai dogaaja su instance neke od xxxListener klasa. Na primer, oslukiva za KeyEvent dogaaj je KeyListener, itd. Mehanizam oslukivaa je uveden u Java verziji 1.1. Pre toga korien je drugaiji mehanizam za obradu dogaaja ija se upotreba danas ne preporuuje.

3.3 Osnovna struktura GUI aplikacije


Svaka Java aplikacija poinje svoje izvravanje metodom main. Tako i GUI aplikacija poinje svoje izvravanje ovom metodom, ali se najee tom prilikom odmah inicijalizuje i glavni prozor aplikacije koji se potom prikae na ekranu.
46

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).

3.4 Razlika u konstrukciji GUI-ja za Windows i Java aplikacije


Operativni sistem Windows koristi tzv. resurse za opis izgleda elemenata korisnikog interfejsa. Resurs je, zapravo, deklarativni opis izgleda nekog elementa korisnikog interfejsa. Takav opis se moe formirati u tekstualnoj datoteci odgovarajueg formata, ali se moe i nacrtati primenom odgovarajueg alata. Tzv. vizuelna okruenja za razvoj softvera su i zamiljena tako da omogue programeru da nacrta izgled svoje aplikacije. Crtanje aplikacije u Windows okruenju predstavlja definisanje resursa. Tim resursima (dugmadima, tekstualnim poljima, itd.) se kasnije mogu pridruiti programski elementi preko kojih e se odvijati pristup resursima (to, na primer, radi Class Wizard u paketu Visual C++). U programskom jeziku Java (i nekim drugim prozorskim operativnim sistemima) izgled korisnikog interfejsa se ne definie deklarativno, pomou resursa, nego programski: komponente interfejsa se nanose na prozor u odreenom segmentu programa (tipino u konstruktoru prozorske klase) pozivima odgovarajuih metoda.

3.5 Dodavanje komponenti na prozor


Pogledajmo sledei primer:
import java.awt.*;

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.

Slika 3.1. Elementarna GUI aplikacija

3.6 Prostorni raspored komponenti


U prethodnom primeru vidi se da su dva dugmeta postavljena na prozor u toku inicijalizacije (u konstruktoru), ali nigde nije eksplicitno specificirano bar na prvi pogled gde e stojati i kako e izgledati, kakve e im biti dimenzije, itd. Za odreivanje ovih karakteristika komponente zaduen je objekat koji se naziva layout manager. U pitanju je instanca neke od xxxLayout klasa. Svaki kontejner ima sebi asociran layout manager. Podrazumevani layout manager je objekat klase BorderLayout. On rasporeuje komponente u okviru kontejnera u zone koje izgledaju kao na slici 3.2.
NORTH

WEST

CENTER

SOUTH

48

EAST

Slika 3.2. Zone BorderLayout manager-a

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)

Slika 3.3. Primer korienja FlowLayout manager-a

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)

Slika 3.4. Primer korienja Borland-ovog XYLayout manager-a

3.7 Rukovanje dogaajima


3.7.1 Dogaaji, oslukivai i komponente U odeljku 3.2 ve je bilo rei o rukovanju dogaajima. Da podsetimo, dogaaji su predstavljeni objektima xxxEvent klasa, a oslukivai dogaaja su odgovarajui xxxListener objekti. xxxListener-i su, zapravo, interfejsi: oslukiva mora biti instanca neke klase koja implementira taj interfejs. Na primer, klasa koja implementira ActionListener interfejs moe da poslui kao obraiva dogaaja za klik miem na dugme. Sledi jedna takva klasa:
import java.awt.*; import java.awt.event.*; public class MyListener implements ActionListener { public void actionPerformed(ActionEvent ev) { System.exit(0);
50

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

3.8 Primeri korienja standardnih komponenti


U prethodnim primerima koriena je komponenta dugme i dogaaj klik na dugme (predstavljeni klasama JButton i ActionEvent). Biblioteka sadri veliki broj komponenti za izgradnju korisnikog interfejsa i veliki broj dogaaja na koje se moe reagovati. Neke od najee korienih komponenti su pobrojane u tabeli 3.1.
Klasa ButtonGroup JButton JCheckBox JComboBox JDialog JFrame JLabel JList JMenu JMenuBar JMenuItem JOptionPane JPanel JRadioButton JTabbedPane JTextArea JTextField Opis povezuje vie radio button-a da rade zajedno; nije vidljiva komponenta dugme check box combo box dijalog (prozor kome se ne moe menjati veliina) prozor labela list box meni linija menija stavka menija prozor koji ispisuje krau poruku (message box) komponenta koja je kontejner za druge komponente radio button kartice (tabs); pojedine kartice se na ovu komponentu dodaju kao JPanel-i vielinijsko polje za unos teksta (memo) jednolinijsko polje za unos teksta
Tabela 3.1. Najee koriene GUI komponente

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!")); } }

Pogledajmo primer jednog elementarnog apleta:

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.

Slika 3.5. Aplet prikazan u okviru HTML stranice

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"); } } }

Iz ovog primera vidimo da je rukovanje dogaajima identino kao u aplikacijama.

3.10 Aplet i aplikacija istovremeno


Iz prethodnih primera smo videli da je rasporeivanje komponenti na aplet i rukovanje dogaajima identino kao i kod klasinih grafikih aplikacija. Zato i nema puno tehnikih razlika u razvoju aplikacije i apleta. Sada sledi primer kako napisati program koji moe biti i aplet i aplikacija, zavisno od toga na koji nain se pokrene. Poto je u pitanju aplet, osnovna klasa mora naslediti klasu JApplet. A kako je u pitanju i aplikacija, dodaemo ovoj klasi metodu main.
import import import public java.awt.*; java.awt.event.*; javax.swing.*; class AppletApplicationTest extends JApplet {

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.

3.11 Korisniki definisane komponente


Swing biblioteka obezbeuje relativno bogat izbor komponenti za izgradnju korisnikog interfejsa, ali to ponekad ne mora biti dovoljno. Pisanje novih komponenti koje e imati neke specifine mogunosti je relativno jednostavno. Potrebno je naslediti neku od postojeih komponenti i redefinisati potrebne
61

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

definisanu komponentu. Komponente se konstruiu i dodaju na povrinu apleta na uobiajen nain.


import java.awt.*; import javax.swing.*; public class UserDefinedTest extends JApplet { public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(u1); cp.add(u2); } UserDefined u1 = new UserDefined("Tekst u komponenti br. 1"); UserDefined u2 = new UserDefined("Tekst u komponenti br. 2"); }

Slika 3.6 prikazuje izgled ovog apleta.

Slika 3.6. Aplet sa korisniki definisanim komponentama

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)

Slika 3.6. Svojstva i dogaaji komponente u JBuilder-u

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

Mreno programiranje u Javi


4.1 Osnovne karakteristike
Pod mrenim programiranjem u programskom jeziku Java podrazumeva se pisanje programa koji komuniciraju sa drugim programima preko raunarske mree. Zahvaljujui konceptu prenosivog izvrnog koda, pisanje ovakvih programa je istovetno na razliitim hardversko/softverskim platformama. Komunikacija putem raunarske mree u Java programima podrazumeva korienje IP mrenog protokola. Drugi protokoli protokoli (npr. Novell IPX) nisu podrani. Standardna Java biblioteka poseduje klase za komunikaciju preko ovakve mree korienjem TCP i UDP protokola. Ovde e biti rei samo o komunikaciji putem TCP protokola. Komuniciranje izmeu dve maine odvija se putem tokova (streams): svaka konekcija izmeu dva programa je dvosmerna za svakog od njih, u smislu da oba programa koji uestvuju u konekciji koriste stream za itanje i stream za pisanje. Stream-ovi se koriste na isti nain kao to se koriste prilikom rada sa datotekama u okviru fajl-sistema. Klase standardne biblioteke namenjene za pristup mrenim funkcijama nalaze se u paketu java.net, a familija stream klasa koja se takoe koristi nalazi se u paketu java.io. 4.1.1 Pojam socket-a Za vezu izmeu dva programa na mrei karakteristian je pojam socket-a. Socket zapravo predstavlja ureeni par (IP adresa, port) jednog uesnika u komunikaciji. Uspostavljena veza izmeu dva programa je zapravo skup dva socket-a. Slika 4.1 ilustruje uspostavljenu vezu izmeu dva programa sa stanovita socket-a.
program A (147.91.177.196, 7534) program B (204.1.177.96, 9000)

Slika 4.1. Uspostavljena veza izmeu dva programa

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

vor Z (147.91.177.195) program D

9864

Slika 4.2. Sluaj vie uspostavljenih veza izmeu programa

4.2 Identifikacija vorova mree


Identifikator vora u IP mrei je IP adresa 32-bitni broj. Ovakve adrese se esto, radi lakeg pamenja, piu u formatu koji se sastoji od etiri decimalno zapisana okteta razdvojena takom (na primer, 147.91.177.196). Java standardna biblioteka poseduje klasu InetAddress koja predstavlja IP adresu. Kreiranje objekta ove klase se najee obavlja pozivom statike metode getByName. Ova metoda prima string parametar koji sadri bilo IP adresu zapisanu u oktetima, bilo simboliku adresu (npr. java.sun.com). Sledi primer:
InetAddress a = InetAddress.getByName("java.sun.com"); InetAddress b = InetAddress.getByName("147.91.177.196");

Statika metoda getLocalHost generie InetAddress objekat koji predstavlja adresu maine na kojoj se program izvrava:
InetAddress c = InetAddress.getLocalHost();

4.3 Klasa Socket


Objekti klase java.net.Socket predstavljaju uspostavljene TCP konekcije. To znai da se prilikom kreiranja objekta klase Socket vri uspostavljanje veze. Tipino se otvaranje konekcije vri na jedan od sledeih naina:
Socket s1 = new Socket(addr, 25); // addr je InetAddress objekat
67

Socket s2 = new Socket("java.sun.com", 80);

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();

4.4 Tipian tok komunikacije klijent strana


Kao rezime prethodnog odeljka, ovde se izlae tipin scenario komunikacije dva programa, podrazumevajui ovde klijent-stranu (tj. klijentski program). Uloga klijenta u klijent/server komunikaciji podrazumeva nekoliko stvari: Klijent inicira komunikaciju. Nakon uspostavljanja veze, komunikacija se obino svodi na niz parova zahtev/odgovor poruka. Zahteve alje klijent, a odgovore server. Klijent prekida komunikaciju.

Ovakva sekvenca aktivnosti moe biti predstavljena sledeim segmentom programa:


// inicijalizacija Socket s = new Socket(addr, port); BufferedReader in = new BufferedReader(...,s.getInputStream()); PrintWriter out = new PrintWriter(...,s.getOutputStream());

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();

4.5 Klasa ServerSocket


Klasa java.net.ServerSocket koristi se na serverskoj strani. Glavna metoda u ovoj klasi je accept metoda koja blokira izvravanje programa sve dok neki klijent ne uspostavi vezu na portu na kome ServerSocket oekuje klijente. Objekti klase ServerSocket kreiraju se na standardan nain, operatorom new. Parametar konstruktora je port na kome e server oekivati klijente; kao IP adresa se podrazumeva IP adresa lokalne maine. Sledi primer:
ServerSocket ss = new ServerSocket(9000);

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();

4.6 Tipian tok komunikacije server strana


Imajui u vidu prethodne odeljke, tipian scenario ponaanja serverskog programa je sledei: 1. Konstrukcija ServerSocket objekta. 2. Oekivanje klijenta metodom accept. 3. Komunikacija sa klijentom: a. Inicijalizacija stream-ova b. Komuniciranje po principu prijem zahteva/slanje odgovora. c. Zavravanje komunikacije oslobaanje resursa. Ovakav scenario moe se predstaviti sledeim segmentom programa:
// ekam klijenta... ServerSocket ss = new ServerSocket(port); Socket s = ss.accept(); // inicijalizacija BufferedReader in = new BufferedReader(...,s); PrintWriter out = new PrintWriter(...,s); // komunikacija

69

String request = in.readLine(); out.println(odgovor); // prekid veze in.close(); out.close(); s.close();

// itam zahtev // aljem odgovor

4.7 Server koji opsluuje vie klijenata


Prethodni primer je prikazao serverski program koji komunicira sa jednim klijentom nakon to ga server saeka, komunikacija izmeu klijenta i servera se obavi i potom zavri. Ovakvi serverski programi su vrlo retki serveri se konstruiu tako da mogu da opsluuju vie klijenata i to istovremeno. Potreba da server komunicira sa vie klijenata istovremeno se moe reiti uvoenjem posebnih programskih niti za komunikaciju sa klijentima, tako da se sa svakim klijentom komunikacija obavlja u posebnoj programskoj niti. Dakle, za n istovremenih klijenata postojae n ovakvih programskih niti. Sa stanovita implementacije u programskom jeziku Java, ove niti predstavljene su odgovarajuom klasom koja nasleuje klasu Thread, kao u sledeem primeru:
// obrada pojedinanog zahteva class ServerThread extends Thread { public void run() { // inicijalizacija // komunikacija // prekid veze } }

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); }

4.8 Primer klijent/server komunikacije


Imajui u vidu dosadanje izlaganje, moemo konstruisati jednostavnu klijent/server aplikaciju. Zadatak klijenta u sledeem primeru je sledei: 1. 2. 3. 4. 5. Uspostavlja vezu sa serverom. alje zahtev serveru (tekst HELLO). ita odgovor servera. Ispisuje odgovor na konzolu. Zavrava komunikaciju.
70

Klijentski program je predstavljen klasom Client1.


import java.io.*; import java.net.*; public class Client1 { public static final int TCP_PORT = 9000; public static void main(String[] args) { try { // odredi adresu racunara sa kojim se povezujemo // (povezujemo se sa nasim racunarom) InetAddress addr = InetAddress.getByName("127.0.0.1"); // otvori socket prema drugom racunaru Socket sock = new Socket(addr, TCP_PORT); // 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); // posalji zahtev System.out.println("[Client]: HELLO"); out.println("HELLO"); // procitaj odgovor String response = in.readLine(); System.out.println("[Server]: " + response); // zatvori konekciju in.close(); out.close(); sock.close(); } catch (UnknownHostException e1) { e1.printStackTrace(); } catch (IOException e2) { e2.printStackTrace(); } } }

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

4.9 Zadatak: klijent i server za listanje sadraja direktorijuma


Zadatak 6. Napisati klijent/server aplikaciju koja omoguava listanje sadraja direktorijuma sa servera na klijentu. Na klijentov zahtev koji sadri putanju direktorijuma na serveru koga treba izlistati, server formira spisak i vraa ga klijentu kao odgovor. Klijent i server nemaju potrebe za GUI interfejsom. Komentar: Jasno je da e klijent i server izgledati nalik klijentu i serveru koji su prikazani u prethodnom primeru. Ono to je neophodno uraditi prvo, je definisati protokol komunikacije klijenta i servera, pre svega format poruka koje se alju tokom komunikacije. Za listanje sadraja direktorijuma treba pogledati kako se koristi klasa java.io.File i njene metode exists, isDirectory i list. Odgovor servera bi trebalo da na odgovarajui nain reaguje na situacije kada traeni direktorijum ne postoji, ili kada je u pitanju fajl, a ne direktorijum.

74

Poglavlje 5

Veba: chat aplikacija


Zadatak 7. Napisati klijent/server GUI aplikaciju koja moguava chat za sve korisnike koji se prijave na isti server. Zadatak klijenta je da omogui unos novih i pregled pristiglih poruka (sa podacima o poiljaocu poruke). Zadatak servera je da poruke poslate od strane jednog klijenta prosledi do ostalih klijenata (ne i poiljaocu). Svaki korisnik u sistemu je jednoznano odreen svojim korisnikim imenom (username) koga bira prilikom prijavljivanja na sistem. Odmah nakon pokretanja klijenta korisnik mora da se prijavi na eljeni server (odreen svojom simbolikom ili numerikom adresom) pod odreenim korisnikim imenom. Naredne slike prikazuju jedan od moguih izgleda ovakve aplikacije; ovakav izgled nije obavezan.

Slika 5.1. Login forma klijenta

Slika 5.2. GUI interfejs servera

75

Slika 5.3. GUI interfejs klijenata

5.1 Uvodna razmatranja


U ostatku ovog poglavlja prikazuje se jedno mogue reenje zadatka. Potrebno je izgraditi klijent/server sistem gde server mora da opsluuje vie korisnika istovremeno. Na prvi pogled, sistem lii na primer iz prethodnog poglavlja. Meutim, ovaj sistem je neto sloeniji. Njegove osnovne osobine su sledee: Klijent moe da alje poruke serveru, to se inicira akcijom nad korisnikim interfejsom. Klijent moe da prima poruke od servera u proizvoljnom vremenskom trenutku, to je posledica slanja poruke od strane nekog drugog klijenta. Server moe da prima poruke od strane vie klijenata istovremeno, i to u sluajnim vremenskim trenucima. Server mora da prosledi poruku jednog klijenta svim ostalim klijentima.

5.2 Funkcije klijenta


Posmatrajmo sada klijenta. On mora biti u mogunosti da reaguje na pristiglu poruku tako to e je prikazati u odgovarajuoj komponenti korisnikog interfejsa. Poruka moe da pristigne u bilo kom trenutku. Dakle, klijent mora neprestano oslukivati da li server alje poruku. Kako realizovati ovakvu reakciju? Razmotrimo sledeu mogunost: klijent ima tano jednu konekciju sa serverom, koja se raskida po zavretku rada. Postoje dve posebne niti, za itanje poruka sa mree i za pisanje poruka na mreu. itaka nit eka poruke servera, a pisaka nit alje serveru one poruke koje je korisnik uneo

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

actionPerfomed() setMessage() notify() writeln()

Slika 5.4. Klijent inicira slanje poruke

readerThread

inputStream

textArea

readLine()

append()

Slika 5.5. Klijent prima poruku od servera

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

Slika 5.6. Klase koje uestvuju u komunikaciji sa serverom

ChatClient

JTextField

LoginDlg JButton

JTextArea

ReaderThread

Slika 5.7. Klase koje uestvuju u radu korisnikog interfejsa

5.3 Funkcije servera


Serverska aplikacija kreira posebne niti za komunikaciju sa svakim korisnikom, i to po dve niti itaku i pisaku. itaka nit je zaduena da oekuje poruke od klijenta, a pisaka da klijentu prenese poruku koju je poslao neki drugi klijent. Pisaka nit alje poruku kada je dobije u svoj deljeni kontejnerski objekat. Situacija kada itaka nit primi poruku i prosledi je svim itakim nitima preko odgovarajuih ActiveClient objekata je prikazana na slici 5.8. Klijent otpoinje komunikaciju prijavljivanjem na server. Dakle, prvo to e server primiti od klijenta je poruka kojom se vri prijavljivanje. Prijavljivanje moe biti uspeno ili neuspeno (u sluaju da se neko ve prijavio pod tim korisnikim imenom). Slika 5.9 opisuje proces uspenog prijavljivanja.

78

readerThread

inputStream

ClientUtils

activeClient

writerThread

outputStream

readLine()

sendMessageToAll() setMessage() notify() writeln()

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

ServerListener PrintWriter BufferedReader ClientUtils

WriterThread

ActiveClient

ReaderThread

Slika 5.10. Klase koje uestvuju u komunikaciji

Kompletan programski kod klijentske i serverske aplikacije dat je u prilogu.

80

Poglavlje 6

Rad sa bazama podataka JDBC


6.1 Osnovne odrednice
Java ima definisan standardni interfejs za komunikaciju sa bazama podataka nazvan JDBC. Njegov naziv namerno podsea na ODBC (Open Database Connectivity) interfejs, jer im je i koncept slian. Naime, JDBC definie skup klasa i interfejsa koji se koriste za pristup bazama podataka. Podrazumeva se da se koristi sistem za upravljanje relacionim bazama podataka (SUBP) sa kojim se komunicira putem jezika SQL. Za komunikaciju sa serverima najee se koristi TCP/IP mrea. Baze zasnovane na nekom drugaijem modelu podataka, na primer objektno-orijentisanom ili mrenom modelu, nisu obuhvaene ovim standardom. To ne znai da se njima ne moe pristupati, ve samo da se za tu namenu nee koristiti JDBC interfejs. Za pristup konkretnoj bazi podataka putem JDBC-a potrebna je odgovarajua biblioteka. Biblioteka, u ovom sluaju, predstavlja obinu Java biblioteku klasa tipino spakovanu u jedan JAR fajl. Takva biblioteka se naziva JDBC drajver. Korienje standardnih klasa i interfejsa za komunikaciju sa SUBP ini jednostavnijom modifikaciju programa za rad sa nekim drugim SUBP. Idealno bi bilo kada program ne bi pretrpeo nikakve izmene u tom sluaju. Korienje JDBC-a je blisko ovom idealu: izmene je potrebno praviti samo u sluaju da su koriene nestandardne SQL naredbe (odnosno naredbe koje nisu podrane kod drugih SUBP). Sve JDBC klase i interfejsi definisani su u standardnom paketu java.sql. JDBC interfejs je deo standardne Java biblioteke od Java verzije 1.1. JDBC interfejs ima svoje oznake sopstvenih verzija; Java 1.1 sadri JDBC verziju 1.22, dok Java 1.2 sadri JDBC verziju 2.0. Verzija 2.0 sadri neka proirenja u odnosu na verziju 1.22, ali se i stariji drajveri obino mogu koristiti u okviru novijih verzija Jave.

6.2 JDBC drajveri


JDBC drajveri su klasine Java biblioteke; da bi se koristile unutar nekog Java programa nije potrebna nikakva specifina instalacija tog drajvera (to je kod ODBC drajvera obavezno), nego je dovoljno tu biblioteku ukljuiti u sastav

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.

6.3 Uspostavljanje veze sa bazom podataka


Dve su radnje neophodne da bi se komuniciralo sa nekom bazom podataka putem JDBC-a: uitavanje drajvera i otvaranje konekcije sa bazom podataka. Pogledajmo primer ove dve operacije za Oracle SUBP:
// uitavanje Oracle drajvera Class.forName("oracle.jdbc.driver.OracleDriver"); // otvaranje konekcije Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@branko.tmd.ns.ac.yu:1526:VTA", "vta", "vta");

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();

6.4 Postavljanje upita


Primeri u ovom poglavlju koriste emu baze podataka prikazanu na slici 6.1. (Koriena je notacija alata PowerDesigner, a korieni SUBP je Oracle; ovde neemo ulaziti u detalje ove notacije, jer smatramo da je dijagram dovoljno jasan). U pitanju je baza podataka o nastavnicima i predmetima koje oni predaju. Jedan nastavnik moe da predaje vie predmeta, a takoe i jedan predmet moe da predaje vie nastavnika.
NASTAVNICI NASTAVNIK_ID INTEGER IME VARCHAR2(25) PREZIME VARCHAR2(35) ZVANJE VARCHAR2(15)
NASTAVNIK_ID = NASTAVNIK_ID

PREDMETI PREDMET_ID NAZIV INTEGER VARCHAR2(150)

PREDAJE PREDMET_ID INTEGER NASTAVNIK_ID INTEGER

PREDMET_ID = PREDMET_ID

Slika 6.1. ema baze podataka koriena u primeru

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();

} }

} catch (Exception ex) { ex.printStackTrace(); }

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.

6.5 DML operacije


Prethodni odeljak je prikazao kako se alju upiti serveru. Za slanje DML (data manipulation) naredbi SQL-a poput INSERT, UPDATE ili DELETE takoe se koristi Statement objekat, koji se inicijalizuje na isti nain kao i u prethodnom sluaju. Razlika je ovde u slanju konkretne naredbe serveru, to se postie pozivom metode executeUpdate. Ova metoda, za razliku od executeQuery, ne vraa ResultSet objekat, ve samo jednu int vrednost koja predstavlja broj redova na koje je operacija uticala (npr. broj auriranih ili obrisanih redova). Sledi primer dodavanja novog reda u tabelu predmeti:
String sql = "INSERT INTO predmeti (predmet_id, naziv) " + "VALUES (10, 'Matematika 1')"; Statement stmt = conn.createStatement(); int rowsAffected = stmt.executeUpdate(sql); stmt.close();

Statement objekat nakon upotrebe i u ovom sluaju treba da oslobodi resurse koje je zauzimao pozivom metode close.

6.6 Uzastopno izvravanje istih SQL naredbi


Prethodni odeljak definie sve to je potrebno da bi se pisao program koji moe da obavlja bilo koju operaciju nad bazom podataka (koja se moe definisati SQL naredbama, naravno). Ovaj odeljak predstavlja situaciju kada se ista SQL naredba alje serveru vie puta uzastopno, to je prilino est sluaj u praksi. Na primer, dodavanje vie redova u tabelu nastavnici moe se obaviti sledeim nizom SQL naredbi:
INSERT INTO nastavnici (nastavnik_id, ime, prezime) VALUES (10, 'Sima', 'Simi'); INSERT INTO nastavnici (nastavnik_id, ime, prezime) VALUES (11, 'Vasa', 'Vasi'); INSERT INTO nastavnici (nastavnik_id, ime, prezime) VALUES (12, 'Petar', 'Petrovi');

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(); }

} }

6.7 Pozivanje uskladitenih procedura


Uskladitene procedure (stored procedures) predstavljaju procedure koje se smetaju u okviru baze podataka i dostupne su za pozivanje od strane klijenata. Najee se piu u nekom od proirenja jezika SQL koje definie proizvoa konkretnog SUBP. Na primer, Oracle sistemi nude PL/SQL za pisanje ovakvih procedura, Microsoft SQL Server ima svoj jezik Transact-SQL, itd. Sve ovo se odnosi i na uskladitene funkcije, pri emu podela na funkcije i procedure je, zapravo, tradicionalna podela na potprograme koji vraaju neku vrednost kao rezultat ili ne vraaju nikakvu vrednost. Prednost korienja uskladitenih procedura je u poboljanju performansi sistema. Naime, procedura najee predstavlja nekakvu sloenu operaciju koja se izvrava nad bazom podataka. Takvu operaciju moemo implementirati i odgovarajuim Java kodom, koristei prethodno izloene JDBC koncepte. Meutim, takav Java kod bi se esto obraao bazi podataka i time generisao veliki mreni saobraaj izmeu klijenta i servera, i potroio bi izvesno procesorsko vreme i na klijentu i na serveru za tu komunikaciju. Sa druge

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

poziv uskladitene procedure

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

CallableStatement cstmt = conn.prepareCall( "{? = call povezi (?, ?, ?)}");

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();

Rezultat izvravanja uskladitene funkcije dobija se sledeim pozivom:


cstmt.getInt(1)

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();

} }

} catch (Exception ex) { ex.printStackTrace(); }

6.8 Upravljanje transakcijama


Svaka konekcija se, inicijalno, nalazi u tzv. auto-commit reimu rada: potvrda uspenog zavretka transakcije (commit) e se slati automatski nakon svake poslate SQL naredbe. Kada se konekcija ne nalazi u auto-commit reimu transakcija se mora runo potvrditi ili opozvati. Potvrda transakcije se vri pozivom metode commit nad Connection objektom:
conn.commit();

Opoziv transakcije se vri pozivom metode rollback nad Connection objektom:


conn.rollback();

Promena reima rada se postie pozivom metode setAutoCommit:


conn.setAutoCommit(false);

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

6.9 Dodatak: inicijalizacija drajvera


Ovaj odeljak je posveen detaljnijem objanjenju nekih pojedinosti vezanih za inicijalizaciju JDBC drajvera i korienje JDBC klasa i interfejsa. Uitavanje JDBC drajvera, kako je ranije reeno, obavlja se pozivom
Class.forName("oracle.jdbc.driver.OracleDriver");

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

Uvod u vieslojne klijent/server sisteme


7.1 Klasini klijent/server sistemi
U klasinim sistemima za obradu podataka po klijent/server modelu mogu se uoiti tri klase komponenti: serveri, klijenti i mrea. Namena servera je, pre svega, optimalno upravljanje zajednikim resursima, to su najee podaci. Server obavlja upravljanje bazom podataka kojoj pristupa vie korisnika, vri kontrolu pristupa i bezbednosti podataka i centralizovano obezbeuje integritet podataka za sve aplikacije. Klijenti omoguavaju korisnicima pristup do podataka. Klijent-aplikacije vre upravljanje korisnikim interfejsom i izvravaju deo logike aplikacije. Raunarska mrea i komunikacioni softver omoguavaju prenos podataka izmeu klijenta i servera. Slika 7.1 prikazuje skicu klijent/server sistema.

Klijent

Mrea

Klijent

Server

Klijent

Slika 7.1. Skica klasinog klijent/server sistema

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.

7.2 WWW i Java kao platforma za klijente


iroka prihvaenost Interneta, odnosno World Wide Web-a, je proizvela, izmeu ostalog, i jedan izuzetno vaan efekat: okruenje Web itaa (browsera) je postalo poznato za veinu korisnika raunara. Informacioni sistemi kod kojih se komunikacija sa korisnikom odvija kroz Web ita u velikoj meri eliminie potrebu za dugotrajnom i skupom obukom korisnika. Sa druge strane, Java je programski jezik ija je osnovna karakteristika potpuna nezavisnost od fizike platforme na nivou prevedenog programskog koda. Java programi se, u formi apleta, mogu ugraivati u Web stranice i na taj nain distribuirati korisnicima. Posledica ovoga je mogunost automatske distribucije i instalacije klijent-aplikacija na mrei, bez obzira na konkretnu fiziku platformu klijenta dovoljan je Web ita sa podrkom za Javu. Kombinacija WWW i Java tehnologija je omoguila implementaciju klijent/server informacionih sistema koje, za razliku od klasinih sistema, karakteriu sledee osobine: jednostavan i iroko prihvaen oblik korisnikog interfejsa (Web ita); automatska distribucija i instalacija klijent-aplikacija; jednostavnije odravanje sistema, naroito u heterogenim mreama.

Slika 7.2 predstavlja skicu klijent/server sistema zasnovanog na WWW i Java tehnologijama.

94

Web ita

Mrea

Web ita

Web server + SUBP

Web ita

Slika 7.2 Sistem zasnovan na WWW i Java tehnologijama

7.3 Troslojni klijent/server sistemi


Informacione sisteme koji su do sada razmatrani moemo nazvati dvoslojnim: u okviru njih izdvajaju se segmenti klijenta i servera. Klijent/server sistemi sa troslojnom arhitekturom (three-tier architecture) predstavljaju sisteme sa tri, u velikoj meri nezavisna, podsistema. U pitanju su sledei podsistemi: 1. podsistem za interakciju sa korisnikom (implementira funkcije korisnikog interfejsa); 2. podsistem za implementaciju osnovnih funkcija sistema (implementira tzv. poslovnu logiku); 3. podsistem za rukovanje podacima, pri emu se pre svega misli na fiziki smetaj podataka (ovo je, zapravo, sistem za upravljanje bazama podataka). Na slici 7.3 je prikazan odnos ova tri podsistema. Na slici se vidi da ne postoji direktna veza izmeu podsistema za interakciju sa korisnikom i podsistema za rukovanje podacima. Zbog ovakvog meusobnog odnosa, ovi podsistemi se nazivaju i slojevi.

Klijent aplikacija

Aplikacioni server

SUBP

Slika 7.3. Elementi troslojne arhitekture sistema

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 Aplikacioni server

Klijent

Slika 7.4. Skica konfiguracije sistema sa troslojnom arhitekturom

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

Web Server Web browser

App server DBMS

Web Server Web browser

App server DBMS

Web Server Web browser App server

Slika 7.5. Jedna mogua arhitektura vieslojnog sistema

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

Slika 7.6. Java tehnologije za izgradnju vieslojnih sistema

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

Dinamiko generisanje HTML-a i servleti


8.1 HTTP protokol
Web itai su namenjeni za prikazivanje Web stranica koje im isporuuju odgovarajui Web serveri. Struktura i izgled samih stranica se opisuje jezikom HTML. Komunikacija izmeu Web klijenta (tj. itaa) i Web servera odvija se po standardnom HTTP protokolu. Slika 8.1 prikazuje slanje zahteva od klijenta ka serveru po HTTP protokolu. (Ovde prikazujemo HTTP protokol kako bi se lake razumela materija u narednim odeljcima; detaljima ovog protokola se neemo baviti.)
HTTP klijent GET /docs.html HTTP/1.0 User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) Accept-Cookies: yes Host: branko.tmd.ns.ac.yu ... HTTP server

Slika 8.1. Slanje zahteva HTTP klijenta

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 klijent HTTP/1.0 200 OK Content-Type: text/html <HTML> <HEAD> ...

HTTP server

Slika 8.2. Slanje odgovora HTTP klijentu

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

8.2 Statiki i dinamiki Web sadraji


Statiki Web sadraji su datoteke koje su unapred smetene u odgovarajui direktorijum fajl-sistema Web servera i spremne su za isporuku klijentima po njihovom zahtevu. Slika 8.3 prikazuje sekvencu dogaaja kada klijent zatrai ovakvu datoteku.
1) klijent zahteva fajl HTTP klijent 2) server uitava fajl iz fajl-sistema i alje ga klijentu HTTP server

Slika 8.3. Isporuka statikih sadraja

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

Slika 8.4. Isporuka dinamikih sadraja

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

8.4 Primer: elementarni servlet


Ovaj odeljak donosi primer elementarnog servleta, koji redefinie samo doGet metodu. U okviru doGet metode generie se HTML stranica koja e biti poslata klijentu. U pitanju je stranica koja sadri tekst Hello world.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; /** Hello world u servlet tehnologiji */ public class HelloWorld extends HttpServlet { // Obrada GET zahteva public void doGet( HttpServletRequest request, HttpServletResponse response)
102

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.

Slika 8.5. Rezultat rada HelloWorld servleta

8.5 Analiza zaglavlja HTTP zahteva


Sledei primer ilustruje mogunost pristupa svim podacima iz HTTP zahteva klijenta na koji se odgovara. Pristup tim podacima se vri preko request objekta koji je parametar doGet metode.

103

import import import import import

javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.Enumeration; java.util.Vector;

/** 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.

8.6 Konkurentni pristup servletu


Za svaku servlet klasu koja se pravilno instalira Web server e kreirati tano jednu instancu ove klase. Ova instanca se koristi za obraivanje svih zahteva koji pristignu od klijenata.

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; }

8.7 Praenje sesije korisnika


U odeljku 8.1 prikazan je HTTP protokol i tamo je naglaeno kako on ne omoguava praenje sesije korisnika. Kako bi se ovaj cilj ipak postigao definisano je pomono reenje. Radi se o mehanizmu slanja kolaia (cookies) izmeu klijenta i servera koga je uveo Netscape Navigator, a kasnije je postao standardan mehanizam za ovu namenu podran od svih Web itaa i servera.

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

1) zahtev + cookie HTTP klijent b) 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; }

Sada sledi programski kod servlet klase.


import javax.servlet.*; import javax.servlet.http.*; import java.io.*;

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

8.8 Preuzimanje podataka sa HTML formi


HTML forme su jedini nain da korisnik unosi neke podatke koristei Web ita. Preuzimanje podataka iz forme je neophodno kako bi se ti podaci negde smestili radi trajnog uvanja. Slika 8.7 prikazuje izgled jedne HTML stranice sa formom za unos imena i prezimena korisnika, i izgled stranice na koju se upuuje Web ita prilikom klika na dugme Poalji.

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>&nbsp;</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.

8.9 Pristup bazama podataka iz servleta


Za pristup bazama podataka iz servleta koristi se JDBC interfejs, opisan u poglavlju 6. Korienje JDBC interfejsa unutar servleta ne razlikuje se ni na koji nain od korienja unutar klasine Java aplikacije. Sledi primer servleta koji pristupa bazi podataka radi listanja svih registrovanih nastavnika (u pitanju je ema baze podataka koriena u poglavlju 6). Rezultat rada servleta je HTML stranica sa listom svih nastavnika pobrojanih u tabeli nastavnici. import import import import import javax.servlet.*; javax.servlet.http.*; java.sql.*; java.util.*; java.io.*;

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

Java Server Pages


9.1 JSP koncept
Prethodno poglavlje predstavlja kratak pregled mogunosti i naina upotrebe servlet tehnologije za dinamiko generisanje HTML stranica. Koncept servleta ima dobrih osobina, npr. prenosivost servleta na razliite raunarske platforme i Web servere, efikasno izvravanje, itd. Meutim, osnovna mana servleta je to je generisanje HTML teksta smeteno unutar servlet klase. To znai da je teko razdvojiti posao Web dizajnera i programera: obe vrste autora moraju da modifikuju iste fajlove sa Java izvornim kodom. Veliki broj Web dizajnera nije vian programiranju u Javi (ili programiranju uopte), to drastino oteava njihov rad u okviru projekata koji koriste servlete. Pored toga, svaka promena u izgledu stranice zahteva izmenu HTML teksta koji taj izgled opisuje. Kako je kompletan HTML tekst koji se generie smeten unutar servleta, to svaka izmena dizajna stranice zahteva izmenu i ponovno prevoenje servleta to dodatno komplikuje njihovo korienje. Ideja koja se nalazi u osnovi Java Server Pages (JSP) tehnologije je da se omogui paralelan, ili bar nezavisan, rad Web dizajnera i programera. JSP stranice su tekstualne datoteke koje sadre HTML dokumente. Pri tome se za pisanje takvih dokumenata mogu koristiti standardni HTML elementi, ali i posebni tagovi koji omoguavaju dodavanje dinamikih elemenata u stranicu. Sledi primer fragmenta jedne takve stranice (dinamiki elementi su naglaeni).
<html> ... <h4>Dobrodoli, <%= username %></h4> Danas je <%= new java.util.Date() %>. ... </html>

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.

9.2 Vrste dinamikih elemenata


Ovaj odeljak prikazuje nekoliko vrsta dinamikih elemenata koje uvodi JSP. 9.2.1 Izrazi JSP izrazi su elementi koji se nalaze izmeu znakova <%= i %>. Sadraj ovog elementa moe biti proizvoljan Java izraz. Treba obratiti panju na to da se izrazi ne zavravaju znakom taka-zarez. Izrazi mogu biti proizvoljnog tipa. Za izraze koji nisu tipa String poziva se metoda toString kako bi se dobila odgovarajua string reprezentacija koja e se ugraditi u stranicu.
test.jsp <html> <head> <title>JSP izrazi</title> </head> <body> <h3>Primeri JSP izraza</h3> Danasnji datum: <%= new java.util.Date() %> </body> </html>

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

Dobro vee! <% } %> ... </html>

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; %>

proizvee sledei atribut servlet klase:


class GenerisaniServlet extends HttpServlet { ... 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" %>

Ekvivalentno je pozivu setContentType metode response objekta u servletu.


<%@ include file="included.jsp" %>

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.

9.3 Predefinisane promenljive


U okviru JSP stranica mogue pristupati izvesnom broju unapred definisanih promenljivih. Te promenljive su nabrojane u tabeli 9.1.
Ime promenljive Tip promenljive request HttpServletRequest response HttpServletResponse out JspWriter session HttpSession application ServletContext config ServletConfig pageContext PageContext page (this) Tabela 9.1. Predefinisane promenljive u JSP stranicama

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.

9.4 Skladitenje podataka i JavaBeans


Podaci iz HTML formi koje unosi korisnik prenose se na nain kako je to opisano u odeljku 8.8. JSP omoguava jednostavno preuzimanje parametara stranice i njihovo smetanje u svojstva (properties) neke JavaBean komponente. Posmatrajmo HTML stranicu sa formom kao na slici 9.1.

118

Slika 9.1 HTML forma

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

<% } %> ... </html>

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.

9.5 Opseg vidljivosti JavaBean komponenti


U prethodnom primeru komponenta user je koriena samo u okviru jedne stranice. To ne mora biti uvek sluaj. Opseg vidljivosti komponente moe biti razliit, to se specificira atributom scope taga jsp:useBean. Sledi primer:
<jsp:useBean id="user" class="somepackage.User" scope="session"/>

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.

Vrednost atributa application session request page

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

9.6 Definisanje sopstvenih tagova


Iz do sada izloenog vidi se da se praktino sva dinamika u JSP stranicama opisuje skriptletima. Korienje skriptleta donosi veliku fleksibilnost, ali nije jednostavno za autora koji nije vian Java i servlet programiranju. Vii nivo upotrebljivosti JSP tehnologije postie se korienjem biblioteka dodatnih tagova koji donose novu funkcionalnost. Sledei primer prikazuje upotrebu tagova koji imaju istu funkcionalnost kao i klasina if-else konstrukcija u programskim jezicima.
<%@ taglib uri="iftags" prefix="mytags"%> <mytags:if> <mytags:condition> <%= Math.random() > 0.5 %> </mytags:condition> <mytags:then> Prijavili ste se! </mytags:then> <mytags:else> Niste se prijavili! </mytags:else> </mytags:if>

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

Tehnologije distribuiranih objekata


10.1 Osnovni koncepti
Koncept distribuiranih objekata polazi od ideje da neki objekat (instanca klase) sa svojim metodama i atributima moe da postoji na nekom raunaru i da drugi programi, odnosno objekti, koji se izvravaju na drugim raunarima mogu da mu pristupaju. Pristup tom objektu zapravo znai mogunost pozivanja metoda i pristupa njegovim atributima. Pristup tom serverskom objektu bi, sa strane ostalih uesnika (klijenata), trebalo da bude to jednostavniji. U idealnom sluaju pristup njemu ne bi trebalo da se razlikuje od pristupa lokalnim objektima ostalim objektima koji se nalaze u sastavu programa. Poziv metode serverskog objekta podrazumava izvravanje te metode na onom raunaru na kome se nalazi sam objekat. To znai da se klijentski program inicijalno izvrava na vie raunara inicijalno na onom na kome je pokrenut, ali i na svim raunarima na kojima se nalaze serverski objekti koje on koristi. Slika 10.1 ilustruje situaciju kada klijentski program pozove metodu serverskog objekta.
1) poziv metode 2) izvravanje metode klijent serverski objekat

3) rezultat

Slika 10.1. Poziv metode serverskog objekta

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

klijent klijent aplikacija

serverski objekat kontejner

Slika 10.2. Klijentski i serverski objekat u okviru svojih programa

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

Slika 10.3. Pristup RMI serverskom objektu preko interfejsa

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

server skeleton JVM 2

RMI objekat

JVM 1

Slika 10.4. Uesnici u RMI komunikaciji

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)

Slika 10.5. Faze u pisanju RMI programa

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

Sledi primer jednog RMI interfejsa:


interface Server extends java.rmi.Remote { SomeClass method(SomeClass x, int y) throws 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

Slika 10.6. Serijalizacija i deserijalizacija 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

ServerImpl server = new ServerImpl(); Naming.rebind( "//branko.tmd.ns.ac.yu:1099/ServerskiObjekat", server); }

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());

2. pronae svoj serverski objekat slanjem zahteva RMI registry-ju:


Server server = (Server)Naming.lookup( "//branko.tmd.ns.ac.yu:1099/ServerskiObjekat");

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.*;

public class Server extends UnicastRemoteObject implements ServerI {

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

Slika 10.7. Komunikacija klijenta i servanta preko ORB-ova

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

Slika 10.8. Meusobni odnos klijent, servant, stub i skeleton klasa

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 Helper.java klijent

Count Holder.java

_Count ImplBase .java

Count.java

Count Operations. java

server

Slika 10.9. Generisanje datoteka idlj alatom

Sledi programski kod klasa servanta, servera i klijenta.


CounterServant.java import counter.*; public class CounterServant extends _CountImplBase { public int increment() { return ++sum; } public int sum() { return sum; } public void sum(int newSum) { sum = newSum; } private int sum = 0; }

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(); } } }

Da bi se ovaj primer pokrenuo, potrebno je prvo pokrenuti CORBA Naming Service:


tnameserv -ORBInitialPort 900

Zatim je potrebno pokrenuti server:


java CounterServer -ORBInitialHost localhost -ORBInitialPort 900

Na posletku, moe da se pokrene i klijent:


java CounterClient -ORBInitialHost localhost -ORBInitialPort 900

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

Slika 10.10. Dodatne klase koje opisuju izuzetak badRead.

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

Slika 10.11 ilustruje ovu sekvencu dogaaja.


3)

client servant client app 4)

server servant server app

2)

naming service

1)

Slika 10.11. Sekvenca poziva kod CORBA callback procedure

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.

10.4 Enterprise JavaBeans


Enterprise JavaBeans (EJB) je specifikacija koju definie Sun Microsystems. Za razliku od RMI tehnologije ija implementacija je sastavni deo Java jezika, EJB specifikacija ne propisuje nijednu odreenu implementaciju. Razliiti proizvoai mogu da proizvode svoje nezavisne implementacije. Specifikacija je dovoljno precizno definisana tako da obezbedi da se softver koji koristi EJB tehno-

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

Slika 10.11. Meusobni odnos EJB servera, kontejnera i komponenti

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

session bean session bean

entity bean entity bean DB

klijent EJB kontejner EJB server

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

EJB komponenta EJB komponenta EJB kontejner EJB server

CORBA klijent

COM+ klijent

Slika 10.13.Pristup EJB serveru korienjem razliitih tehnologija

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

Slika 10.14. Meusobni odnos delova EJB komponente

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)

Sledi primer jednog remote interfejsa:


public interface Demo extends EJBObject { public String demoSelect() throws RemoteException; }

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)

Sledi primer jednog home interfejsa:


public interface DemoHome extends EJBHome { public Demo create() throws CreateException, RemoteException; public Demo create(int i) throws CreateException, RemoteException; }

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

Sledi primer ovakve bean klase.


import javax.ejb.*; import java.rmi.*; import java.sql.*; public class DemoBean implements SessionBean { public void ejbActivate() { }

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

Veba: Web shop aplikacija


Zadatak. Potrebno je implementirati Web aplikaciju koja predstavlja sajt za kupovinu prozvoda. Sistem funkcionie kao posrednik izmeu kupaca (koji poseuju Web sajt radi kupovine) i dobavljaa. Sistem ine dva odvojena sajta: Front office: sajt koji je dostupan svim korisnicima; omoguava pregledanje kataloga proizvoda i njihovo naruivanje. Back office: sajt koji je dostupan samo administratorima sistema; omoguava izvravanje administrativnih funkcija. Registracija i autentifikacija korisnika Pregled ponuenih proizvoda, organizovanih hijerarhijski u kategorije Izbor proizvoda za kupovinu i njihovo naruivanje (shopping cart/ potroaka korpa sistem) Registracija i autentifikacija korisnika Auriranje baze dobavljaa, proizvoda i kategorija

Front office funkcije su sledee:

Back office funkcije su sledee:

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

Slika 11.1. Arhitektura sistema

11.1 Model podataka


Model podataka ovog sistema je prikazan da ER dijagramu na slici 11.2. U pitanju je notacija alata Sybase PowerBuilder. Dobijeni relacioni model koji

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

Order Order ID Order Date

Contains Items

Ordered Item Quantity

Product
Is Ordered

Product ID Product Name Vendor Description Price

Contains Products

Category Category ID Category Name Category Desc

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

Slika 11.2. ER model

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_ID = PRODUCT_ID USER_ID = USER_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

Slika 11.3 Relacioni model

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).

11.2 Struktura Web sajta


Za implementaciju Web sajta se koristi JSP tehnologija implementirana u okviru Apache Tomcat Web servera. U pitanju je javno dostupan Web server koji predstavlja referentnu implementaciju servlet i JSP tehnologija. Slike 11.4 i 11.5

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

Slika 11.4. Struktura front office sajta

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

Slika 11.5. Struktura back office sajta

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

purchase.jsp oldPurchase.jsp login.jsp register.jsp

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

Tabela 11.2 sadri opis parametara stranica back office sajta.


Stranica index.jsp categories.jsp products.jsp Parametar name desc parent name desc vendor category supplier price name address productID Znaenje Naziv nove kategorije Opis nove kategorije ID roditeljske kategorije; 0 ako nova kategorija nema roditelja Naziv novog proizvoda Opis novog proizvoda Naziv proizvoaa ID kategorije kojoj proizvod pripada ID dobavljaa prodajna cena proizvoda Naziv novog dobavljaa Adresa novog dobavljaa ID proizvoda kome se dodaje slika

suppliers.jsp addPicture.jsp savePicture.jsp

147

login.jsp register.jsp

height width title uploaded username password username password

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

11.3 Softverske komponente


Dijagram klasa sistema je prikazan na slici 11.6.
User -userID : int = 0 -username : String = "" -password : String = "" -firstName : String = "" -lastName : String = "" -address : String = "" -email : String = "" -receiveNews : Boolean = false +register() : void +login() : void 1 Order 1 -orderID : int = 0 -orderDate : Date +add() : void 1 1 Category -categoryID : int = 0 -name : String = "" -desc : String = "" +add() : void *

* * Item
AdminUser

Supplier -supplierID : int = 0 -name : String = "" -address : String = "" +add() : void

-quantity : int = 0 +add() : void * 1

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

Slika 11.6. Dijagram klasa sistema

Prikazane klase se praktino direktno poklapaju sa entitetima u modelu podataka sistema.

11.4 Dodatna razmatranja


11.4.1 Rukovanje konekcijama sa bazom podataka U odeljku 8.9 bilo je rei o pristupanju bazi podataka iz servleta. Dva mogua reenja su tamo pomenuta. Jedna mogunost je bila da se konekcija sa bazom podataka otvori prilikom inicijalizacije servleta, i da se zatvori prilikom unitavanja servleta. Obrada HTTP zahteva svih klijenata koristi istu konekciju za pristup bazi podataka. Ovakvo reenje moe da se upotrebi samo u sluaju da svi pristupi bazi podataka obuhvataju samo upite; operacije koje modifikuju podatke u bazi ne mogu koristiti konekciju na ovakav nain.
148

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.

Programski jezik Java


Arnold K., Gosling J., Holmes D., Programski jezik Java, tree izdanje, CET, Beograd 2001. Eckel B., Thinking in Java, 2nd edition, Prentice-Hall, New Jersey 2000. The Java Tutorial, Sun Microsystems 2001. http://java.sun.com The Java Virtual Machine Specification, Sun Microsystems 1999. http://java.sun.com

Java 2 Enterprise Edition tehnologije


Enterprise JavaBeans Specification, version 2.0, Sun Microsystems 2000. http://java.sun.com Orfali R., Harkey D., Client/Server Programming with Java and CORBA, 2nd edition, Wiley, New York 1998. Roman E., Mastering Enterprise JavaBeans and the Java 2 Enterprise Edition, Wiley, New York 2000. Java Server Pages Specification, version 1.1, Sun Microsystems, 2000. http://java.sun.com Hall M., Core Servlets and Java Server Pages, Prentice-Hall, New Jersey 2000. JSP by Example, Sun Microsystems 2000. http://java.sun.com JSP Syntax Reference, Sun Microsystems 2000. http://java.sun.com The JNDI Tutorial: Building Directory-Enabled Java Applications, Sun Microsystems 2000. http://java.sun.com

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.

Java API Help


JDK 1.3 API Documentation, Sun Microsystems 2000. http://java.sun.com Servlet 2.1 API Documentation, Sun Microsystems 2000. http://java.sun.com JSP 1.1 API Documentation, , Sun Microsystems 2000. http://java.sun.com

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;

Sledi programski kod servera:


chat\server\ChatServer.java package chat.server;

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

You might also like