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

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

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

5
6
7
9
9
10
10
11
13
14
14
15
17
17
19
19
20

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

1.29 Konvencije davanja imena


1.30 Generisanje programske dokumentacije i javadoc
1.31 Zadaci: modifikacije klase Matrix

25
28
28
29
30
30
31

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

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

3.10. Aplet i aplikacija istovremeno


3.11. Korisniki definisane komponente
3.12. JavaBeans

50
51

53
57
57
58
59

60
61
63

4. Mreno programiranje u Javi ................................................................................66


4.1. Osnovne karakteristike
66
4.1.1. Pojam socket-a

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

66

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

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

8.9 Pristup bazama podataka iz servleta

102
102
102

102
103
104
105
108
109

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

9.3. Predefinisane promenljive


9.4. Skladitenje podataka i JavaBeans
9.5. Opseg vidljivosti JavaBean komponenti
9.6. Definisanje sopstvenih tagova

115
115
117
117

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

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

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

124
125
126
126
127
127
129

129
129
130
131
131
134
136
137

137
138
139
140
140

11. Veba: Web shop aplikacija ..............................................................................144


11.1. Model podataka
144
11.2. Struktura Web sajta
145
11.3. Softverske komponente
147
11.4. Dodatna razmatranja
147
11.4.1. Rukovanje konekcijama sa bazom podataka

147

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

heap

stek

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

objekat klase
Automobil

b
a

heap

stek

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

objekat klase
Automobil

b
a

heap

stek

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

radi: false
x
x

x
a)

b)

radi: true

radi: true

x
x

x
c)

d)

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=0

a)

b)

a=1
a=0

a=0

c)

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

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

a2

a1

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

Opisuje parametar metode.


@param ime opis

@return

Opisuje rezultat metode.


@return opis

@throws

Opisuje izuzetak koji moe da izazove metoda.


@throws puno-ime-klase opis

@see

Referenca na neku drugu klasu, metod ili atribut.


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

@author

Podaci o autoru.
@author author-info

@version

Opis verzije.

@version version-info
@since

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

nit B

sync {
2) locked?

1) lock

obj

sync {

3) yes

4) unlock

5) lock

wait

}
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

nit B

sync {
wait();

obj
sync {

waiting
for
notify
notify

notify();

waiting
for
lock

}
}

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.

CENTER

SOUTH

48

EAST

WEST

NORTH

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.

b)

a)

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

Pogledajmo primer jednog elementarnog apleta:


import java.awt.*;
import javax.swing.*;
public class AppletTest extends JApplet {
public void init() {
getContentPane().add(new JLabel("Applet!"));
}
}

57

Aplet klasa se zove AppletTest, nasleuje klasu JApplet i redefinie metodu init.
U okviru init metode vri se inicijalizacija apleta: u ovom sluaju to se svodi na
postavljanje jedne labele na povrinu apleta.
Aplet nema metodu main, tako da ga ne moemo pokrenuti na do sada poznat
nain iz komandne linije. Potrebno je ovakav aplet ugraditi u HTML stranicu
u okviru koje e biti prikazan. Sledi primer ovakve HTML stranice.
<html>
<head>
<title>Test stranica sa apletom</title>
</head>
<body>
<applet code = "AppletTest" width = 100 height = 50>
</applet>
</body>
</html>

Ugraivanje apleta u stranicu se postie tagom applet. Njegovi atributi su code


(naziv aplet klase), width (irina apleta u pikselima) i height (visina apleta u
pikselima). Ovakva stranica, kada se otvori u Web itau, izgleda kao na slici
3.5.

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

b)

a)

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

program B

(147.91.177.196, 7534)

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

vor Y (204.1.177.96)

7534

9000

program A

program B
8080

7826

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

// itam zahtev
// aljem odgovor

// prekid veze
in.close();
out.close();
s.close();

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

create()

activeClient

create()
create()

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

WriterThread

ActiveClient

ClientUtils

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)

PREDMETI

NASTAVNIK_ID = NASTAVNIK_ID

PREDMET_ID
NAZIV

PREDAJE
PREDMET_ID
INTEGER
NASTAVNIK_ID
INTEGER

INTEGER
VARCHAR2(150)

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

App server

Web Server

DBMS

Web browser

Web Server

App server

Web browser

DBMS

Web Server
App server

Web browser

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
Servlets / JSP

HTML

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

HTTP server
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
...

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

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
2) server uitava fajl iz fajl-sistema
i alje ga klijentu

HTTP klijent

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

2) odgovor + cookie

HTTP server

a)

1) zahtev + cookie
HTTP klijent

2) odgovor + cookie

HTTP server

b)

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.

b)

a)

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

Opis

application

istu instancu dele svi korisnici sajta

session

svaki korisnik sajta ima svoju instancu

request

svaki zahtev za stranicom ima svoju instancu

page

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
serverski
objekat

klijent

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

serverski
objekat

klijent aplikacija

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

interfejs

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

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

RMI
objekat
JVM 2

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

javac
(.class)

(.java)
javac

rmic
(.class)

klijent
(.class)

(.class)
RMI interfejs
(.class)

client stub
(.class)

(.class)
server skeleton
(.class)

RMI objekat
(.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

byte
stream

byte
stream

deserijalizacija

JVM 1

kopija
objekta

JVM 2

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

2. generiu se stub i skeleton klase:


rmic Server

3. startuje se RMI registry:


start rmiregistry (Win32)
rmiregistry & (Unix)

128

4. startuje se server:
java Server

5. startuje se klijent:
java Client

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

ORB

servant

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

_Count
ImplBase
.java

Count
Holder.java

Count.java

klijent

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

server
servant

client app

server app

4)

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

entity
bean

session
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

CORBA klijent

EJB komponenta

COM+ klijent

EJB kontejner
EJB server

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
bean
klasa

remote
interfejs
klijent
EJB kontejner
EJB server

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.

Front office funkcije su sledee:

Registracija i autentifikacija korisnika


Pregled ponuenih proizvoda, organizovanih hijerarhijski u kategorije
Izbor proizvoda za kupovinu i njihovo naruivanje (shopping cart/
potroaka korpa sistem)

Back office funkcije su sledee:

Registracija i autentifikacija korisnika


Auriranje baze dobavljaa, proizvoda i kategorija

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

Supplier

Admin ID
Username
Pasword

Supplier ID
Supplier Name
Supplier Address

Supplies

User
User ID
Username
Pasword
First Name
Last Name
User Address
Email
Receive news

Order
Order ID
Order Date

Places

Product

Ordered Item
Quantity

Contains Items

Is Parent To

Is Ordered

Product ID
Product Name
Vendor
Description
Price

Contains Products

Category
Category ID
Category Name
Category Desc

Has Images

Product Image
Image ID
Image Title
Image Height
Image Width
Content Type
Image Data

Conceptual Data Model


Project : Web Shop
Model : Web Shop
Author : Branko Milosavljevic

Version 1.0 11.4.2001

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

PRODUCTS

ORDERED_ITEMS
ORDER_ID = ORDER_ID

ORDER_ID
PRODUCT_ID
QUANTITY

INTEGER not null


INTEGER not null
INTEGER not null

PRODUCT_ID = PRODUCT_ID

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 = CATEGORY_ID

CATEGORY_ID
PARENT_CATEGORY_ID
CATEGORY_NAME
CATEGORY_DESC

INTEGER
INTEGER
VARCHAR2(50)
VARCHAR2(500)

not null
null
not null
null

PRODUCT_ID = PRODUCT_ID
USER_ID = USER_ID

PRODUCT_IMAGES

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)

PRODUCT_ID
IMAGE_ID
IMAGE_TITLE
IMAGE_HEIGHT
IMAGE_WIDTH
CONTENT_TYPE
IMAGE_DATA

ADMINS
not null
not null
not null
not null
not null
not null
not null
not null

ADMIN_ID
USERNAME
PASWORD

INTEGER
VARCHAR2(20)
VARCHAR2(20)

not null
not null
not null

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

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

submit

addPicture

register

savePicture

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

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

Parametar

Znaenje

catID
catID
prodID
quantity

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

suppliers.jsp
addPicture.jsp
savePicture.jsp

Parametar

Znaenje

name
desc
parent
name
desc
vendor
category
supplier
price
name
address

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

productID

ID proizvoda kome se dodaje slika

147

height
width
title
uploaded
username
password
username
password

login.jsp
register.jsp

visina slike u pikselima


irina slike u pikselima
naziv slike
slika koja se dodaje
korisniko ime administratora koji se prijavljuje
lozinka administratora koji se prijavljuje
korisniko ime administratora koji se registruje
lozinka administratora koji se registruje
Tabela 11.2. Parametri stranica back office sajta

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

Category
-categoryID : int = 0
-name : String = ""
-desc : String = ""
+add() : void

*
*

Supplier

Item
AdminUser

-adminID : int = 0
-username : String = ""
-password : String = ""
+register() : void
+login() : void

Product

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

*
1

-productID : int = 0
-name : String = ""
-vendor : String = ""
-desc : String = ""
+add() : void

-supplierID : int = 0
-name : String = ""
-address : String = ""
+add() : 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

Socket sock;
BufferedReader in;
ChatData chatData;
JTextArea ta;

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

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