175
Univerzitet u Novom Sadu Fakultet tehničkih nauka Katedra za računarske nauke i informatiku Branko Milosavljević Milan Vidaković Java i Internet programiranje Materijal za predmet Sintetski praktikum iz računarstva Novi Sad, 2001.

68261077 Java i Internet Programiranje1

Embed Size (px)

DESCRIPTION

Java

Citation preview

Page 1: 68261077 Java i Internet Programiranje1

Univerzitet u Novom Sadu Fakultet tehničkih nauka Katedra za računarske nauke i informatiku

Branko Milosavljević Milan Vidaković

Java i Internet programiranje

Materijal za predmet

Sintetski praktikum iz računarstva

Novi Sad, 2001.

Page 2: 68261077 Java i Internet Programiranje1

Sadržaj

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 mašina 3 1.2 Programski jezik Java 4 1.3 Osnovni koncepti 4

1.3.1 Tipovi podataka 4 1.4 Klase i objekti 5 1.5 Prevođenje i pokretanje programa 6 1.6 Reference na objekte 7 1.7 Operatori 9 1.8 Kontrola toka programa 9 1.9 Inicijalizacija objekata 10 1.10 Uništavanje objekata 10 1.11 Metode i njihovi parametri 11 1.12 Ključna reč final 13 1.13 Ključna reč static 14 1.14 Nizovi 14 1.15 Višedimenzionalni nizovi 15 1.16 Paketi, CLASSPATH i JAR arhive 17

1.16.1 Paketi 17 1.16.2 CLASSPATH 19 1.16.3 JAR arhive 19 1.16.4 Podrazumevane komponente u CLASSPATH-u 20

1.17 Zadatak: klasa Matrix 21 1.18 Nasleđivanje 22 1.19 Modifikatori pristupa 22 1.20 Redefinisanje metoda 23 1.21 Apstraktne klase 23 1.22 Interfejsi 24 1.23 Unutrašnje klase 24 1.24 Polimorfizam 25

Page 3: 68261077 Java i Internet Programiranje1

1.25 Izuzeci 25 1.26 Klasa Object 28 1.27 Klasa String 28 1.28 Primeri nekih klasa iz standardne biblioteke 29

1.28.1 Klasa java.util.Vector 30 1.28.2 Klasa java.util.Hashtable 30 1.28.3 Klasa java.util.StringTokenizer 31

1.29 Konvencije davanja imena 31 1.30 Generisanje programske dokumentacije i javadoc 31 1.31 Zadaci: modifikacije klase Matrix 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 više 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 događajima 50

3.7.1 Događaji, osluškivači i komponente 50 3.7.2 Osluškivači kao unutrašnje klase 51

3.8 Primeri korišćenja standardnih komponenti 53 3.9 Apleti 57

3.9.1 Pojam apleta 57 3.9.2 Web čitači i Java Plug-In 58 3.9.3. Apleti i komponente korisničkog interfejsa 59

3.10. Aplet i aplikacija istovremeno 60 3.11. Korisnički definisane komponente 61 3.12. JavaBeans 63

4. Mrežno programiranje u Javi ................................................................................66 4.1. Osnovne karakteristike 66

4.1.1. Pojam socket-a 66 4.2. Identifikacija čvorova mreže 67 4.3. Klasa Socket 67 4.4. Tipičan tok komunikacije – klijent strana 68 4.5. Klasa ServerSocket 69 4.6. Tipičan tok komunikacije – server strana 69 4.7. Server koji opslužuje više klijenata 70 4.8 Primer klijent/server komunikacije 70

Page 4: 68261077 Java i Internet Programiranje1

4.9. Zadatak: klijent i server za listanje sadržaja direktorijuma 74

5. Vežba: 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 izvršavanje istih SQL naredbi 85 6.7. Pozivanje uskladištenih procedura 87 6.8. Upravljanje transakcijama 90 6.9. Dodatak: inicijalizacija drajvera 91

7. Uvod u višeslojne klijent/server sisteme............................................................93 7.1. Klasični klijent/server sistemi 93 7.2. WWW i Java kao platforma za klijente 94 7.3. Troslojni klijent/server sistemi 95

8. Dinamičko generisanje HTML-a i servleti .........................................................99 8.1. HTTP protokol 99 8.2. Statički i dinamički Web sadržaji 101 8.3. Servleti 101

8.3.1. Metoda init 102 8.3.2. Metoda destroy 102 8.3.3. Metoda doGet 102

8.4. Primer: elementarni servlet 102 8.5. Analiza zaglavlja HTTP zahteva 103 8.6. Konkurentni pristup servletu 104 8.7. Praćenje sesije korisnika 105 8.8. Preuzimanje podataka sa HTML formi 108

8.8.1 GET i POST zahtevi 109 8.9 Pristup bazama podataka iz servleta 111

9. Java Server Pages ...................................................................................................114 9.1. JSP koncept 114 9.2. Vrste dinamičkih elemenata 115

9.2.1. Izrazi 115 9.2.2. Skriptleti 115 9.2.3. Deklaracije 117 9.2.4. Direktive 117

9.3. Predefinisane promenljive 118 9.4. Skladištenje podataka i JavaBeans 119 9.5. Opseg vidljivosti JavaBean komponenti 120 9.6. Definisanje sopstvenih tagova 121

Page 5: 68261077 Java i Internet Programiranje1

10. Tehnologije distribuiranih objekata ...............................................................123 10.1. Osnovni koncepti 123 10.2. RMI 124

10.2.1. Faze u pisanju RMI programa 124 10.2.2. RMI interfejs 125 10.2.3. RMI serverski objekat 126 10.2.4. RMI registry 126 10.2.5. RMI klijent 127 10.2.6. Primer RMI programa 127 10.2.7. RMI i multithreading 129

10.3. CORBA 129 10.3.1. Osnovne odrednice 129 10.3.2. IDL 130 10.3.3. CORBA Naming Service 131 10.3.4. Proces pisanja CORBA programa 131 10.3.5. CORBA izuzeci 134 10.3.6. Pozivi unatrag 136 10.3.7. RMI i CORBA 137

10.4. Enterprise JavaBeans 137 10.4.1. Session Beans 138 10.4.2. Entity Beans 139 10.4.3. Komunikacija klijenata sa EJB komponentama 140 10.4.4. Struktura EJB komponente 140

11. Vežba: 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

Page 6: 68261077 Java i Internet Programiranje1

1

Poglavlje 0

Namena i program kursa

Kurs “Java i Internet programiranje” ima za cilj upoznavanje polaznika sa programskim jezikom Java i arhitekturom višeslojnih Internet/intranet sistema i odgovarajućim Java tehnologijama za njihovu implementaciju. Nakon završenog kursa polaznici su osposobljeni da samostalno produbljuju znanja iz prikazanih oblasti i da učestvuju u razvoju softerskih sistema koji su predmet kursa.

Kurs je predviđen za izvođenje u laboratorijskim uslovima, na odgovarajućoj računarskoj opremi. Materijal za kurs čine slajdovi koji se prikazuju u toku izlaganja, Web sajt koji sadrži primere prikazane tokom izlaganja, zadatke za vežbu, literaturu koja se preporučuje za detaljnije proučavanje materije i potreban softver koji je u javnom vlasništvu. Ovaj praktikum je, takođe, sastavni deo tog materijala.

0.1 Potrebno predznanje Za polaznike kursa je neophodno da poseduju znanja iz sledećih oblasti:

• objektno-orijentisano programiranje: poznavanje osnovnih pojmova i konce-pata (klasa, objekat, apstrakcija, nasleđivanje, polimorfizam);

• konkurentno programiranje: pojmovi procesa i niti; raspoređivanje procesa, sinhronizacija procesa, nedeljive operacije;

• relacione baze podataka i SQL: poznavanje relacionog modela podataka, njegova implementacija u okviru sistema za upravljanje relacionim baza-ma podataka, upotreba jezika SQL za operacije nad bazom podataka;

• HTML: osnovni elementi strukture HTML dokumenata, rukovanje Web čitačima;

Za polaznike je poželjno, ali ne i obavezno, poznavanje jezika C++. Polaznici koji poznaju ovaj jezik mogu daleko brže usvajati materiju koja se izlaže na početku kursa.

0.2 Program kursa Kurs se sastoji iz više tema koje obuhvataju obradu nove materije i vežbanja. U ovom odeljku dat je sažet pregled sadržaja kursa po odgovarajućim temama.

Page 7: 68261077 Java i Internet Programiranje1

2

U prvom poglavlju, Uvod u programski jezik Java, govori se o osnovnim karakte-ristikama Jave, kao programskog jezika, i kao platforme za izvršavanje pro-grama. Upoznaje se koncept Java virtuelne mašine (JVM) i prenosivosti prevedenog Java koda. Zatim se vrši pregled osobina Jave kao programskog jezika, i obrađuju se jezičke konstrukcije.

Drugo poglavlje nosi naziv Konkurentno programiranje u Javi i donosi pregled jezičkih koncepata koji omogućavaju pisanje konkurentnih programa.

Treće poglavlje, GUI aplikacije i JavaBeans, predstavlja sažeti prikaz pisanja aplikacija i apleta sa grafičkim korisničkim interfejsom. Definiše se struktura ovakvih aplikacija, način reagovanja na događaje koje izaziva korisnik i navode primeri korišćenja brojnih komponenti za izgradnju korisničkog interfejsa.

Četvrto poglavlje, Mrežno programiranje u Javi, definiše pojmove koji se koriste prilikom pisanja programa koji komuniciraju preko mreže, a zatim opisuje elemente jezika koji se koriste za pisanje ovakvih programa. Podrazumeva se rad preko TCP/IP mreže.

Peto poglavlje sadrži vežbu, čiji je cilj konstrukcija mrežne klijent/server aplikacije za chat preko TCP/IP mreže. Konstrukcija ovakvog sistema obuhvata sve prethodno obrađene teme.

U šestom poglavlju Rad sa bazama podataka – JDBC dat je uvod u metode pri-stupa i korišćenja relacionih baza podataka iz Java programa. Podrazumeva se korišćenje sistema za upravljanje relacionim bazama podataka sa kojima se komunicira preko jezika SQL.

Sedmo poglavlje, Uvod u višeslojne klijent/server sisteme, definiše okvire u kojima se nalazi materija izložena u narednim poglavljima.

Osmo poglavlje – Dinamičko generisanje HTML-a i servleti – prikazuje servlete, osnovnu Java tehnologiju za dinamičko generisanje Web sadržaja i izgradnju Web sajtova.

U narednom poglavlju, Java Server Pages, predstavljena je tehnologija za pisanje dinamičkih Web stranica koja omogućava razdvajanje zadataka Web dizajnera i programera, pojednostavljujući tako razvoj Web-orijentisanih informacionih sistema.

Deseto poglavlje, Tehnologije distribuiranih objekata, donosi sažet prikaz tehno-logija namenjenih za pisanje distribuiranih objektno-orijentisanih aplikacija dostupnih iz programskog jezika Java.

Poslednje poglavlje sadrži vežbu čiji je cilj konstrukcija Web aplikacije za elektronsko poslovanje. Zadatak je napisati softver za Web sajt koji omogućava kupovinu putem Web-a.

Page 8: 68261077 Java i Internet Programiranje1

3

Poglavlje 1

Uvod u programski jezik Java

1.1 Java virtuelna mašina Specifikacija Jave obuhvata dve relativno nezavisne celine: specifikaciju pro-gramskog jezika Java i specifikaciju Java virtuelne mašine (JVM). Specifikacija programskog jezika Java se ne razlikuje mnogo od sličnih specifikacija za druge jezike slične namene. Međutim, JVM specifikacija predstavlja novinu u odnosu na druge raširene objektno-orijentisane programske jezike opšte namene.

Naime, JVM specifikacija predstavlja, zapravo, specifikaciju platforme za izvr-šavanje Java programa u čijoj osnovi se nalazi programski model izmišljenog procesora. Programi napisani u programskom jeziku Java se prevode za ovakvu platformu za izvršavanje. Samim tim, prevedeni programi se ne mogu pokretati direktno na nekoj konkretnoj računarskoj platformi; potreban je poseban softver koji će takav prevedeni program da prilagodi konkretnoj mašini i operativnom sistemu. Zapravo, potreban je odgovarajući interpreter.

Kompanija koja je vlasnik jezika Java, Sun Microsystems, je stavila u javno vlasništvo JVM interpreter, kompajler i skup drugih razvojnih alata grupisanih u paket pod nazivom Java Development Kit (JDK). U pitanju su alati koji se pokreću iz komandne linije i nude samo osnovni set funkcija za razvoj softvera. Sun je izdao JDK paket za nekoliko različitih platformi: Windows, Solaris/ SPARC, Solaris/Intel i Linux/Intel.

Kako je Java specifikacija (i sam jezik i JVM) javno dostupna, drugi proizvođači su proizveli svoje implementacije Jave za različite platforme. Na primer, IBM nudi svoje verzije implementacije za većinu svojih hardversko/softverskih plat-formi, ali i za Linux na Intel mašinama.

Iako se najčešće programski jezik Java i Java virtuelna mašina pominju u paru, kao dve komplementarne specifikacije, nema prepreka da se Java kod prevodi i za izvršavanje na nekoj drugoj platformi (na primer, TowerJ paket generiše Windows izvršni kod). Takođe, nema prepreka da se neki drugi jezici prevode za izvršavanje u okviru Java virtuelne mašine.

Page 9: 68261077 Java i Internet Programiranje1

4

Kao posledica prethodno rečenog, može se reći da je Java kombinacija programskog jezika i platforme za izvršavanje programa koja ima nekoliko važnih osobina:

• Projektovana je tako da što manje zavisi od karakteristika konkretnog računarskog sistema na kome se izvršava.

• Jednom napisan i preveden program se može pokretati na bilo kojoj platformi za koju postoji odgovarajući JVM interpreter. Dakle, preno-sivost programa je garantovana na nivou izvršnog (prevedenog) koda.

• Java je interpretirani jezik, što ima odgovarajući efekat na brzinu izvr-šavanja programa.

Proizvod prevođenja izvornog Java koda je program predviđen za rad u okviru JVM, koji se često naziva bajt-kod (byte-code).

1.2 Programski jezik Java Iako je Java virtuelna mašina sastavni deo specifikacije, o njoj se govori veoma retko; praktično je koriste samo autori kompajlera i JVM interpretera iza konkretne računarske platforme. Sa druge strane, većina Java programera govori o drugom delu Java specifikacije, samom programskom jeziku Java, koji je i tema preostalog teksta u ovom poglavlju.

Može se reći da je Java objektno-orijentisani programski jezik opšte namene, posebno pogodan za pisanje konkurentnih, mrežnih 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 klasičnoj štampanoj formi i u elektronskom obliku koji je besplatan na http://www.bruceeckel.com.

1.3 Osnovni koncepti Sintaksa Jave izuzetno podseća na sintaksu jezika C++, mada nije jednaka njoj. Sintaksna pravila neće biti posebno obrađena, jer smatramo da su dovoljno očigledna iz primera koji slede.

1.3.1 Tipovi podataka Java operiše sa dve vrste tipova podataka: primitivnim tipovima i objektima. Primitivni tipovi su tipovi koji se sreću i u drugim jezicima, npr. celobrojni tip, karakter, itd. Tabela 1.1 sadrži spisak svih primitivnih tipova sa njihovim osnovnim karakteristikama.

Primitivni tip Veličina Minimum Maksimum boolean 1-bit - - char 16-bit Unicode 0 Unicode 216-1 byte 8-bit -128 +127 short 16-bit -215 +215-1 int 32-bit -231 +231-1 long 64-bit -263 +263-1

Page 10: 68261077 Java i Internet Programiranje1

5

Primitivni tip Veličina Minimum Maksimum float 32-bit IEEE 754 IEEE 754 double 64-bit IEEE 754 IEEE 754 void - - -

Tabela 1.1. Primitivni tipovi

Iz tabele se vidi da Java raspolaže primitivnim tipovima koji su na isti način definisani i u drugim programskim jezicima. Jedini izuzetak je tip char, koji zauzima dva bajta, umesto uobičajenog jednog bajta. Radi se o tome da se tipom char može predstaviti svaki karakter definisan Unicode standardom koji definiše kodni raspored koji obuhvata praktično sve današnje jezike (uključujući indoevropske, dalekoistočne, itd). To znači da su Java programi u startu osposobljeni da rade sa višejezičnim tekstom, ili u našim uslovima, ravnopravno sa srpskom latinicom i srpskom ćirilicom.

Treba primetiti da string, kao često korišćen tip podatka, nema odgovarajući primitivni tip u Javi, slično jezicima C i C++.

1.4 Klase i objekti Druga vrsta podataka sa kojima operiše Java program su objekti. Objekti predstavljaju osnovni koncept objektno-orijentisane paradigme u modelovanju sistema. Svaki objekat realnog sistema koga posmatramo predstavljamo odgovarajućim objektom koji je sastavni deo modela sistema. Objekte koji zajedničke osobine (ne moraju imati iste vrednosti tih osobina) možemo da opišemo 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 ključne reči jezika). Klasa ima naziv Automobil, definiše jedan atribut koji se zove radi i logičkog je tipa (boolean), i definiše dve metode koje se mogu pozvati nad objektima te klase, metode upali i ugasi.

Kreiranje objekata koji predstavljaju instance (primerke) ove klase može se obaviti na sledeći način:

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 može se pristupiti pomoću:

a.radi

Page 11: 68261077 Java i Internet Programiranje1

6

a poziv metoda upali i ugasi mogao bi da izgleda kao u sledećem primeru: a.upali(); b.ugasi();

Ovo do sada rečeno izuzetno podseća na C++. Neke od osobina Jave koje je bitno razlikuju u odnosu na C++ su:

• Nije moguće definisati promenljive i funkcije izvan neke klase. Samim tim, nije moguće 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 sadrže deklaraciju klase.

Kako Java ne dopušta postojanje bilo čega što bi postojalo izvan neke klase, postavlja se pitanje odakle počinje izvršavanje Java programa. C i C++ koriste funkciju main kao osnovnu funkciju od koje počinje izvršavanje programa. Java takođe 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 sadrži metodu main, i predstavlja primer jednog izvršivog Java programa dat je u sledećem primeru:

class Hello { public static void main(String args[]) { System.out.println(“Hello world!”); } }

(Trenutno nije bitno zašto metoda main mora biti definisana kao public static void, ali mora biti tako.) Kompletan tekst ove klase smešten je u datoteku Hello.java. Treba obratiti pažnju na naziv ove datoteke: njena ekstenzija je obavezno .java, a ime mora biti jednako imenu klase, uključujući i razliku između velikih i malih slova. Standardna preporuka je da se svaka klasa programa smešta u posebnu datoteku. Naziv datoteke mora odgovarati nazivu klase na prethodno opisani način. Iako će neki prevodioci dopustiti smeštanje teksta više klasa u isti fajl, ta praksa se ne preporučuje. Dakle, svakoj Java klasi odgovara jedan fajl sa identičnim nazivom i ekstenzijom .java.

1.5 Prevođenje i pokretanje programa Svaka Java klasa se može prevesti nezavisno od ostalih elemenata programa. Komanda kojom se klasa Hello iz prethodnog primera prevodi je

javac Hello.java

(Primeri za prevođenje i pokretanje opisuju korišćenje alata iz standardnog JDK paketa). Dakle, kompajler se poziva komandom javac, a kao parametri navode se imena onih datoteka koje želimo da prevedemo (može ih biti više, i možemo da koristimo džoker-znake). Treba obratiti pažnju na to da je navođenje ekstenzije datoteke obavezno, iako je ekstenzija uvek .java.

Page 12: 68261077 Java i Internet Programiranje1

7

Prevođenjem datoteke Hello.java dobija se datoteka Hello.class, koja sadrži 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 odgovarajućem .java fajlu, kao rezultat prevođenja daje odgovarajući .class fajl. Treba obratiti pažnju da je kod prevođenja 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) sledećom komandom:

java Hello

Ovog puta nije dozvoljeno navođenje ekstenzije .class prilikom pokretanja. U slučaju da se tako učini dobićemo poruku o grešci.

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 smeštene u odgovarajućim datotekama Automobil.java i Test.java. Njihovim prevođenjem dobijaju se dva .class fajla, Automobil.class i Test.class. Program se pokreće tako što se navodi ime one klase koja sadrži metodu main, što bi u ovom primeru bilo

java Test

Nema nikakve prepreke da više klasa koje čine program poseduju metodu main. Odakle će se početi sa izvršavanjem programa? To se određuje 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.

Page 13: 68261077 Java i Internet Programiranje1

8

Promenljiva a predstavlja, zapravo, referencu na objekat klase Automobil. Promenljiva a je lokalna promenljiva u metodi main, tako da se smešta na stek, na sličan način 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 klaseAutomobil

aheap

stek Slika 1.1. Referenca koja ukazuje na objekat

U tom smislu kaže se da je a “referenca na objekat” klase Automobil. Promenljiva a nije pointer u smislu kako ga definiše C++, jer nije dopuštena nikakva aritmetika sa ovakvim promenljivama, niti dodeljivanje proizvoljnih vrednosti. Jedina vrednost koju referenca može da sadrži je “adresa” (namerno je pod navodnicima jer to nije prava adresa u memoriji) pravilno inicijalizovanog objekta na koga ukazuje.

Sledeći primer prikazuje kreiranje dva objekta klase Automobil i inicijalizaciju referenci tako da ukazuju na odgovarajuće objekte. Reference se nalaze na steku programa, dok su objekti smešteni 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 klaseAutomobil

aheap

stek

b

objekat klaseAutomobil

Slika 1.2. Dve refence koje ukazuju na dva objekta

Ako se sada izvrši naredba b = a;

u memoriji će biti sledeća situacija (slika 1.3).

Page 14: 68261077 Java i Internet Programiranje1

9

objekat klaseAutomobil

aheap

stek

b

objekat klaseAutomobil

Slika 1.3. Situacija nakon kopiranja referenci

Postavlja se pitanje šta se u ovoj situaciji dešava sa objektom na koga je ukazivala referenca b: taj objekat više nije dostupan ni na jedan način, jer je jedina mogućnost da se nekoj referenci dodeli vrednost dodela vrednosti postojeće reference (tipa b = a) ili dodela vrednosti reference na novokreiran objekat (tipa a = new ...). Kako objekat više nije dostupan, valjalo bi ga ukloniti iz memorije kako bi se izbeglo “curenje memorije”. Java ne poseduje posebnu jezičku konstrukciju kojom se memorija dealocira (poput operatora delete u jeziku C++). Za dealokaciju memorije zadužen je poseban pozadinski proces programa koji se naziva garbage collector (“skupljač đubreta”). O garbage collector-u će biti više reči u odeljku 1.10.

1.7 Operatori Operatori koji služe za gradnju Java izraza su operatori koji su, praktično, preuzeti iz jezika C++. Možemo ih grupisati u nekoliko grupa:

• aritmetički operatori (+, -, *, /) • relacioni operatori (==, <, >, =, !=, >=, <=) • logički operatori (&&, ||, !) • bit-operatori (&, |, !) • operator dodele (=)

Bitna razlika u odnosu na C++ je postojanje primitivnog tipa boolean; vrednost logičkih izraza mora biti ovog tipa. To znači da su vrednosti u if ili while konstrukcijama logičkog tipa, pa nije moguće pisati

while (1)

ili, još opasnije if (a = 1)

Prioritet operatora je definisan na standardan način. 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 između Jave i jezika C++ i u ovom slučaju, pa za detaljnije informacije treba konsultovati specifikaciju jezika. Dostupne konstrukcije su sledeće:

Page 15: 68261077 Java i Internet Programiranje1

10

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

1.9 Inicijalizacija objekata Prilikom konstruisanja novog objekta, nakon alokacije potrebne memorije za smeštaj objekta, biće 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 vrši i inicijalizacija ove reference na novokreirani objekat. U trenutku kada se izvrši ovaj red (zapravo kreiranje objekta pomoću new A()), na konzoli će se ispisati

konstruktor

što je rezultat izvršavanja 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 Uništavanje objekata U odeljku 1.6 već je bilo reči o problemu uklanjanja nedostupnih objekata iz memorije. Za taj posao zadužen je poseban pozadinski proces koji se naziva garbage collector (GC). Ovaj proces radi nezavisno od pokrenutog programa, u smislu da sam odlučuje u kom trenutku će iz memorije osloboditi koji nedostupni objekat. Pored automatske dealokacije memorije, GC je zadužen 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, mrežne konekcije, itd). Ukoliko je

Page 16: 68261077 Java i Internet Programiranje1

11

neophodno obaviti neku operaciju neposredno pre nego što GC uništi objekat, ta operacija se može implementirati u okviru specijalne metode finalize. Metoda finalize se poziva neposredno pre uništavanja objekta od strane GC-a.

Ovde je važno naglasiti da metodu finalize ne treba koristiti za oslobađanje zauzetih resursa, jer se metoda finalize ne mora pozvati! Naime, GC sam određuje kada će ukloniti objekat iz memorije, i lako se može desiti da se to nikad ne dogodi: program je pokrenut, radio je neko vreme, računar raspolaže sa dovoljno radne memorije tako da GC nije počeo sa uklanjanjem objekata, i tako sve do završetka rada programa; GC nije uklonio objekat, samim tim nije pozvao metodu finalize, i eventualno oslobađanje zauzetih resursa se nije ni desilo.

Sledi primer klase koja implementira metodu finalize (pokretanjem ovog pro-grama sa java A verovatno će se demonstrirati mogućnost 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 navođenja 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 vraća vrednost tipa void (tj. ne vraća rezultat).

void metoda(String name, int value, boolean test) { ... }

Parametri metode mogu biti primitivni tipovi i reference na objekte; tip rezultata metode može 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 korišćena kao parametar nakon povratka iz metode. Posmatrajmo sledeću metodu:

void test(Automobil a) { a.radi = true; }

U pitanju je metoda koja u okviru svog tela vrši modifikaciju svog parametra a preko metode upali. (Koristi se klasa Automobil definisana u prethodnim primerima). U slučaju da se ova metoda pozove u sledećem segmentu koda:

Automobil x = new Automobil();

Page 17: 68261077 Java i Internet Programiranje1

12

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

vrednost atributa radi objekta x biće 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, slično kao i u drugim programskim jezicima, smeštaju 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 slučaju svodi na promenu vrednosti atributa radi na true (slika c). Kod vraćanja iz metode nazad, sa steka se uklanjaju parametri korišćeni prilikom poziva metode. Tako se sa steka uklanja druga kopija reference x i ostaje samo originalna referenca. Kada preko nje pristupimo atributu radi, videćemo da je on promenio vrednost (slika d).

x x

xradi: false

a)

radi: false

b)

x

x

radi: true

c)x

radi: true

d) Slika 1.4. Promena stanja objekta koji je parametar metode

Nakon ovog primera može se zaključiti sledeće: promene nad parametrima metode načinjene u okviru tela metode koji su reference na objekte su vidljive nakon povratka iz metode. Ili, kako se to sreće 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 ponašaju 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 dešava u ovom slučaju: deklariše se promenljiva a tipa int i odmah se inicijalizuje na vrednost 0. Lokalne promenljive primitivnog

Page 18: 68261077 Java i Internet Programiranje1

13

tipa se smeštaju na stek (trenutno stanje ilustruje slika a). Zatim se poziva metoda test sa parametrom a; parametar a se smešta 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)

a = 0

a = 1

c)

a = 0

d)

b)

Slika 1.5. Promena vrednosti parametra metode koji je primitivnog tipa

Preklapanje metoda (method overloading) je u Javi dopušteno. Preklopljene metode su metode koje imaju isto ime, ali se razlikuju po listi parametara. Kompajler ih smatra za sasvim različite metode, bez obzira što imaju isto ime. Metode ne mogu da se razlikuju samo po tipu rezultata kojeg vraćaju. Sledi primer klase sa tri preklopljene metode:

class A { int metoda() { ... } int metoda(int i) { ... } int metoda(String s) { ... } }

Preklapanje metoda se odnosi kako na klasične metode, tako i na konstruktore.

1.12 Ključna reč final Ključna reč final se može naći ispred definicije atributa ili metode unutar definicije klase. Ako se nađe ispred atributa, označava atribut kome nije moguće promeniti vrednost. Drugim rečima, final atribut predstavlja konstantu. Inicijalizacija prilikom deklaracije atributa je obavezna. Primer definicije jednog final atributa bio bi:

final int size = 100;

Ključna reč final ima drugo značenje kod metoda: označava metode koje se ne mogu redefinisati prilikom nasleđivanja date klase. O nasleđivanju će više biti reči u odeljku 1.18, a o redefinsanju metoda u odeljku 1.20. Primer jedne final metode glasi:

final int metoda(int i) { ... }

Page 19: 68261077 Java i Internet Programiranje1

14

1.13 Ključna reč static Ključna reč static se može naći ispred definicije atributa ili metode, nezavisno od pojave ključne reči final. Kada se nađe ispred definicije atributa, označava atribut koji pripada klasi, a ne objektima (kao instancama klase). Drugim rečima, može se reći da svi objekti date klase dele istu vrednost statičkog atributa. Na primer, klasa StaticTest poseduje jedan statički atribut:

class StaticTest { static int i = 0; static void metoda() { i++; } }

Tada će sledeći 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

Statički atribut je pridružen klasi, a ne njenim instancama. U tom smislu, može 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 sledećem primeru:

StaticTest.i++;

Statičke metode su metode koje ne mogu biti pozvane nad objektima-instancama klase, već nad klasom samom. U tom smislu, nije moguće pisati

a.metoda();

nego samo StaticTest.metoda();

Statičke metode imaju pristup samo statičkim atributima klase.

Često korišćeni primer upotrebe statičkog atributa je ispisivanje na konzolu: System.out.println("Hello, world!");

pri čemu se poziva metoda println objekta out koji je statički atribut klase System (out zapravo predstavlja standardni izlaz, slično kao stdout u jeziku C).

1.14 Nizovi Nizovi se u Javi definišu vrlo slično kao u jeziku C++. Na primer, niz čiji su elementi tipa int se definiše na sledeći način:

int[] a; ili int a[];

Ovim je samo definisana referenca na niz; niz se nakon toga mora kreirati na način sličan kreiranju objekata. Sledi primer gde se alocira niz od pet int elemenata:

a = new int[5];

Page 20: 68261077 Java i Internet Programiranje1

15

Prvi element niza ima indeks nula. Postoji i način da se niz definiše, alocira memorija za njega i odmah inicijalizuje, kao u sledećem primeru:

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

Treba voditi računa o tome da se prilikom definicije niza referenca na niz čuva na steku, dok se elementi niza čuvaju na heap-u, slično kao i objekti. Slika 1.6 ilustruje ovu situaciju za niz definisan u prethodnom primeru.

a heap

1 2 3 4 5

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 slučaj kada je u pitanju niz čiji su elementi objekti neke klase. Na primer, niz od 5 elemenata klase Automobil se definiše 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 može inicijalizovati, recimo, u odgovarajućoj 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 dužinu alociranog niza). Sada će stanje u memoriji izgledati kao na slici 1.8.

parking heapa1 a2 a3 a4 a5

Slika 1.8. Niz inicijalizovanih objekata

1.15 Višedimenzionalni nizovi Višedimenzionalni nizovi se predstavljaju kao nizovi nizova. Sintaksa je slična jeziku C++. Na primer, definicija

Page 21: 68261077 Java i Internet Programiranje1

16

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.

a heap1 2 3

4 5 6

Slika 1.9. Dvodimenzionalni niz

Višedimenzionalni niz se može kreirati na sledeći način: int[][] a = new int[2][3];

pri čemu će se izvršiti potrebna alokacija memorije, ali ne i inicijalizacija vrednosti elemenata niza. Dvodimenzionalni niz se može kreirati i postupno, kao u sledećem primeru:

int[][] a = new int[2][]; for (int i = 0; i < a.length; i++) a[i] = new int[3];

Prilikom kreiranja višedimenzionalnog niza čiji su elementi objekti, a ne primitivnog tipa, potrebno je još izvršiti 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 izgledaće kao na slici 1.10.

parking heapa1 a2 a3

a4 a5 a6

Slika 1.10 Višedimenzionalni niz čiji elementi su objekti

Višedimenzionalni niz objekata može se inicijalizovati odmah prilikom definicije, slično kao kod višedimenzionalnog niza primitivnih tipova. Sledi primer:

Automobil[][] a = { { new Automobil(), new Automobil() }, { new Automobil(), new Automobil() } };

Page 22: 68261077 Java i Internet Programiranje1

17

1.16 Paketi, CLASSPATH i JAR arhive

1.16.1 Paketi Java programi se sastoje isključivo iz klasa. Broj klasa koje čine program može biti relativno velik, pa je uvođenje nekakve organizacije u takav skup klasa neophodno. Paketi su način da se klase grupišu po nekom kriterijumu. Paketi mogu da sadrže 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 može da sadrži klase i potpakete, koji sa svoje strane takođe mogu da sadrže 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 odgovarajućim datotekama.

Klasa koja se nalazi u nekom paketu (osim korenskog), mora u okviru svoje datoteke imati odgovarajuću deklaraciju, kao u sledećem primeru:

package paket1; class Automobil { ... }

Deklaracija package se mora nalaziti na samom početku teksta datoteke, tj. nijedna druga deklaracija se ne sme nalaziti ispred nje. Datoteka Automobil.java mora biti smeštena u direktorijum paket1 koji se nalazi u korenskom direkto-rijumu aplikacije. Naziv korenskog direktorijuma nije važan, niti je važno gde se on nalazi u okviru fajl-sistema. Prevođenje klase Automobil se mora obaviti komandom:

Važno je primetiti da se komanda za prevođenje 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 početi odgovarajućom deklaracijom:

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

Page 23: 68261077 Java i Internet Programiranje1

18

Vidimo da se za separaciju imena paketa u okviru Java programa koristi tačka, a ne kosa crta ili obrnuta kosa crta.

Već je rečeno da se prilikom pokretanja programa navodi ime one klase koja sadrži metodu main. Prilikom navođenja imena ove klase mora se navesti njeno puno ime, uključujući i paket u kome se klasa nalazi. Na primer, ukoliko klasa Tocak poseduje metodu main, i želimo da odatle počne izvršavanje programa, program moramo pokrenuti pomoću sledeće komande:

Dakle, ponovo je važno 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 odgovarajućeg direktorijuma. Separator naziva paketa je, kao što je već rečeno, tačka, a ne kosa crta ili obrnuta kosa crta.

Programski jezik Java stiže sa velikim brojem klasa grupisanim u pakete. Te klase su dostupne kao i klase koje sami pišemo (čak se može 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 navođenje pune putanje do nje (odnosno navođenje odgovarajućeg paketa), to bi program učinilo manje čitljivim. Zato je moguće na početku 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 navođenjem njenog imena, bez imena paketa u kome se nalazi. Treba obratiti pažnju na to da se import deklaracija mora nalaziti između (opcione) package deklaracije i definicije klase.

Ukoliko koristimo više klasa iz istog paketa, moramo svaku od njih navesti u odgovarajućoj import deklaraciji. Drugi način je da se importuju sve klase iz datog paketa pomoću džoker-znaka *:

package paket1; import java.util.*; class Automobil { ... }

Ovakav način importovanja ne obuhvata i sve potpakete importovanog paketa! Niti je dozvoljeno korišćenje džoker znakova kao u primeru:

import java.util.Vec*; // nije dozvoljeno!

Klase koje se nalaze u paketu java.lang nije potrebno importovati. Odgovarajuća import deklaracija se podrazumeva.

Page 24: 68261077 Java i Internet Programiranje1

19

1.16.2 CLASSPATH S obzirom na do sada izloženo, korišćenje klase Vector iz paketa java.util bi značilo da se odgovarajuće stablo direktorijuma java\util\... koje sadrži kompajlirane klase mora kopirati unutar strukture direktorijuma svake aplikacije koju pišemo. Time se bespotrebno zauzima prostor i komplikuje održavanje softvera. Zato postoji način da se paketi sa klasama koji se koriste iz više aplikacija čuvaju na jednom mestu, a sve aplikacije će pomoću odgovara-jućeg mehanizma te klase videti kao da je struktura direktorijuma iskopirana u okviru svake aplikacije. U pitanju je mehanizam sličan korišćenju PATH pro-menljive okruženja (environment variable).

Java interpreter za ovu svrhu koristi promenljivu okruženja koja se naziva CLASSPATH. Ona sadrži listu direktorijuma u kojima treba tražiti klase koje se koriste. Na primer, ukoliko je cela java\... hijerarhija paketa smeštena u direktorijum C:\java\lib, vrednost CLASSPATH promenljive bi mogla da glasi:

CLASSPATH=C:\java\lib

čime bi sve klase smeštene po svojim paketima unutar direktorijuma C:\java\lib bile vidljive za sve Java aplikacije. Ukoliko CLASSPATH treba da sadrži više direktorijuma, oni se navode jedan za drugim, sa tačkom-zarez kao separatorom. Na primer:

CLASSPATH=C:\java\lib;D:\mojalib

Na UNIX sistemima sintaksa navođenja vrednosti CLASSPATH promenljive se unekoliko razlikuje, time što se direktorijumi razdvajaju znakom dvotačka.

Ukoliko u CLASSPATH dodamo direktorijum D:\temp\korenski paket iz pretho-dnih primera, na primer komandom:

tada i naš program možemo pokrenuti sa bilo kog mesta u okviru fajl sistema, jer će klase biti vidljive preko CLASSPATH-a. Na primer, komanda:

će pokrenuti naš program iako se ne nalazimo u direktorijumu D:\temp\korenski paket.

1.16.3 JAR arhive Distribucija biblioteka klasa smeštenih u svoje pakete nije preterano elegantna u slučaju većeg broja klasa i paketa, jer se povećava broj datoteka i direktorijuma koje treba instalirati i navesti u CLASSPATH-u. Zbog toga je omogućeno arhiviranje biblioteka u tzv. JAR arhive. U pitanju su arhive koje sadrže klase u svojim paketima arhivirane u klasičnom Zip formatu. Podrazumevana ekstenzija im je .jar (mada može biti i .zip). Ovakve arhive se mogu generisati alatkom jar koja je sastavni deo JDK paketa, ali mogu i bilo kojim drugim programom koji može da generiše Zip arhive (ekstenziju možemo sami promeniti kasnije). Sve klase iz osnovne Java biblioteke su, prilikom instalacije

Page 25: 68261077 Java i Internet Programiranje1

20

JDK paketa, smeštene u datoteku %JAVA_HOME%\jre\lib\rt.jar, gde je JAVA_HOME direktorijum gde je instaliran JDK paket. Ovu datoteku možemo 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 sadržaj arhive rt.jar, možemo navesti samu datoteku rt.jar (sa svojom putanjom) i dobićemo isti efekat. Na primer:

CLASSPATH=C:\jdk1.3\jre\lib\rt.jar

Dakle, CLASSPATH može da sadrži nazive direktorijuma i Zip arhiva u kojima se nalaze deljene biblioteke. Korišćenje direktorijuma u arhiva je u ovom slučaju potpuno ravnopravno.

1.16.4 Podrazumevane komponente u CLASSPATH-u U dosadašnjim primerima je naglašavano da se prilikom pokretanja programa moramo nalaziti u korenskom direktorijumu aplikacije. To je, zapravo, posledica činjenice da se tekući direktorijum u kome se nalazimo nalazi u CLASSPATH-u kada on nije definisan, kao da je

CLASSPATH=.

gde je tačka (.) oznaka za tekući direktorijum. Ako se CLASSPATH promenljiva definiše, tekući 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 reči u prethodnom odeljku. Nju ne moramo navoditi čak ni kada definišemo promenljivu CLASSPATH. Dakle, svaki CLASSPATH uvek sadrži ovu dve komponentu:

CLASSPATH=C:\jdk1.3\jre\lib\rt.jar

Ukoliko CLASSPATH uopšte nije definisan, onda on obuhvata i tekući direktorijum, tako da se može reći da CLASSPATH u tom slučaju glasi:

Page 26: 68261077 Java i Internet Programiranje1

21

CLASSPATH=.;C:\jdk1.3\jre\lib\rt.jar

Ovde treba obratiti pažnju 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 brži. jikes nije deo standardne JDK instalacije i mora se instalirati posebno. Do svoje verzije 1.02 (tekuća verzija u ovom trenutku) on se ponaša kao Java 1.1 kompajler, tako da nema podrazumevanih komponenti u CLASSPATH-u. Kako je prilično nezgodno menjati sadržaj CLASSPATH promenljive naizmenično za kompajliranje jikes-om i pokretanje java-om, problem se može prevazići korišćenjem promenljive okruženja JIKESPATH koju koristi isključivo jikes. Ona ima isto značenje kao CLASSPATH do Java verzije 1.1. Dakle, ako CLASSPATH ima sadržaj:

CLASSPATH=.;D:\nekamojabibl.zip

JIKESPATH bi trebalo da ima sledeći sadržaj: 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 odgovarajućim kometarom u tekstu klase.

Napomena: u realizaciji izostaviti provere ispravnosti parametara (odgovarajuće dimenzije matrica). class Matrix { /* Postavlja sadržaj matrice. */ void setData(double[][] x) {...} /* Vraća sadržaj matrice. */ int[][] getData() {...} /* Množi sadržaj matrice objekta koji je pozvan (this) sa sadržajem matrice b (objekta koji je prosleđen kao parametar). Rezultat množenja smešta u novi objekat koga vraća kao rezultat metode. */ Matrix multiply(Matrix b) {...} /* Množi sadržaj dve date matrice i rezultat množenja vraća kao rezultat metode. Obratiti pažnju da je ovo statička metoda! */ static Matrix multiply(Matrix a, Matrix b){...} /* Množi sadržaj matrice objekta koji je pozvan (this) sa sadržajem matrice b (objekta koji je prosleđen kao parametar). Rezultat množenja se smesta u matricu objekta koji je pozvan. Metoda ne vraća nikakav rezultat! */ void multiply2(Matrix b) {...} /* Polazna tačka programa. Služi za testiranje funkcionalnosti ostalih metoda klase. Za potrebe testiranja formirati nekoliko

Page 27: 68261077 Java i Internet Programiranje1

22

višedimenzionalnih nizova. */ public static void main(String[] args) {...} /* Sadrzaj matrice */ double[][] data; /* Dimenzije matrice */ int n, m; }

1.18 Nasleđivanje Nasleđivanje, kao jedan od osnovnih koncepata objektno-orijentisanog pro-gramiranja, postoji i u Javi. Kada jedna klasa nasleđuje drugu, potrebno je to naglasiti u okviru teksta klase klazulom extends kao u sledećem primeru, gde klasa BorbeniAvion nasleđuje 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 dopušta višestruko nasleđivanje (onako kako je to definisano recimo u jeziku C++). Dakle klasa može da nasledi najviše jednu klasu.

1.19 Modifikatori pristupa U Javi postoje sledeća tri modifikatora pristupa:

• public: označava 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 sličan način modifikatori pristupa se mogu primeniti i na celu klasu, na primer:

public class Avion { ... }

Page 28: 68261077 Java i Internet Programiranje1

23

1.20 Redefinisanje metoda Redefinisanje metoda (method overriding) je postupak kada klasa naslednica redefiniše telo metode nasleđene od roditeljske klase. U Javi se to specificira prostim navođenjem 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 slučaju da se izvrši sledeći segment koda: A varA = new A(); B varB = new B(); varA.metoda1(); varB.metoda1(); varA.metoda2(); varB.metoda2();

na konzoli će se ispisati: metoda1 klase A metoda1 klase B metoda2 klase A metoda2 klase 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 proglašena za apstraktnu korišćenjem ključne reči abstract. Njena implementacija nije navedena. Samim tim klasa je apstraktna pa se i za nju to mora navesti navođenjem ključne reči abstract ispred class. Dakle iskaz poput:

A x = new A();

Page 29: 68261077 Java i Internet Programiranje1

24

nije dopušten.

1.22 Interfejsi Interfejsi su poseban koncept u Javi: nisu u pitanju klase, ali interfejsi mogu da sadrže deklaracije apstraktnih metoda, konstanti i statičkih atributa. Sledi primer:

interface Instrument { void sviraj(); void nastimaj(); }

Vidimo da interfejsi podsećaju na apstraktne klase. Veza između klasa i interfejsa je sledeća: kaže se da klasa implementira (a ne nasleđuje) interfejs. Klasa može da implementira više interfejsa istovremeno, što nije slučaj sa nasleđivanjem. Nema prepreke da klasa koja nasleđuje drugu klasu implementira i neke interfejse. Jedan interfejs može 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 neće dopustiti prevođenje klase koja implementira interfejs a nije redefinisala sve njegove metode.

1.23 Unutrašnje klase Od Java verzije 1.1 klasa može, osim atributa i metoda, da poseduje i tzv. unutrašnje 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 može promeniti modifikatorima pristupa na uobičajen način. Instanca unutrašnje klase se može kreirati i izvan nje, ali samo preko instance spoljašnje klase, kao u sledećem primeru:

Spoljasnja s = new Spoljasnja(); Spoljasnja.Unutrasnja u = s.new Unutrasnja();

Sledeći izraz (pokušaj konstrukcije instance inutrašnje klase bez instance spoljašnje klase) nije dozvoljen:

Spoljasnja.Unutrasnja u = new Spoljasnja.Unutrasnja();

Koncept unutrašnjih klasa se najviše koristi prilikom izgradnje grafičkog korisničkog interfejsa, o čemu će više reči biti u poglavlju 3.

Page 30: 68261077 Java i Internet Programiranje1

25

1.24 Polimorfizam Polimorfizam je koncept koji omogućava objektima da ispolje različito ponašanje, zavisno od njihove klase, bez obzira što se oni koriste kao instance nekog zajedničkog roditelja. Posmatrajmo sledeće tri klase:

abstract class Instrument { abstract void sviraj(); } class Violina extends Instrument { void sviraj() { ... } } class Klarinet extends Instrument { void sviraj() { ... } }

Dakle, primer definiše tri klase: klasa Instrument je apstraktna klasa (njena metoda sviraj je apstraktna), a klase Violina i Klarinet nasleđuju klasu Instrument i, naravno, implementiraju (zapravo, redefinišu) apstraktnu metodu. Posma-trajmo 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 može imati instance, jer je apstraktna. Ova metoda će ipak biti upotrebljiva, jer se njoj kao parametar može proslediti instanca neke klase koja nasleđuje klasu Instrument – u ovom slučaju 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 različite efekte zavisno od toga koji objekat prosledimo kao parametar. Određivanje koja metoda će se pozvati se obavlja u toku izvršavanja programa.

U primeru se vidi da ovo specijalno ponašanje metoda nije ničim naglašeno u tekstu programa. Ovakav efekat se u jeziku C++ postizao korišćenjem tzv. virtuelnih funkcija članica, a u Javi je ovo podrazumevano (i jedino moguće) ponašanje. Dakle, možemo reći, u terminologiji jezika C++, da su sve metode u Javi virtuelne, pa se ta osobina ne mora naglašavati posebno u programu.

1.25 Izuzeci Izuzeci su mehanizam za kontrolu toka programa koji se koristi za obradu grešaka nastalih u toku izvršavanja programa. Segment programskog koda za

Page 31: 68261077 Java i Internet Programiranje1

26

koji smatramo da može da izazove izuzetak možemo da smestimo u tzv. try/catch blok, kao u sledećem primeru:

try { // kod koji može da izazove izuzetak } catch (Exception ex) { System.out.println("Desio se izuzetak: " + ex); }

Izuzetak može biti, na primer, deljenje nulom, pristup elementu niza koji je izvan granice niza, itd. Ukoliko se prilikom izvršavanja koda koji se nalazi u try bloku desi izuzetak, tok izvršavanja programa se automatski prebacuje na početak catch bloka. Nakon izvršavanja 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.

Različite vrste izuzetaka su predstavljene različitim exception klasama, na primer: svi izuzeci prilikom izvršavanja aritmetičkih operacija (deljenje nulom, overflow, itd.) su predstavljene klasom ArithmeticException, pristup elementu čiji je indeks izvan granice niza je predstavljen klasom ArrayIndexOutOfBounds-Exception, itd. Klasa Exception je zajednički predak svim exception klasama.

Jedan try blok može imati više sebi pridruženih catch blokova, kao u sledećem primeru:

try { // kod koji može 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 izvršava u svakom slučaju }

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 slučaju znači: u pitanju je klasa kojoj exception objekat pripada, ili njen predak. Kada poslednji catch blok hvata izuzetak klase Exception, to znači da će svi izuzeci biti obrađeni, jer je klasa Exception zajednički roditelj.

Blok finally se ne mora navesti. On sadrži blok koda koji će se izvršiti u svakom slučaju, desio se izuzetak ili ne.

Page 32: 68261077 Java i Internet Programiranje1

27

Kao što postoje odgovarajuće klase koje opisuju različite vrste izuzetaka, moguće je definisati i nove vrste izuzetaka definicijom odgovarajuće klase. Na primer, možemo da definišemo 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 odgovarajuće konstruktore roditeljske klase Exception. Pisanje ovakvih konstruktora nije obavezno, ali je obavezno naslediti klasu Exception (ili nekog njenog potomka).

Ovakav korisnički izuzetak može biti izazvan samo programski, pomoću ključne reči throw, kao u sledećem primeru:

if (errorCheck()) throw new MojException("Houston, we have a problem.");

Programski kod koji sadrži ovakvu throw naredbu mora biti smešten unutar try bloka koji hvata izuzetak MojException – na to će nas naterati kompajler. Dakle, ovo bi moglo da izgleda na sledeći način:

try { if (errorCheck()) throw new MojException("Houston, we have a problem."); } catch (MojException ex) { System.out.println("Exception: " + ex); }

Drugi način da obradimo nastanak ovakvog izuzetka je da metodu u kojoj se nalazi throw naredba označimo kao metodu u kojoj može 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 odgovarajućem try bloku, ili metoda u kojoj sadrži ovaj poziv mora isto biti označena da može da izazove izuzetak. Dakle:

public void m1() { try { metoda(); } catch (MojException ex) { System.out.println("Exception: " + ex); }

}

Page 33: 68261077 Java i Internet Programiranje1

28

ili: public void m1() throws MojException { metoda(); }

Na ovaj način odgovornost za obradu izuzetka može propagirati sve do metode main od koje počinje izvršavanje programa!

1.26 Klasa Object Klasa Object predstavlja osnovnu klasu u hijerarhiji Java klasa, u smislu da sve klase nasleđuju klasu Object. Za klase za koje se navede da ne nasleđuju nijednu klasu (izostavljanjem extends klauzule) podrazumeva se da nasleđuju klasu Object. Klasa Object nije apstraktna, tako da je moguće kreirati objekat ove klase. Ona definiše neke metode koje se relativno često koriste, poput ovih koje su opisane u nastavku ovog odeljka.

public boolean equals(Object o);

Koristi se prilikom poređenja objekata; poređenje tipa (a == b) je zapravo poređenje referenci. Poređenje (a.equals(b)) vraća rezultat zavisno od implementacije metode equals klase kojoj pripada objekat a. Klasa Object definiše podrazumevano poređenje objekata koje se svodi na poređenje referenci.

public int hashCode();

Izračunava hash vrednost za dati objekat. Koristi se najviše u hash-tabelama. public String toString();

Vraća string reprezentaciju objekta. Ukoliko se ne redefiniše, poziva se implementacija iz klase Object koja vraća prilično nerazumljiv rezultat. Ova metoda ima donekle specijalan tretman od strane kompajlera, o čemu će više reči biti u sledećem odeljku.

1.27 Klasa String Klasa String se nalazi u paketu java.lang i predstavlja string kao tip podatka (sećamo se da stringovi nemaju odgovarajući 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 odgova-rajućim konstantama. Na primer, 16 je vrednost tipa int, vrednost 16L označava vrednost tipa long, 'x' je vrednost tipa char, itd. Objektima se ne može pridružiti vrednost koja se može predstaviti literalom, osim u slučaju 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

Page 34: 68261077 Java i Internet Programiranje1

29

String x = new String("tekst");

2. Java ne omogućava redefinisanje operatora (kao što je to moguće u jeziku C++). Međutim, operator + može da se upotrebi za konkatenaciju stringova. Kada naiđe na izraz poput

"ime" + "prezime" ili x + "prezime" ili x + y

kompajler će generisati kod koji će izvršiti konkatenaciju stringova i vratiti rezultat u obliku novokreiranog objekta klase String. Konkatenacija može 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 sadržaj je "vrednost: 10". U slučaju konkatenacije stringa sa objektom neke druge klase, kao na primer:

Automobil a = new Automobil(); String x = "Moj auto je: " + a;

biće pozvana metoda toString klase Automobil, pa će se zatim izvršiti konkatenacija stringova pomoću dobijenog stringa.

3. Metoda koja kao parametar ima tip String, može 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 prosleđuje metodi koja se poziva. Na primer, posmatrajmo metodu

public void handleMessage(String message) { ... }

i njen poziv handleMessage(new Automobil());

Ovaj poziv metode handleMessage biće, u stvari, preveden u sledeći poziv: handleMessage(new Automobil().toString());

4. Za razliku od svih ostalih parametara, parametri metoda tipa String se ponašaju kao da se prenose po vrednosti a ne po referenci, iako su u pitanju objekti, a ne primitivni tipovi. Na primer, poziv sledeće metode

public void handleMessage(String message) { message += "xxx"; }

neće izazvati promenu objekta koji je prosleđen 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 uništava 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 korišćenje 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 sigurnošću zna da su

Page 35: 68261077 Java i Internet Programiranje1

30

ove klase dostupne u svakoj Java instalaciji, pa se klase iz biblioteke mogu slobodno koristiti, bez bojazni da ih neće biti na računaru gde softver instalira, ili čak, u slučaju Jave 1.2, da ih neće biti u CLASSPATH-u.

1.28.1 Klasa java.util.Vector Klasa Vector služi kao “kontejnerska” klasa koja čuva kolekciju objekata kao sekvencu. Elementima sekvence se može 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 doda-vanje elemenata u vektor koristi se metoda addElement, a za pristup objektima koristi se metoda elementAt. Iako ovaj primer demonstrira smeštanje objekata iste klase u vektor, to ne mora biti slučaj. Nema prepreke da se u isti vektor smeštaju objekti različitih 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 ključeve na vrednosti (i ključevi i vrednosti su predstavljeni odgovarajućim objektima). Parovi (ključ, vrednost) se u metodu smeštaju metodom put. Vrednost se u tabeli traži pomoću svog ključa metodom get. Sledeći primer predstavlja generisanje 10000 slučajnih celih brojeva u opsegu [0, 19] i brojanje koliko se puta koji broj pojavio pomoću hash-tabele. Tabela kao ključ koristi generisani broj, a vrednost je objekat klase Counter koji služi kao brojač pojava odgovarajućeg 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 =

Page 36: 68261077 Java i Internet Programiranje1

31

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, generišu se tokeni u odgovarajućem redosledu. Metoda hasMoreTokens vraća true ako nije iscrpljena lista tokena dobijenih parsiranjem. Metoda nextToken vraća string koji predstavlja tekući 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 opšteprihvaćenih konvencija za davanje imena identifikatorima u Java programima.

1. Nazivi klasa se pišu malim slovima, osim što počinju velikim slovom (npr. Automobil, Vector). Ne postoji nikakav prefiks (kao CVector ili TVector). Ukoliko se naziv klase sastoji iz više reči, reči se spajaju, a svaka od njih počinje velikim slovom (npr. StringTokenizer, ArrayIndexOutOfBoundsException).

2. Nazivi metoda i atributa se pišu malim slovima (npr. size, width). Ako se sastoje od više reči, reči se spajaju, pri čemu sve reči počevši od druge počinju velikim slovom (npr. setSize, handleMessage).

3. Nazivi paketa se pišu isključivo malim slovima. Ukoliko se sastoje iz više reči, reči 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 sadrži i alat javadoc namenjen generisanju programske dokumentacije. Kao izvor informacija za generisanje dokumen-

Page 37: 68261077 Java i Internet Programiranje1

32

tacije koriste se specijalni komentari u izvornom kodu, koji počinju sa /** i završavaju sa */ (za razliku od klasičnih 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 smešta na odgovarajuće mesto u dokumentaciji. Slično 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 takođe i sledeće posebne oznake (tzv. doc tags): @param Opisuje parametar metode.

@param ime opis @return Opisuje rezultat metode.

@return opis @throws Opisuje izuzetak koji može 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 može da radi. Obično se koristi da označi minimalnu potrebnu verziju JDK paketa. @since since-info

@deprecated Označava zastarelu mogućnost koja može biti napu-štena u sledećim verzijama. Kompajler će generisati upozorenje ako se koriste ovakve metode ili atributi.

Primer jedne Java klase koja sadrži odgovarajuće 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 slučaju da je neka od * dimenzija manja od 1 */ public Matrix(int n, int m) throws MathException { ... } /** Postavlja sadržaj matrice. * @param x Nova vrednost matrice * @throws MathException U slučaju da je neka od

Page 38: 68261077 Java i Internet Programiranje1

33

* dimenzija manja od 1, ili je vrednost parametra * <code>null</code> */ public void setData(double[][] x) throws MathException { ... } /** Vraća sadržaj matrice. * @return Tekuća vrednost matrice */ public double[][] getData() { ... } /** Množi sadržaj matrice objekta koji je pozvan (this) sa * sadržajem matrice b (objekta koji je prosleđen kao * parametar). Rezultat množenja smešta u novi objekat koga * vraća kao rezultat metode. * @param b Druga matrica u množenju * @return Rezultat množenja * @throws MathException U slučaju neispravnih dimenzija matrica */ public Matrix multiply(Matrix b) throws MathException { ... } /** Množi sadržaj dve date matrice i rezultat množenja * vraća kao rezultat metode. Obratiti pažnju da je ovo * statička metoda! * @param a Prvi činilac poizvoda * @param b Drugi činilac proizvoda * @return Rezultat množenja * @throws MathException U slučaju neispravnih dimenzija matrica */ public static Matrix multiply(Matrix a, Matrix b) throws MathException { ... } /** Množi sadržaj matrice objekta koji je pozvan (this) sa * sadržajem matrice b (objekta koji je prosleđen kao * parametar). Rezultat množenja se smešta u matricu objekta * koji je pozvan. Metoda ne vraća nikakav rezultat! * @param b Drugi činilac proizvoda * @throws MathException U slučaju neispravnih dimenzija matrica */ public void multiply2(Matrix b) throws MathException { ... } /** Vraća string reprezentaciju objekta. * @return String reprezentacija objekta */ public String toString() { ... } /** Sadržaj matrice */ private double[][] data; /** Dimenzije matrice */ private int n, m; }

1.31. Zadaci: modifikacije klase Matrix Zadatak 2. Proširiti zadatak 1 tako da se u okviru metoda klase Matrix vrši provera dimenzija matrice na odgovarajući način. Signalizaciju greške reali-zovati pomoću mehanizma izuzetaka. Napisati sopstvenu klasu MathException za obradu izuzetaka ove vrste.

Page 39: 68261077 Java i Internet Programiranje1

34

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 odgovarajući način. b) Paket vta.math spakovati u JAR arhivu vta.jar. Arhivu dodati u CLASSPATH i pokrenuti klasu TestMatrix.

Page 40: 68261077 Java i Internet Programiranje1

35

Poglavlje 2

Konkurentno programiranje u Javi

Java je programski jezik koji sadrži koncepte potrebne za pisanje konkurentnih programa. Izvršavanje ovakvih programa zavisi, naravno, od korišćene Java virtuelne mašine, operativnog sistema, i hardverske platforme, ali su te zavisnosti sklonjene od Java programera. Drugim rečima, isti konkurentni program se može izvršavati na različitim računarskim platformama bez modifikacija. Pisanje konkurentnih programa obično zahteva korišćenje odgovarajućih funkcija operativnog sistema, što takve programe čini teško prenosivim na različite operativne sisteme.

Svaki Java program je, zapravo, konkurentan program: garbage collector, o kome je bilo reči u prethodnom poglavlju, se izvršava kao posebna nit programa. Na izvršavanje garbage collector-a nemamo velikog uticaja, a u pisanju konkurentnih programa možemo 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 suštini, roditeljska klasa koju je potrebno naslediti prilikom definisanja nove programske niti.

Sam programski kod koji definiše rad niti je smešten 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 naglašeni elementi programskog koda na koje se želi skrenuti posebna pažnja). 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 neće izazvati potreban efekat. Metoda start (koja se obično ne redefiniše) će obaviti potrebnu inicijalizaciju i pozvati metodu run. Dakle, pokretanje nove niti klase MojThread može da se obavi sledećim segmentom koda:

Page 41: 68261077 Java i Internet Programiranje1

36

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

Od ove tačke nadalje, naš program se sastoji iz dve niti: osnovne niti programa koja počinje svoje izvršavanje od metode main, i novostvorene niti koja je opisana u metodi run klase MojThread. Slika 2.1 ilustruje ovu situaciju.

osnovna nit

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

mt

Slika 2.1 Pokretanje nove niti

Drugi način za kreiranje programske niti je implementacija interfejsa Runnable. Ovaj postupak se obično koristi kada nije moguće naslediti klasu Thread (zato što nova klasa već nasleđuje neku klasu, a višestruko nasleđivanje nije dozvoljeno). Implementiranje interfejsa Runnable se svodi na implementiranje metode run koja ima istu funkciju kao i u prethodnom slučaju. Na primer, klasa MojaNit u ovom slučaju 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 slučaja. 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 slučaja, 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 proglašava za daemon-nit tako što se pozove metoda setDaemon(true), a non-daemon-nit tako što se pozove setDaemon(false).

Suštinska razlika između ove dve vrste niti je u tome što će se Java program završiti kada se okončaju sve njegove non-daemon niti. Inicijalno program startuje sa jednom non-daemon niti koja počinje svoje izvršavanje metodom main. Daemon-niti su namenjene za obavljanje zadataka u pozadini, čije kompletno izvršavanje nije od značaja za rad programa.

Page 42: 68261077 Java i Internet Programiranje1

37

2.3 Primer programa sa više niti Posmatrajmo sledeći 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 sadrži metodu main odakle počinje izvršavanje 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 završeno 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 brojačke promenljive counter. U okviru metode run izvršava se petlja od 10000 iteracija (pomoću brojača counter) i u svakom hiljaditom prolazu ispisuje se poruka na konzolu. Nakon izlaska iz petlje nit se završava.

Page 43: 68261077 Java i Internet Programiranje1

38

Posmatrajmo početak jedne moguće varijante izvršavanja ovog programa prikazane na slici 2.2.

Slika 2.2. Izvršavanje programa ThreadTest

Na slici vidimo da je prva ispisana poruka zapravo poruka koju ispisuje metoda main kada završava sa radom. To znači da je u ovom slučaju, prilikom pokretanja programa, osnovna nit programa stigla da izvrši celokupan svoj programski kod pre nego što su druge niti dobile priliku da zauzmu procesor. Samim tim, ovo je ilustracija slučaja gde završavanje osnovne niti programa ne predstavlja i završavanje celog programa: postoji još deset non-daemon niti koje nisu završile svoj rad. Ova situacija bi se grafički 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

xx

x

Slika 2.3. Grafička predstava izvršavanja programa ThreadTest

Page 44: 68261077 Java i Internet Programiranje1

39

2.4 Sinhronizacija niti Prethodni primer predstavlja program u kome izvršavanje jedne niti ne utiče na izvršavanje ostalih niti (osim što ta nit konkuriše za zauzeće procesora). Konkurentni programi ovakve vrste su relativno retki. Kada je potrebno da dve niti komuniciraju, komunikacija se mora obaviti putem zajedničkog (deljenog) resursa. U Javi je u pitanju zajednički objekat kojem obe niti mogu da pristupe.

Kako niti dobijaju deo procesorskog vremena na osnovu odluke Java virtuelne mašine i operativnog sistema, ne možemo biti sigurni da jedna nit u toku pristupa deljenom objektu neće biti prekinuta i kontrola biti predata drugoj niti koja isto tako može početi da pristupa deljenom objektu i izazvati greške prilikom nastavka izvršavanja prve niti (koja u objektu zatiče drugačije stanje u odnosu na trenutak kada je bila prekinuta).

Zbog toga je neophodno koristiti mehanizam zaključavanja objekata koji obezbeđuje da najviše jedna nit može da pristupa deljenom objektu u nekom periodu vremena. Ovaj mehanizam je u Javi implementiran pomoću tzv. synchronized blokova. Synchronized blok izgleda kao u sledećem primeru:

synchronized (obj) { // obj je deljeni objekat; // imamo ekskluzivno pravo pristupa njemu // unutar ovog bloka }

Početak synchronized bloka predstavlja zaključavanje objekta od strane niti. Kraj bloka predstavlja oslobađanje objekta. Kada hronološki prva nit pokuša da uđe u synchronized blok, dobiće pravo pristupa i zaključaće objekat (acquire lock). Sve dok ta nit ne oslobodi objekat (release lock), druge niti neće moći da mu pristupe. Ako neka druga nit pokuša da uđe u svoj synchronized blok, biće blokirana u toj tački sve dok prva nit ne oslobodi objekat. Tada će druga nit dobiti pravo pristupa, zaključati objekat i ući u svoj synchronized blok. Slika 2.4 ilustruje ovu situaciju.

obj

nit A nit B

1) lock

sync {

}

4) unlock

2) locked?

3) yes5) lock

6) unlock

wait

sync {

}

Slika 2.4. Pristup deljenom objektu iz dve niti

Drugi način za implementaciju mehanizma zaključavanja objekata su tzv. synchronized metode. Synchronized metoda se definiše kao u sledećem primeru:

public synchronized void metoda() { ... }

Page 45: 68261077 Java i Internet Programiranje1

40

Poziv ovakve metode se ponaša kao ulazak u synchronized blok: za vreme izvršavanja metode samo nit koja je pozvala metodu ima prava pristupa objektu.

2.5 Dodatne metode za sinhronizaciju Nekad je potrebno da nit sačeka na neki događaj, iako se nalazi unutar synchronized bloka. To čekanje može da traje proizvoljno dugo, pa bi u tom slučaju pristup zaključanom objektu bio nemoguć u proizvoljno dugačkom intervalu vremena.

Metoda wait (nasleđena iz klase Object, tako da je dostupna u svim klasama) radi sledeće: oslobađa zauzeti objekat i blokira izvršavanje niti sve dok neka druga nit ne pozove metodu notify nad istim objektom.

Metoda notify (takođe nasleđena iz klase Object) obaveštava nit koja je (hronološki) prva pozvala wait da može da nastavi sa radom. Nit koja je čekala u wait metodi neće odmah nastaviti izvršavanje, nego tek nakon što nit koja je pozvala notify ne izađe iz svog synchronized bloka (slika 2.5).

Metoda notifyAll obaveštava 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.

obj

nit A nit B

sync {

}

sync {

}

wait();

notify();

waitingfor

notify

notify

waitingfor

lock

Slika 2.5. Mehanizam wait/notify

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

2.6 Primer programa sa sinhronizacijom niti Posmatrajmo dve niti, jednu koja “proizvodi” podatke i drugu koja ih “troši”. One komuniciraju preko deljenog objekta koji je zapravo bafer za podatke. Potrebno je omogućiti da se punjenje i pražnjenje bafera odvijaju u paralelnim nitima (slika 2.6).

Bafer predstavlja klasičnu implementaciju kružnog bafera. Nit “proizvođač” će puniti bafer (osim ako već nije pun, tada mora da čeka), a nit “potrošač” će

Page 46: 68261077 Java i Internet Programiranje1

41

prazniti bafer (osim ako nije prazan, tada mora da čeka). U našem primeru nit “proizvođač” je implementirana klasom Producer, a nit “potrošač” 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 vrši 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 pročitaju, odnosno upišu u bafer.

Klasa Buffer sadrži sve što je potrebno za sinhronizaciju niti. Sledi programski kod klase Buffer. /** Implementacija kružnog bafera */ public class Buffer { /** Konstruktor * @param size Veličina kružnog bafera */ public Buffer(int size) { this.size = size; data = new int[size]; readPos = 0; writePos = 0;

Page 47: 68261077 Java i Internet Programiranje1

42

} /** 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 Pročitana 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 Vraća <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; } /** Veličina kružnog bafera */ private int size; /** Sadržaj kružnog bafera */ private int[] data; /** Naredna lokacija za čitanje */ private int readPos; /** Naredna lokacija za pisanje */

Page 48: 68261077 Java i Internet Programiranje1

43

private int writePos; }

Konstruktor klase Buffer prima kao parametar veličinu 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 služi 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.

writePos readPos writePos readPos

writePos readPos writePosreadPos

writePosreadPos

a)

b)

c)

Slika 2.6. a) Slučajevi kada je bafer prazan; b) slučajevi kada je bafer pun;

c) slučajevi 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 obezbeđuje 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 vraća se kao rezultat metode. Ako je bafer prazan, poziva se wait metoda, čime se izvršavanje ove niti suspenduje sve dok “proizvođač” nit ne upiše novi podatak u bafer; tada će se, u okviru metode write, pozvati metoda notify, čime će se “potrošač” nit ponovo aktivirati.

Metoda write je namenjena za pisanje podataka u bafer. Takođe je definisana kao synchronized. Njeno funkcionisanje je simetrično 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 “potrošač” nit ne pročita podatak iz bafera, čime će se “proizvođač” nit ponovo aktivirati.

Klasa Test je namenjena za pokretanje programa. Sadrži samo metodu main u okviru koje se kreira bafer, “proizvođač” nit, “potrošač” nit i niti se pokrenu.

public class Test { public static final int BUFFER_SIZE = 100;

Page 49: 68261077 Java i Internet Programiranje1

44

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.

Objašnjenje: Posmatra se okrugli sto za kojim sedi pet filozofa. Između njihovih tanjira nalazi se pet štapića za jelo. Kako su za obedovanje potrebna dva štapića, nije moguće obezbediti da svih pet filozofa obeduje istovremeno. Svaki od filozofa će zauzeti jedan od njemu potrebnih štapića čim ovaj bude slobodan. Ovaj problem može da ilustruje nastanak deadlock-a. Slika 2.7 prikazuje ovaj problem.

Slika 2.7. Ilustracija problema pet filozofa

Page 50: 68261077 Java i Internet Programiranje1

45

Poglavlje 3

GUI aplikacije i JavaBeans

3.1 AWT i Swing Programski jezik Java je, u svojoj inicijalnoj verziji, posedovao biblioteku komponenti za izgradnju grafičkog korisničkog interfejsa (GUI) zvanu Abstract Window Toolkit (AWT). U pitanju je biblioteka koja se zasniva na korišćenju komponenti korisničkog interfejsa koje su dostupne na platformi na kojoj se program pokreće (Windows, Motif, Macintosh, itd). To znači da je implementacija AWT komponenti različita za svaki operativni sistem. Java klase koje pred-stavljaju AWT komponente koriste su u velikoj meri native programski kod koji je vršio interakciju sa operativnim sistemom. Na primer, AWT klase u Windows distribuciji Java virtuelne mašine 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 izvršavati. To dalje znači da je ovakav skup komponenti vrlo siromašan. 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 siromašnog skupa komponenti od kojih mogu biti sačinjene.

U vreme kada je bila aktuelna Java verzija 1.1, počet je razvoj na novoj biblioteci GUI koponenti koja je imala drugačiji koncept: kompletna biblioteka je napisana u Javi, što znači 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 ograničenja na broj i tip GUI komponenti koje će ući u biblioteku. Naziv biblioteke u toku njenog razvoja bio je Swing, i to ime se zadržalo i kasnije.

Biblioteka je zamišljena 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 odgovarajuće Windows komponente), Motif (GUI okruženje na UNIX-u) i Metal (izgled svojstven samo Java aplikacijama, i, moguće, nekom budućem Java opera-

Page 51: 68261077 Java i Internet Programiranje1

46

tivnom sistemu). Kasnije su se pojavili look-and-feel dodaci sa Macintosh izgle-dom, itd. Promena izgleda aplikacije može da se obavi čak i za vreme izvršavanja programa.

Iako se Swing biblioteka može koristiti i sa Java verzijom 1.1 (uz dodavanje biblioteke u CLASSPATH), sve mogućnosti biblioteke su dostupne tek od verzije 1.2. Od verzije 1.2 ova biblioteka je proglašena za standard za razvoj korisničkog interfejsa u Java aplikacijama, dok je AWT zadržan zbog kompatibilnosti sa starijim programima. Swing je postao sastavni deo veće biblioteke nazvane Java Foundation Classes (JFC).

U ovom praktikumu biće reči isključivo o Swing komponentama.

3.2 Event-driven model Kaže se da je kod GUI aplikacija korisnički interfejs upravljan događajima (event-driven). To znači da se program sastoji od relativno nezavisnih segmenata koji su namenjeni za obradu odgovarajućeg događaja (koga je najčešće izazvao korisnik). Na primer, klik mišem, kucanje na tastaturi, itd. su događaji korisničkog interfejsa na koje program reaguje u okviru svojih obrađivača događaja – posebnih funkcija pisanih za tu namenu.

Event-driven model ima za posledicu da se program sastoji iz određenog inicijalizacionog bloka i raznih obrađivača događaja. Sam program se ne izvršava linearno – “od gore prema dole” – nego se izvršava samo u određenim vremenskim intervalima. To su momenti pokretanja aplikacije (kada se vrši inicijalizacija programa) i reakcije na događaje (kada se vrši obrada događaja). Sve ostalo vreme je vreme koje koristi operativni sistem ili druge aplikacije koje rade po istom principu.

Inicijalizacioni blok se u Java GUI programima izvršava počevši od metode main, dakle na način koji smo i do sada koristili. Događaji se opisuju tzv. xxxEvent klasama. Za svaku vrstu događaja definisana je posebna klasa. Na primer, pritisku na taster odgovara klasa KeyEvent, pomeranju miša odgovara klasa MouseEvent, itd. Sve xxxEvent klase nasleđuju klasu Event, slično kao kod raznih exception klasa o kojima je bilo reči u prvom poglavlju.

Kada se dogodi neki događaj, kreira se objekat odgovarajuće klase koji opisuje taj događaj, i zatim se taj objekat prosleđuje onima koji su se registrovali da “osluškuju” taj događaj. “Osluškivači” događaja su instance neke od xxxListener klasa. Na primer, osluškivač za KeyEvent događaj je KeyListener, itd.

Mehanizam osluškivača je uveden u Java verziji 1.1. Pre toga korišćen je drugačiji mehanizam za obradu događaja čija se upotreba danas ne prepo-ručuje.

3.3 Osnovna struktura GUI aplikacije Svaka Java aplikacija počinje svoje izvršavanje metodom main. Tako i GUI aplikacija počinje svoje izvršavanje ovom metodom, ali se najčešće tom prilikom odmah inicijalizuje i glavni prozor aplikacije koji se potom prikaže na ekranu.

Page 52: 68261077 Java i Internet Programiranje1

47

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 sadrži main metodu. U okviru main metode kreira se objekat klase MainFrame (što predstavlja inicija-lizaciju glavnog prozora aplikacije) i zatim se taj prozor prikaže na ekranu (poziv metode setVisible). Klasa MainFrame nasleđuje klasu JFrame, što je standardan način za definisanje novih prozora. Klasa JFrame je deo Swing biblioteke smeštene u paket javax.swing. Komponente Swing korisničkog interfejsa po pravilu počinju velikim slovom J. U okviru konstruktora klase MainFrame se postavlja veličina 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 korisni-čkog interfejsa. Resurs je, zapravo, deklarativni opis izgleda nekog elementa korisničkog interfejsa. Takav opis se može formirati u tekstualnoj datoteci odgovarajućeg formata, ali se može i nacrtati primenom odgovarajućeg alata. Tzv. “vizuelna” okruženja za razvoj softvera su i zamišljena tako da omoguće programeru da nacrta izgled svoje aplikacije. Crtanje aplikacije u Windows okru-ženju predstavlja definisanje resursa. Tim resursima (dugmadima, tekstualnim poljima, itd.) se kasnije mogu pridružiti 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 siste-mima) izgled korisničkog interfejsa se ne definiše deklarativno, pomoću resursa, nego programski: komponente interfejsa se nanose na prozor u određenom segmentu programa (tipično u konstruktoru prozorske klase) pozivima odgovarajućih metoda.

3.5 Dodavanje komponenti na prozor Pogledajmo sledeći primer:

import java.awt.*;

Page 53: 68261077 Java i Internet Programiranje1

48

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 najčešće 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 korisničkog interfejsa najčešće se definišu kao atributi prozorske klase, u ovom slučaju 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 možemo 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 određivanje ovih karakteristika komponente zadužen 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 raspoređuje komponente u okviru kontejnera u zone koje izgledaju kao na slici 3.2.

NORTH

SOUTH

WE

ST

EASTCENTER

Page 54: 68261077 Java i Internet Programiranje1

49

Slika 3.2. Zone BorderLayout manager-a

Različiti layout manager-i će iste komponente raspoređivati na različit način, zavisno od sopstvenog algoritma raspoređivanja. Sledeći 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 korišćenjem FlowLayout manager-a dobiće se prozor čije su varijante prikazane na slici 3.3.

a)

b)

Slika 3.3. Primer korišćenja FlowLayout manager-a

Sa slike 3.3 se vidi da FlowLayout raspoređuje komponente sleva na desno, dajući im neku podrazumevanu veličinu. Kada se prvi red sa komponentama popuni (što zavisi od širine prozora i broja i oblika komponenti) prelazi se u sledeći red i tako dalje.

Layout manager mehanizam se koristi kako u Swing, tako i u AWT biblioteci. Odgovarajuće 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. Korišćenjem ove komponente mogu se dobiti složeni rasporedi komponenti na formi.

Zanimljivo je, međutim, da osnovni skup layout manager-a ne obuhvata nijedan koji omogućava postavljanje komponenti na prozor na način kako su to navikli Windows programeri – postavljanjem komponente na tačno određeno mesto na

Page 55: 68261077 Java i Internet Programiranje1

50

prozoru, određeno 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 java.awt.*; import javax.swing.*; import com.borland.jbcl.layout.*; public 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 veličine prozora.

a)

b)

Slika 3.4. Primer korišćenja Borland-ovog XYLayout manager-a

3.7 Rukovanje događajima

3.7.1 Događaji, osluškivači i komponente U odeljku 3.2 već je bilo reči o rukovanju događajima. Da podsetimo, događaji su predstavljeni objektima xxxEvent klasa, a osluškivači događaja su odgova-rajući xxxListener objekti. xxxListener-i su, zapravo, interfejsi: osluškivač mora biti instanca neke klase koja implementira taj interfejs. Na primer, klasa koja implementira ActionListener interfejs može da posluži kao obrađivač događaja za klik mišem 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);

Page 56: 68261077 Java i Internet Programiranje1

51

} }

Interfejs ActionListener definiše jednu metodu, actionPerformed. Njen parametar je objekat klase ActionEvent koji bliže opisuje događaj. U prikazanom primeru osluškivač će, kada se događaj dogodi, zatvoriti aplikaciju (metoda exit). Listener mehanizam se koristi kako u Swing, tako i u AWT biblioteci tako da su svi događaji definisani u starijim paketima java.awt i java.awt.event.

Osluškivač događaja (instanca neke Listener klase) se pridružuje onoj kompo-nenti korisničkog interfejsa za koju želimo da reaguje na taj događaj. To pridruživanje se obavlja metodom addxxxListener koju ima komponenta. Sledi primer MainFrame klase iz prethodnih primera koja je modifikovana tako što je dugmadima dodat prethodno prikazani osluškivač. 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 sadrži dva dugmeta koja će, na klik mišem, reagovati na isti način – koriste isti Listener – zatvaranjem aplikacije. (Ovo morate probati prakti-čno, slika ne pomaže puno).

3.7.2 Osluškivači kao unutrašnje klase Prozori često znaju biti pretrpani komponentama koje, sa svoje strane, obrađuju više vrsta događaja. Rezultat može biti jedna prozorska klasa koja sadrži par desetina komponenti, i nekoliko desetina Listener klasa. Definisati pedesetak Listener klasa samo za jedan prozor može učiniti program nepreglednim. Zato se Listener klase najčešće definišu kao unutrašnje klase u okviru prozorske klase. Posmatrajmo sledeći 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 možemo

Page 57: 68261077 Java i Internet Programiranje1

52

konstruisati još jedan objekat ove klase. Sve što nam je iz ove komplikovane konstrukcije potrebno je referenca na objekat, koju možemo iskoristiti na sledeći način: bCancel.addActionListener(a);

Time smo definisali Listener klasu i njenu instancu (osluškivač) pridružili dugmetu bCancel. Ove dve operacije (kreiranje osluškivača i njegovo pridruživanje dugmetu) se mogu obaviti jednim iskazom: bCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } });

Upravo je ovaj poslednji način i najčešće korišćeni način za definisanje osluškivača (ovakve iskaze koriste i alati poput Borland JBuilder-a kada generišu kod). Njegova komplikovana sintaksa traži malo navikavanja, ali je kudikamo preglednija od desetina odvojenih datoteka u kojima se nalaze definicije osluškivača onako kako je to u prvom primeru prikazano. Sledeći primer predstavlja prozor sa dva dugmeta, od kojih dugme Cancel zatvara aplikaciju, a klik na dugme OK menja boju samog dugmeta slučajnim 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"); }

Page 58: 68261077 Java i Internet Programiranje1

53

3.8 Primeri korišćenja standardnih komponenti U prethodnim primerima korišćena je komponenta dugme i događaj klik na dugme (predstavljeni klasama JButton i ActionEvent). Biblioteka sadrži veliki broj komponenti za izgradnju korisničkog interfejsa i veliki broj događaja na koje se može reagovati. Neke od najčešće korišćenih komponenti su pobrojane u tabeli 3.1.

Klasa Opis ButtonGroup povezuje više radio button-a da rade zajedno; nije vidljiva komponenta JButton dugme JCheckBox check box JComboBox combo box JDialog dijalog (prozor kome se ne može menjati veličina) JFrame prozor JLabel labela JList list box JMenu meni JMenuBar linija menija JMenuItem stavka menija JOptionPane prozor koji ispisuje kraću poruku (message box) JPanel komponenta koja je kontejner za druge komponente JRadioButton radio button JTabbedPane kartice (tabs); pojedine kartice se na ovu komponentu dodaju kao JPanel-i JTextArea višelinijsko polje za unos teksta (memo) JTextField jednolinijsko polje za unos teksta

Tabela 3.1. Najčešće korišćene GUI komponente

Na isti način na koji je dugme u prethodnim primerima reagovalo na klik mišem, može se dodati osluškivač neke druge vrste događaja nekoj komponenti. Tabela 3.2 prikazuje najčešće korišćene događaje i odgovarajuće Listener-e.

Tip događaja Odgovarajući listener ActionEvent ActionListener AdjustmentEvent AdjustmentListener ComponentEvent ComponentListener ContainerEvent ContainerListener FocusEvent FocusListener KeyEvent KeyListener MouseEvent MouseListener WindowEvent WindowListener ItemEvent ItemListener TextEvent TextListener

Tabela 3.2. Tipovi događaja i odgovarajući listener-i

U narednih nekoliko primera biće ilustrovana upotreba različitih komponenti korisničkog interfejsa i reakcije na događaje 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 osluškivača događaja, objekat klase Reakcija. Klasa Reakcija ne implementira interfejs KeyListener, što bismo očekivali, već nasleđuje klasu KeyAdapter. Radi se o tome da interfejs KeyListener definiše više metoda za obradu događaja (keyPressed,

Page 59: 68261077 Java i Internet Programiranje1

54

keyReleased, itd). Kako želimo da reagujemo samo na jednu vrstu događaja – otpušten taster, tj. keyReleased, želimo da redefinišemo samo tu metodu. Pošto implementiranje interfejsa obavezuje implementiranje svih njegovih metoda, morali bismo da napišemo i nekoliko metoda sa praznim telom, da bismo zadovoljili formu. Klasa KeyAdapter nam rešava taj problem, jer ona implementira interfejs KeyAdapter na takav način da su joj sve metode “prazne”. Tako ćemo nasleđivanjem klase KeyAdapter i redefinisanjem samo onih metoda koje su nam potrebne uštedeti nešto kodiranja i doprineti preglednosti programskog koda.

Prilikom otpuštanja pritisnutog tastera dok je fokus na tf komponenti, poziva se osluškivač klase Reakcija. Ukoliko je pritisnut taster A, tekst labele l se menja u “Pritisnuo taster a”, a inače 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"); }

Sledeći primer predstavlja mogućnost obrade događaja pomeranja kursora unutar tekstualnog polja. Kursor se može pomeriti kucanjem teksta, brisanjem teksta, strelicama za kretanje po tekstu, itd. Prilikom pomeranja kursora (CaretEvent) u gornjem JTextArea polju, novi položaj kursora se upisuje kao sadržaj donjeg JTextArea polja. Za osluškivanje ovakvog događaja potrebno je implementirati CaretListener interfejs. Metoda koja se poziva prilikom pome-ranja kursora je caretUpdate. CaretEvent klasa koja predstavlja događaj ima metodu getDot koja vraća novi položaj kursora. import java.awt.*; import javax.swing.event.*; import javax.swing.*;

Page 60: 68261077 Java i Internet Programiranje1

55

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 događaj izbora stavke (ItemEvent) u check box polju (JCheckBox) i radio button polju (JRadioButton). Interfejs koji treba implementirati je ItemListener, metoda koja se poziva prilikom događaja je itemStateChanged, a metoda getItem klase ItemEvent vraća 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 dese-lekciju drugog, potrebno je staviti ih u istu grupu, tj. ButtonGroup objekat. Metoda add klase ButtonGroup služi 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);

Page 61: 68261077 Java i Internet Programiranje1

56

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 događaj izbora stavke (ItemEvent) u combo box-u (JComboBox). Primer je sličan prethodnom, sa malom razlikom što se u metodi itemStateChanged koristi metoda getSource klase ItemEvent koja vraća objekat koji je izvor događaja. 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 može 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");

Page 62: 68261077 Java i Internet Programiranje1

57

}

Ispitivanje da li je izvor događaja instanca klase JComboBox u prethodnom primeru je ilustracija kako se jedan osluškivač događaja može upotrebiti za obradu događaja koji potiču od različitih komponenti korisničkog interfejsa.

3.9 Apleti

3.9.1 Pojam apleta Apleti su posebna vrsta Java programa koji su namenjeni za ugrađivanje u HTML stranice. U okviru stranice aplet dobija na raspolaganje određenu pravougaonu površinu čije su dimenzije date u pikselima. U tom smislu, aplet se u HTML stranicu ugrađuje na sličan način kao i slika.

Kada Web čitač pristupi stranici koja sadrži aplet, automatski će preuzeti i programski kod apleta (prevedene Java klase), pokrenuti Java virtuelnu mašinu i početi izvršavanje apleta.

Aplet je Java program koji na raspolaganju ima gotovo sve mogućnosti klasičnih Java aplikacija, izuzev dva bitna ograničenja, uvedena iz bezbedno-snih razloga:

• apleti ne mogu da pristupe fajl-sistemu računara na kome se izvršavaju; • apleti ne mogu da uspostave mrežnu konekciju sa bilo kojim računarom

osim sa Web serverom sa koga su preuzeti.

Aplet je zapravo klasa koja nasleđuje klasu Applet (za AWT) ili JApplet (za Swing). Pisanje apleta svodi se na nasleđivanje klase JApplet i implementiranje odgovarajućih metoda. Neke od najvažnijih metoda pobrojane su ovde:

• init: poziva je Web čitač prilikom učitavanja apleta u JVM Web čitača • destroy: poziva je Web čitač prilikom uklanjanja apleta iz JVM Web čitača;

obično se koristi za oslobađanje zauzetih resursa (npr. zaustavljanje niti ili zatvaranje mrežne konekcije)

• start: poziva je Web čitač kada hoće da naznači da aplet treba da počne sa svojim izvršavanjem

• stop: analogno prethodnom, poziva je Web čitač kada aplet treba da prekine sa svojim izvršavanjem

• paint: poziva je Web čitač kada je potrebno da aplet iscrta svoj sadržaj

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

Page 63: 68261077 Java i Internet Programiranje1

58

Aplet klasa se zove AppletTest, nasleđuje klasu JApplet i redefiniše metodu init. U okviru init metode vrši se inicijalizacija apleta: u ovom slučaju to se svodi na postavljanje jedne labele na površinu apleta.

Aplet nema metodu main, tako da ga ne možemo pokrenuti na do sada poznat način – 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>

Ugrađivanje apleta u stranicu se postiže 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 čitaču, izgleda kao na slici 3.5.

Slika 3.5. Aplet prikazan u okviru HTML stranice

3.9.2 Web čitači i Java Plug-In Kada Web čitač prilikom analize HTML stranice koju je preuzeo naiđe na applet tag, vrši inicijalizaciju svoje Java virtuelne mašine, preuzima aplet i pokreće ga. To znači da se aplet izvršava u okviru JVM koja pripada Web čitaču. Današnji Web čitači većinom imaju svoje JVM, ali je njihova kompatibilnost sa standard-nom JVM problematična. Naime, Internet Explorer (u verzijama do 5.5) i Netscape Navigator (u verzijama do 4.7) poseduju virtuelne mašine koje odgovaraju Java verziji 1.1, i čak nemaju sve mogućnosti koje verzija 1.1 definiše. Najuočljiviji nedostatak je izostanak podrške za Swing biblioteku.

Da bi apleti koji koriste Swing bili upotrebljivi u okviru ovih Web čitača, Sun je izdao Java 1.2 Plug-In za te čitače. Radi se o klasičnim plug-in dodacima za Netscape Navigator i Internet Explorer, slično kao što se koristi Macromedia Flash plug-in. Namena tog plug-ina je da zameni osnovnu virtuelnu mašinu Web čitača svojom, koja je u potpunosti kompatibilna sa Java verzijom 1.2.

Page 64: 68261077 Java i Internet Programiranje1

59

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č, analizirajući HTML stranicu, naiđe na applet tag, on će ponovo pokrenuti svoju JVM umesto nove koju je instalirao plug-in. Ugrađivanje plug-ina u stranicu se razlikuje od ugrađivanja apleta, po tome što se koristi object tag (za Internet Explorer), odnosno embed tag (za Netscape Navigator). Sledeći primer prikazuje HTML stranicu koja sadrži aplet iz prethodnog primera, samo što se umesto podrazumevane JVM Web čitača 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 prilično komplikovana HTML konstrukcija, ona se ponaša kao applet tag. Crvenom bojom su naznačeni elementi koji su promenljivi – to su naziv aplet klase, širina i visina apleta. Sve ostale parametre ne treba menjati. Ostali parametri, između ostalog, specificiraju Web čitaču kako automatski instalirati plug-in ako on nije već instaliran, i navode adesu na JavaSoft Web sajtu gde se plug-in može naći.

Dokle god savremene verzije čitača ne budu posedovale svoje JVM u verziji 1.2, primorani smo da koristimo Java 1.2 plug-in na ovakav način. Svi naredni primeri apleta koriste ovaj plug-in u svojim HTML stranicama.

3.9.3 Apleti i komponente korisničkog interfejsa Naredni primer prikazuje aplet koji na svojoj površini sadrži jedno dugme. import java.awt.*;

Page 65: 68261077 Java i Internet Programiranje1

60

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 površinu apleta ni po čemu ne razlikuje od postavljanja komponenti na prozor klasične aplikacije. Za raspoređivanje komponenti koristi se mehanizam layout manager-a. Sledeći primer predstavlja proširenje prethodnog primera, u smislu da je dugmetu dodat osluškivač događaja za klik mišem (ActionEvent). Aplet sadrži 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 događajima identično kao u aplikacijama.

3.10 Aplet i aplikacija istovremeno Iz prethodnih primera smo videli da je raspoređivanje komponenti na aplet i rukovanje događajima identično kao i kod klasičnih grafičkih aplikacija. Zato i nema puno tehničkih razlika u razvoju aplikacije i apleta. Sada sledi primer kako napisati program koji može biti i aplet i aplikacija, zavisno od toga na koji način se pokrene. Pošto je u pitanju aplet, osnovna klasa mora naslediti klasu JApplet. A kako je u pitanju i aplikacija, dodaćemo ovoj klasi metodu main. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class AppletApplicationTest extends JApplet {

Page 66: 68261077 Java i Internet Programiranje1

61

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 osluškivač događaja za zatvaranje prozora klikom na dugme close ( ). Dalje, poziva se metoda init apleta, a zatim i metoda start. Na ovaj način aplikacija simulira Web čitač prilikom pokretanja apleta. Aplet se postavlja na sredinu prozora, postave se dimenzije prozora i on se prikaže.

Suština se ovde nalazi u inicijalizaciji apleta (metode init i start) i njegovom postavljanju na prozor aplikacije. Na taj način aplet će biti prikazan u osnovnom prozoru aplikacije i imaćemo utisak da je u pitanju klasična GUI aplikacija.

Klikom na dugme close zatvara se aplikacija. Pri tome, potrebno je ponovo simulirati Web čitač: pozivaju se metode stop i destroy našeg apleta.

Dakle, možemo da zaključimo da je omogućavanje apletu da se koristi i kao aplikacija vrlo jednostavno: svodi se na dodavanje metode main i jednog osluškivača događaja. Sadržaj metode main i obrada događaja se praktično i ne menjaju, tako da se ovaj segment koda može kopirati u sve slične programe.

3.11 Korisnički definisane komponente Swing biblioteka obezbeđuje relativno bogat izbor komponenti za izgradnju korisničkog interfejsa, ali to ponekad ne mora biti dovoljno. Pisanje novih komponenti koje će imati neke specifične mogućnosti je relativno jednostavno. Potrebno je naslediti neku od postojećih komponenti i redefinisati potrebne

Page 67: 68261077 Java i Internet Programiranje1

62

metode. Ako nijedna konkretna komponenta nije adekvatna za ovakvo nasleđivanje, može se koristiti generiška komponenta JComponent. Sledi primer jedne korisnički 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); } /** Vraća poželjnu veličinu 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 nasleđena 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 okruženje. Samo crtanje se obavlja koristeći objekat klase Graphics, koji ima veliki broj metoda za crtanje. U okviru ove metode postavlja se crna boja kao tekuća boja za crtanje, iscrtava se pravougaonik oko ivica komponente, i unutar njega se ispisuje dati tekst.

Metoda getPreferredSize vraća dimenzije “koje bi komponenta volela da ima”. Ovu metodu će pozivati layout manager prilikom raspoređivanja komponenti na prozor. Izračunavanje ove “poželjne veličine” komponente se vrši na osnovu dimenzija koje će zauzimati tekst kada se ispiše.

Ovako definisana komponenta koristi se u aplikacijama i apletima na isti način kao i komponente iz biblioteke. Sledi primer apleta koji koristi prethodno

Page 68: 68261077 Java i Internet Programiranje1

63

definisanu komponentu. Komponente se konstruišu i dodaju na površinu apleta na uobičajen način. 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 korisnički definisanim komponentama

3.12 JavaBeans JavaBeans je standard za kreiranje softverskih komponenti koje imaju svoje osobine i ponašanje 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 događaje (events). Formalno posmatrano, JavaBean je svaka Java klasa za koju važi:

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 pažnju na odnos velikih i malih slova!). Atribut klase koji bi sadržao vrednost tog svojstva nije obavezan!

3. Za svaki događaj predstavljen klasom xxxEvent na koji komponenta može da reaguje, moraju da postoje metode addxxxListener(XxxListener) i removeXxxListener(XxxListener).

Page 69: 68261077 Java i Internet Programiranje1

64

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 opisaćemo property-jem text. To znači da klasa mora posedovati metode setText i getText. Kako naša komponenta već nasleđuje JComponent, nasleđuje više addXxxListener/removeXxxListener metoda. Među njima su i addMouseListener i removeXxxListener koje omogućavaju reagovanje na događaje izazvane mišem. import java.awt.*; import java.awt.event.*; import javax.swing.*; public 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 događaje ove komponente i omogući nam da ih podešavamo u vreme pisanja aplikacije (design-time). Slika 3.6a prikazuje svojstva koja je JBuilder “otkrio” u našoj komponenti, a slika 3.6b prikazuje događaje na koje naša komponenta može da reaguje. Vidimo da se u spisuku property-ja nalazi i text, koji smo sami definisali. Ostali property-ji su nasleđeni iz klase JComponent.

Page 70: 68261077 Java i Internet Programiranje1

65

a)

b)

Slika 3.6. Svojstva i događaji komponente u JBuilder-u

JBuilder ne koristi nikakve posebne tehnike za “otkrivanje” svojstava i događaja 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 obezbeđuje Java platforma!

Page 71: 68261077 Java i Internet Programiranje1

66

Poglavlje 4

Mrežno programiranje u Javi

4.1 Osnovne karakteristike Pod “mrežnim programiranjem” u programskom jeziku Java podrazumeva se pisanje programa koji komuniciraju sa drugim programima preko računarske mreže. Zahvaljujući konceptu prenosivog izvršnog koda, pisanje ovakvih programa je istovetno na različitim hardversko/softverskim platformama.

Komunikacija putem računarske mreže u Java programima podrazumeva korišćenje IP mrežnog protokola. Drugi protokoli protokoli (npr. Novell IPX) nisu podržani. Standardna Java biblioteka poseduje klase za komunikaciju preko ovakve mreže korišćenjem TCP i UDP protokola. Ovde će biti reči samo o komunikaciji putem TCP protokola.

Komuniciranje između dve mašine odvija se putem tokova (streams): svaka konekcija između dva programa je dvosmerna za svakog od njih, u smislu da oba programa koji učestvuju u konekciji koriste stream za čitanje i stream za pisanje. Stream-ovi se koriste na isti način kao što se koriste prilikom rada sa datotekama u okviru fajl-sistema.

Klase standardne biblioteke namenjene za pristup mrežnim funkcijama nalaze se u paketu java.net, a familija stream klasa koja se takođe koristi nalazi se u paketu java.io.

4.1.1 Pojam socket-a Za vezu između dva programa na mreži karakterističan je pojam socket-a. Socket zapravo predstavlja uređeni par (IP adresa, port) jednog učesnika u komunikaciji. Uspostavljena veza između dva programa je zapravo skup dva socket-a. Slika 4.1 ilustruje uspostavljenu vezu između dva programa sa stanovišta socket-a.

program A program B

(147.91.177.196, 7534) (204.1.177.96, 9000) Slika 4.1. Uspostavljena veza između dva programa

Page 72: 68261077 Java i Internet Programiranje1

67

Kada se govori o vezi, govori se o vezi “između dva programa”, a ne o vezi “između dva računara”. Dva programa koji učestvuju u vezi mogu se izvršavati i na istom računaru. Sa druge strane, jedan računar može istovremeno izvršavati više programa koji pristupaju mreži. Koncept porta je upravo način da se omogući razlikovanje više programa koji su pokrenuti na istom računaru (tj. na istoj IP adresi) i istovremeno pristupaju mreži. Slika 4.2 ilustruje situaciju kada se na jednom čvoru mreže izvršavaju 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 Y (204.1.177.96)čvor X (147.91.177.196)

program A program B7534 9000

program C

čvor Z (147.91.177.195)

program D

80807826

9864

Slika 4.2. Slučaj više uspostavljenih veza između programa

4.2 Identifikacija čvorova mreže Identifikator čvora u IP mreži je IP adresa – 32-bitni broj. Ovakve adrese se često, radi lakšeg pamćenja, pišu u formatu koji se sastoji od četiri decimalno zapisana okteta razdvojena tačkom (na primer, 147.91.177.196). Java standardna biblioteka poseduje klasu InetAddress koja predstavlja IP adresu. Kreiranje objekta ove klase se najčešće obavlja pozivom statičke metode getByName. Ova metoda prima string parametar koji sadrži bilo IP adresu zapisanu u oktetima, bilo simboličku adresu (npr. java.sun.com). Sledi primer: InetAddress a = InetAddress.getByName("java.sun.com"); InetAddress b = InetAddress.getByName("147.91.177.196");

Statička metoda getLocalHost generiše InetAddress objekat koji predstavlja adresu mašine na kojoj se program izvršava: InetAddress c = InetAddress.getLocalHost();

4.3 Klasa Socket Objekti klase java.net.Socket predstavljaju uspostavljene TCP konekcije. To znači da se prilikom kreiranja objekta klase Socket vrši uspostavljanje veze. Tipično se otvaranje konekcije vrši na jedan od sledećih načina: Socket s1 = new Socket(addr, 25); // addr je InetAddress objekat

Page 73: 68261077 Java i Internet Programiranje1

68

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

Kreiranje Socket objekta, tj. otvaranje konekcije, omogućava da se preuzmu reference na stream objekte koji se koriste za slanje i primanje poruka. Jedna moguća inicijalizacija stream-ova je prikazana na sledećem 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 odgovarajući Reader/Writer objekti generišu na osnovu stream-ova koje obezbeđuje Socket objekat, metodama getInputStream i getOutputStream.

Komunikaca sa programom sa kojim je uspostavljena konekcija sada se može odvijati putem poziva odgovarajućih metoda Reader i Writer klasa. Na primer: out.writeln("Hello"); String response = in.readLine();

Prekid komunikacije treba završiti propisnim zatvaranjem konekcije. Zatvara-nje konekcije se nejčešće svodi na zatvaranje ulaznog i izlaznog stream-a i zatvaranje socket-a. Sledi primer: out.close(); in.close(); sock.close();

4.4 Tipičan tok komunikacije – klijent strana Kao rezime prethodnog odeljka, ovde se izlaže tipičn scenario komunikacije dva programa, podrazumevajući ovde klijent-stranu (tj. klijentski program). Uloga klijenta u klijent/server komunikaciji podrazumeva nekoliko stvari:

• Klijent inicira komunikaciju. • Nakon uspostavljanja veze, komunikacija se obično svodi na niz parova

zahtev/odgovor poruka. Zahteve šalje klijent, a odgovore server. • Klijent prekida komunikaciju.

Ovakva sekvenca aktivnosti može biti predstavljena sledećim segmentom programa: // inicijalizacija Socket s = new Socket(addr, port); BufferedReader in = new BufferedReader(...,s.getInputStream()); PrintWriter out = new PrintWriter(...,s.getOutputStream());

Page 74: 68261077 Java i Internet Programiranje1

69

// 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 izvršavanje programa sve dok neki klijent ne uspostavi vezu na portu na kome ServerSocket očekuje klijente.

Objekti klase ServerSocket kreiraju se na standardan način, operatorom new. Parametar konstruktora je port na kome će server očekivati klijente; kao IP adresa se podrazumeva IP adresa lokalne mašine. Sledi primer: ServerSocket ss = new ServerSocket(9000);

Ovim je konstruisan ServerSocket objekat pomoću koga će se očekivati klijenti na portu 9000. Samo “osluškivanje” na datom portu inicira se pozivom metode accept. Već je rečeno da ova metoda blokira izvršavanje 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. Tipično poziv accept metode izgleda ovako: Socket s = ss.accept();

4.6 Tipičan tok komunikacije – server strana Imajući u vidu prethodne odeljke, tipičan scenario ponašanja serverskog pro-grama je sledeći:

1. Konstrukcija ServerSocket objekta. 2. Očekivanje klijenta metodom accept. 3. Komunikacija sa klijentom:

a. Inicijalizacija stream-ova b. Komuniciranje po principu prijem zahteva/slanje odgovora. c. Završavanje komunikacije – oslobađanje resursa.

Ovakav scenario može se predstaviti sledećim 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

Page 75: 68261077 Java i Internet Programiranje1

70

String request = in.readLine(); // čitam zahtev out.println(“odgovor”); // šaljem odgovor // prekid veze in.close(); out.close(); s.close();

4.7 Server koji opslužuje više klijenata Prethodni primer je prikazao serverski program koji komunicira sa jednim klijentom – nakon što ga server sačeka, komunikacija između klijenta i servera se obavi i potom završi. Ovakvi serverski programi su vrlo retki – serveri se konstruišu tako da mogu da opslužuju više klijenata i to istovremeno.

Potreba da server komunicira sa više klijenata istovremeno se može rešiti uvođenjem posebnih programskih niti za komunikaciju sa klijentima, tako da se sa svakim klijentom komunikacija obavlja u posebnoj programskoj niti. Dakle, za n istovremenih klijenata postojaće n ovakvih programskih niti. Sa stanovišta implementacije u programskom jeziku Java, ove niti predstavljene su odgovarajućom klasom koja nasleđuje klasu Thread, kao u sledećem primeru: // obrada pojedinačnog 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 “osluškuje” serverski port i, po uspostavljanju veze, pokreće nit za komunikaciju sa klijentom, a sama se vraća 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 Imajući u vidu dosadašnje izlaganje, možemo konstruisati jednostavnu kli-jent/server aplikaciju. Zadatak klijenta u sledećem primeru je sledeći:

1. Uspostavlja vezu sa serverom. 2. Šalje zahtev serveru (tekst “HELLO”). 3. Čita odgovor servera. 4. Ispisuje odgovor na konzolu. 5. Završava komunikaciju.

Page 76: 68261077 Java i Internet Programiranje1

71

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 sledeći:

1. Čeka klijente u beskonačnoj petlji. 2. Za svakog klijenta koji je uspostavio vezu pokreće posebnu nit koja radi

sledeće: a. Čita zahtev klijenta (tekst “HELLO”).

Page 77: 68261077 Java i Internet Programiranje1

72

b. Šalje odgovor – redni broj obrađenog zahteva.

Osnovna nit servera u kojoj se očekuju 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; // slušaj 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 beskonačna while petlja u okviru koje se očekuju klijenti i pokreće 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(); }

Page 78: 68261077 Java i Internet Programiranje1

73

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 Socket sock; private int value; private BufferedReader in; private PrintWriter out; }

Komunikacija između, recimo, Web servera i klijenta (Web čitača), podseća na komunikaciju prikazanu u gornjem primeru. Zahtev Web čitača tipično sadrži naziv datoteke koju čitač traži. Odgovor servera je poruka u kojoj se nalazi tražena datoteka.

U objašnjenju prethodnog primera bilo je malo reči o formatu poruka koje se razmenjuju između klijenta i servera. Zahtev klijenta je tekst HELLO koji se završava znakom za novi red (linefeed, LF). Slanje ovakve poruke postiže se pozivom out.println("HELLO");

u okviru klijentskog programa. Sa druge strane, server očitava zahtev klijenta pomoću poziva metode readLine: String request = in.readLine();

Ova metoda će blokirati izvršavanje programa sve dok se na ulazu ne pojavi znak za novi red (LF), i tada će vratiti tekst koji je sa mreže pristigao pre tog znaka.

Korišćenje 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 može biti tako specificiran da je dužina poruke koja se očekuje unapred poznata, tako da takvu poruku možemo pročitati pozivom in.read(buffer, 0, length);

U slučaju da je dužina poruke nije unapred poznata, korišćenje karaktera LF je zgodno jer postoji metoda readLine koja blokira izvršavanje sve dok taj karakter ne pristigne sa mreže. U slučaju da odlučimo da ne koristimo karakter LF nego neki drugi, morali bismo sami da implementiramo funkcionalnost ove metode.

Page 79: 68261077 Java i Internet Programiranje1

74

4.9 Zadatak: klijent i server za listanje sadržaja direktorijuma Zadatak 6. Napisati klijent/server aplikaciju koja omogućava listanje sadržaja direktorijuma sa servera na klijentu. Na klijentov zahtev koji sadrži putanju direktorijuma na serveru koga treba izlistati, server formira spisak i vraća 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 sadržaja direktorijuma treba pogledati kako se koristi klasa java.io.File i njene metode exists, isDirectory i list. Odgovor servera bi trebalo da na odgovarajući način reaguje na situacije kada traženi direktorijum ne postoji, ili kada je u pitanju fajl, a ne direktorijum.

Page 80: 68261077 Java i Internet Programiranje1

75

Poglavlje 5

Vežba: chat aplikacija

Zadatak 7. Napisati klijent/server GUI aplikaciju koja mogućava chat za sve korisnike koji se prijave na isti server. Zadatak klijenta je da omogući unos novih i pregled pristiglih poruka (sa podacima o pošiljaocu poruke). Zadatak servera je da poruke poslate od strane jednog klijenta prosledi do ostalih klijenata (ne i pošiljaocu). Svaki korisnik u sistemu je jednoznačno određen svojim korisničkim imenom (username) koga bira prilikom prijavljivanja na sistem. Odmah nakon pokretanja klijenta korisnik mora da se prijavi na željeni server (određen svojom simboličkom ili numeričkom adresom) pod određenim korisničkim imenom. Naredne slike prikazuju jedan od mogućih izgleda ovakve aplikacije; ovakav izgled nije obavezan.

Slika 5.1. Login forma klijenta

Slika 5.2. GUI interfejs servera

Page 81: 68261077 Java i Internet Programiranje1

76

Slika 5.3. GUI interfejs klijenata

5.1 Uvodna razmatranja U ostatku ovog poglavlja prikazuje se jedno moguće rešenje zadatka.

Potrebno je izgraditi klijent/server sistem gde server mora da opslužuje više korisnika istovremeno. Na prvi pogled, sistem liči na primer iz prethodnog poglavlja. Međutim, ovaj sistem je nešto složeniji. Njegove osnovne osobine su sledeće:

• Klijent može da šalje poruke serveru, što se inicira akcijom nad korisničkim interfejsom.

• Klijent može da prima poruke od servera u proizvoljnom vremenskom trenutku, što je posledica slanja poruke od strane nekog drugog klijenta.

• Server može da prima poruke od strane više klijenata istovremeno, i to u slučajnim vremenskim trenucima.

• Server mora da prosledi poruku jednog klijenta svim ostalim klijentima.

5.2 Funkcije klijenta Posmatrajmo sada klijenta. On mora biti u mogućnosti da reaguje na pristiglu poruku tako što će je prikazati u odgovarajućoj komponenti korisničkog interfejsa. Poruka može da pristigne u bilo kom trenutku. Dakle, klijent mora neprestano osluškivati da li server šalje poruku. Kako realizovati ovakvu reakciju? Razmotrimo sledeću mogućnost: klijent ima tačno jednu konekciju sa serverom, koja se raskida po završetku rada. Postoje dve posebne niti, za čitanje poruka sa mreže i za pisanje poruka na mrežu. “Čitačka” nit čeka poruke servera, a “pisačka” nit šalje serveru one poruke koje je korisnik uneo

Page 82: 68261077 Java i Internet Programiranje1

77

posredstvom GUI interfejsa. Kako će pisačka nit znati da treba da pošalje 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 događaje 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 pošalje.

outputStreameventDispatchThread writerThreadchatDatabSend.doClick()

setMessage()

notify()

writeln()

actionListener

actionPerfomed()

Slika 5.4. Klijent inicira slanje poruke

readerThread textAreainputStream

readLine()

append()

Slika 5.5. Klijent prima poruku od servera

Slika 5.6 predstavlja dijagram klasa koje učestvuju 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. Konačno, kreiraju se ReaderThread i WriterThread objekti koji implementiraju čitačku i pisačku nit. Ovi objekti koriste odgovarajuće Reader/Writer objekte za komunikaciju. ChatData objekat je namenjen za sinhronizovanu komunikaciju sa WriterThread niti.

Slika 5.7 prikazuje dijagram klasa koje učestvuju u radu korisničkog interfejsa. LoginDlg je dijalog za prijavljivanje na server prilikom pokretanja klijenta. JTextArea je višelinijsko tekstualno polje u kome se hronološki ispisuju poruke.

Page 83: 68261077 Java i Internet Programiranje1

78

JButton je dugme za iniciranje slanja poruke. JTextField je jednolinijsko tekstualno polje u kome se unosi nova poruka.

Socket

InputStream OutputStream

BufferedReader PrintWriter

ReaderThread WriterThreadChatData

ChatClient

Slika 5.6. Klase koje učestvuju u komunikaciji sa serverom

ChatClient

LoginDlg

JTextArea

ReaderThread

JButton

JTextField

Slika 5.7. Klase koje učestvuju u radu korisničkog interfejsa

5.3 Funkcije servera Serverska aplikacija kreira posebne niti za komunikaciju sa svakim korisnikom, i to po dve niti – čitačku i pisačku. Čitačka nit je zadužena da očekuje poruke od klijenta, a pisačka da klijentu prenese poruku koju je poslao neki drugi klijent. Pisačka nit šalje poruku kada je dobije u svoj deljeni kontejnerski objekat. Situacija kada čitačka nit primi poruku i prosledi je svim čitačkim nitima preko odgovarajućih ActiveClient objekata je prikazana na slici 5.8.

Klijent otpočinje komunikaciju prijavljivanjem na server. Dakle, prvo što će server primiti od klijenta je poruka kojom se vrši prijavljivanje. Prijavljivanje može biti uspešno ili neuspešno (u slučaju da se neko već prijavio pod tim korisničkim imenom). Slika 5.9 opisuje proces uspešnog prijavljivanja.

Page 84: 68261077 Java i Internet Programiranje1

79

readerThread inputStream

readLine()

ClientUtils

sendMessageToAll()

activeClient

setMessage()

writerThread

notify()

outputStream

writeln()

Slika 5.8. Čitačka nit prima poruku i prosleđuje je svim pisačkim nitima

serverListener serverSocket

inputStream

accept()

create()

outputStreamcreate()

activeClientcreate()

readerThread

writerThread

create()

create()

Slika 5.9. Uspešno prijavljivanje klijenta i kreiranje posvećene čitačke i pisačke 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 između čitačkih i pisačkih niti i, na posletku, kreira čitačku i pisačku nit posvećenu novom

Page 85: 68261077 Java i Internet Programiranje1

80

klijentu. Klasa ClientUtils implementira operacije nad kolekcijom prijavljenih klijenata (registracija, slanje poruke svima, itd). Slika 5.10. prikazuje klase koje učestvuju u komunikaciji sa klijentima.

ChatServer

ServerListenerBufferedReaderPrintWriter

ReaderThreadWriterThread ActiveClient

ClientUtils

Slika 5.10. Klase koje učestvuju u komunikaciji

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

Page 86: 68261077 Java i Internet Programiranje1

81

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 podseća na ODBC (Open Database Connectivity) interfejs, jer im je i koncept sličan. Naime, JDBC definiše 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 najčešće se koristi TCP/IP mreža. Baze zasnovane na nekom drugačijem modelu podataka, na primer objektno-orijentisanom ili mrežnom modelu, nisu obuhvaćene ovim standardom. To ne znači da se njima ne može pristupati, već samo da se za tu namenu neće koristiti JDBC interfejs.

Za pristup konkretnoj bazi podataka putem JDBC-a potrebna je odgovarajuća biblioteka. Biblioteka, u ovom slučaju, predstavlja običnu Java biblioteku klasa tipično spakovanu u jedan JAR fajl. Takva biblioteka se naziva JDBC drajver.

Korišćenje 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 slučaju. Korišćenje JDBC-a je blisko ovom idealu: izmene je potrebno praviti samo u slučaju da su korišćene nestandardne SQL naredbe (odnosno naredbe koje nisu podržane 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 sadrži JDBC verziju 1.22, dok Java 1.2 sadrži JDBC verziju 2.0. Verzija 2.0 sadrži neka proširenja u odnosu na verziju 1.22, ali se i stariji drajveri obično mogu koristiti u okviru novijih verzija Jave.

6.2 JDBC drajveri JDBC drajveri su klasične Java biblioteke; da bi se koristile unutar nekog Java programa nije potrebna nikakva specifična instalacija tog drajvera (što je kod ODBC drajvera obavezno), nego je dovoljno tu biblioteku uključiti u sastav

Page 87: 68261077 Java i Internet Programiranje1

82

programa. Tipično se JAR fajl u kome se nalazi JDBC drajver/biblioteka smešta u CLASSPATH i time postaje dostupan svim programima na datom računaru. Nema prepreke da se više različitih drajvera smesti u CLASSPATH isto-vremeno. Takođe, nema prepreke da jedan program komunicira sa više baza podataka istovremeno.

Na primer, za pristup Oracle serveru potrebno je u program uključiti odgovarajući 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 uključiti u CLASSPATH da bi se JDBC drajver za Oracle “instalirao”. Slično drajveru za Oracle koriste se i drajveri drugih proizvođača sistema za upravljanje bazama podataka. Obično proizvođač određenog SUBP nudi i JDBC drajver za svoje sisteme. Najupadlji-viji izuzetak je Microsoft, koji nema drajver za svoj SQL Server, ali se takav drajver može 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: učitavanje drajvera i otvaranje konekcije sa bazom podataka. Pogledajmo primer ove dve operacije za Oracle SUBP: // učitavanje 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 učitavanje odgovarajuće drajver klase; proizvođač drajvera obavezno navodi naziv ove klase za svoj drajver. Šta, zapravo, znači “učitavanje klase”? Odeljak 6.9 opisuje detaljnije ovu temu i druge pojedinosti vezane za korišćenje JDBC drajvera koje nisu od velikog značaja za samo korišćenje drajvera.

Drugi red primera predstavlja otvaranje konekcije sa SUBP nakon što je drajver učitan. Prvi parametar metode getConnection je string koji sadrži podatke potrebne drajveru da bi se povezao sa SUBP. Format ovog stringa propisuje proizvođač drajvera. Tipično ovaj string sadrži adresu računara na kome se nalazi SUBP (u primeru to je branko.tmd.ns.ac.yu), TCP port na kome SUBP očekuje klijente (u primeru 1526) i naziv baze kojoj se pristupa (VTA). Druga dva parametra metode getConnection su korisničko ime i lozinka kojima se vrši prijavljivanje na SUBP. Sledi primer povezivanja na SQL Server SUBP pomoću JDBC drajvera TaveConn24C kompanije Atinav. // učitavanje SQL Server drajvera Class.forName("net.avenir.jdbc2.Driver"); // otvaranje konekcije Connection conn = DriverManager.getConnection( "jdbc:AvenirDriver://branko.tmd.ns.ac.yu:1526/VTA",

Page 88: 68261077 Java i Internet Programiranje1

83

"vta", "vta");

Rezultat uspešne uspostave veze sa SUBP je, sa stanovišta Java programa, inicijalizovani Connection objekat.

Metoda forName može da izazove izuzetak ClassNotFoundException, a metoda getConnection izuzetak SQLException. Pozivi ovih metoda moraju biti smešteni u odgovarajući try/catch blok. Sve ostale JDBC metode mogu da izazovu izuzetak SQLException.

Prilikom završetka komunikacije sa bazom podataka potrebno je zatvoriti otvorenu konekciju. To se postiže pozivom metode close klase Connection: conn.close();

6.4 Postavljanje upita Primeri u ovom poglavlju koriste šemu baze podataka prikazanu na slici 6.1. (Korišćena je notacija alata PowerDesigner, a korišćeni SUBP je Oracle; ovde nećemo 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 može da predaje više predmeta, a takođe i jedan predmet može da predaje više nastavnika.

PREDMET_ID = PREDMET_ID

NASTAVNIK_ID = NASTAVNIK_IDNASTAVNICI

NASTAVNIK_ID INTEGERIME VARCHAR2(25)PREZIME VARCHAR2(35)ZVANJE VARCHAR2(15)

PREDMETIPREDMET_ID INTEGERNAZIV VARCHAR2(150)

PREDAJEPREDMET_ID INTEGERNASTAVNIK_ID INTEGER

Slika 6.1. Šema baze podataka korišćena u primeru

Sve operacije nad bazom podataka, pa tako i postavljanje upita, definišu se odgovarajućim SQL naredbama koje se šalju serveru. SQL naredba je, u okviru JDBC interfejsa, definisana Statement objektom. Ovakav objekat se može kreirati nakon uspostavljene veze sledećim 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 sadrži 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 skladištenje rezultata upita. Rezultat upita se može pročitati pomoću 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 tekućeg reda. Tekući red se može pomerati isključivo od početka ka kraju tabele, bez preskakanja i bez više prolaza (u JDBC verziji 1.22; JDBC 2.0 omogućava prolaz kroz tabelu rezultata u oba smera i mnogo veću fleksibilnost u korišćenju ResultSet objekata). Inicijalno, nijedan red nije tekući red. Prvim

Page 89: 68261077 Java i Internet Programiranje1

84

pozivom metode next klase ResultSet tekući red će biti prvi red tabele rezultata (ukoliko tabela sadrži bar jedan red). Metoda next vraća boolean vrednost koja označava da li novi tekući red postoji ili ne. Tipično se rezultat upita čita u petlji kao u sledećoj primeru: while (rset.next()) { // ovde čitamo red po red rezultata }

Za tekući red rezultata pojedine vrednosti polja očitavaju se metodama getString, getInt, getDate, itd. (za svaki tip podatka u bazi postoji odgovarajuća metoda). Konverziju između tipova podataka baze i jezika Java obavlja JDBC drajver. Parametar getXXX metoda je redni broj kolone koja se očitava; redni brojevi počinju od 1, a ne od nula kako bi se očekivalo.

Nakon prestanka korišćenja ResultSet objekta potrebno je pozvati njegovu metodu close radi oslobađanja resursa koje je taj rezultat upita zauzimao. Slično važi 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 { // učitavanje 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)); } // oslobađanje resursa i zatvaranje veze rset.close(); stmt.close(); conn.close(); } catch (Exception ex) { ex.printStackTrace(); } } }

Page 90: 68261077 Java i Internet Programiranje1

85

Iz primera se vidi da je jedini deo programa koji zavisi od upotrebljenog SUBP blok koji obavlja inicijalizaciju: učitavanje odgovarajućeg 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 različite SUBP, jer se u celom programu menjaju samo iskazi za učitavanje drajvera i otvaranje konekcije; ostatak programa bi, u idealnom slučaju, 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 takođe se koristi Statement objekat, koji se inicijalizuje na isti način kao i u prethodnom slučaju. Razlika je ovde u slanju konkretne naredbe serveru, što se postiže pozivom metode executeUpdate. Ova metoda, za razliku od executeQuery, ne vraća ResultSet objekat, već samo jednu int vrednost koja predstavlja broj redova na koje je operacija uticala (npr. broj ažuriranih 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 slučaju treba da oslobodi resurse koje je zauzimao pozivom metode close.

6.6 Uzastopno izvršavanje istih SQL naredbi Prethodni odeljak definiše sve što je potrebno da bi se pisao program koji može da obavlja bilo koju operaciju nad bazom podataka (koja se može definisati SQL naredbama, naravno). Ovaj odeljak predstavlja situaciju kada se ista SQL naredba šalje serveru više puta uzastopno, što je prilično čest slučaj u praksi. Na primer, dodavanje više redova u tabelu nastavnici može se obaviti sledećim 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 praktično ista naredba koja se ponavlja više puta: njena struktura je ista, a razlikuju se samo podaci koji se u njoj pojavljuju. Slanje ovakvih naredbi pomoću 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 izvršavanja. Nakon toga će tu

Page 91: 68261077 Java i Internet Programiranje1

86

naredbu i izvršiti. U ovakvim slučajevima moguće je popraviti performanse programa korišćenjem klase PreparedStatement.

Ideja iza ove klase je sledeća: ako se serveru šalje više identičnih SQL naredbi (kao u prethodnom primeru), bilo bi zgodno da server samo jednom izvrši parsiranje i analizu SQL naredbe i formira plan izvršavanja. Takav plan izvršavanja može da se koristi više puta, za identične naredbe koje se razlikuju samo u podacima koje nose u sebi. To bi značilo da se za n identičnih naredbi takva naredba šalje samo jednom. Ona se tamo jednom analizira i n puta izvrši na osnovu plana izvršavanja. U slučaju da koristimo Statement objekte, server bi n puta vršio analizu i n puta izvršio naredbu.

Objekat klase PreparedStatement se konstruiše kao u sledećem 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 sledeći 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 odgovarajuća setXXX metoda za svaki tip podatka, analogno getXXX metodama klase ResultSet. Prvi parametar setXXX metoda je redni broj upitnika u SQL naredbi (počevši brojanje od 1). Drugi parametar je konkretna vrednost koju prima dati parametar. Sada je moguće izvršiti konkretnu naredbu iskazom: pstmt.executeUpdate();

Sekvenca postavljanja vrednosti parametara (setXXX) i izvršavanja naredbe (executeUpdate) se može ponoviti više puta, bez ponovne inicijalizacije PreparedStatement objekta – to je i bila ideja analize SQL naredbi unapred.

PreparedStatement je moguće 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 način.

Nakon korišćenja PreparedStatement objekta potrebno je osloboditi resurse koje je zauzimao pozivom metode close. Sada sledi primer programa koji koristi PreparedStatement za dodavanje više redova u tabelu nastavnici. import java.sql.*; public class Demo2 { public static void main(String args[]) {

Page 92: 68261077 Java i Internet Programiranje1

87

try { // učitavanje 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 uskladištenih procedura Uskladištene procedure (stored procedures) predstavljaju procedure koje se smeštaju u okviru baze podataka i dostupne su za pozivanje od strane klijenata. Najčešće se pišu u nekom od proširenja jezika SQL koje definiše proizvođač 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 uskladištene funkcije, pri čemu podela na funkcije i procedure je, zapravo, tradicionalna podela na potprograme koji vraćaju neku vrednost kao rezultat ili ne vraćaju nikakvu vrednost.

Prednost korišćenja uskladištenih procedura je u poboljšanju performansi sistema. Naime, procedura najčešće predstavlja nekakvu složenu operaciju koja se izvršava nad bazom podataka. Takvu operaciju možemo implementirati i odgovarajućim Java kodom, koristeći prethodno izložene JDBC koncepte. Međutim, takav Java kod bi se često obraćao bazi podataka i time generisao veliki mrežni saobraćaj između klijenta i servera, i potrošio bi izvesno procesorsko vreme i na klijentu i na serveru za tu komunikaciju. Sa druge

Page 93: 68261077 Java i Internet Programiranje1

88

strane, uskladištena procedura nalazi se kompletno na serveru. Tamo je smeštena u obliku koji je unapred pripremljen za efikasno izvršavanje, tako da njen poziv ne obuhvata i parsiranje, analizu naredbi, itd. Jedan poziv ovakve uskladištene procedure generiše daleko manji saobraćaj na mreži, a takođe je i izvršavanje procedure u okviru servera brže nego što bi to bio slučaj da se ona izvršava na klijentu. Slika 6.2 ilustruje razliku između pisanja Java koda koji operiše nad bazom i korišćenja uskladištenih procedura.

klijent server

SQL naredbe

procedura

klijent server poziv uskladištene procedure procedura

a)

b) Slika 6.2. a) procedura koja operiše nad bazom pisana u jeziku klijenta

b) poziv ekvivalentne uskladištene procedure na serveru

Sledi primer jedne uskladištene funkcije pisane u Oracle-ovom jeziku PL/SQL. Namena ove procedure je da poveže nastavnika datog svojim imenom i prezimenom i predmet koji on predaje datog svojim nazivom. Funkcija vraća 1 ako je operacija uspešno izvršena 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 može pozvati iz Java programa. Za tu namenu koristi se CallableStatement objekat koji se konstruiše kao u sledećem primeru:

Page 94: 68261077 Java i Internet Programiranje1

89

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

Format stringa koji predstavlja poziv procedure definisan je JDBC-om; ne definiše ga svaki proizvođač SUBP posebno. Ovakav string obavezno sadrži vitičaste zagrade. Obavezna je i ključna reč call iza koje sledi naziv procedure ili funkcije koja se poziva. Zatim sledi lista parametara datih upitnicima u običnim zagradama. Ukoliko je u pitanju poziv funkcije, pre ključne reči call treba da se nađe 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 ulazno-izlazni. Svi ulazni parametri moraju biti definisani pre samog poziva procedure. Svim izlaznim parametrima se mora definisati tip podatka, takođe pre poziva procedure. Evo i primera kako se to radi, koristeći objekat cstmt inicijalizovan u prethodnom primeru: cstmt.setString(2, "Sima"); cstmt.setString(3, "Simić"); cstmt.setString(4, "Osnovi računarstva"); cstmt.registerOutParameter(1, Types.INTEGER);

Metode setXXX se koriste na isti način kao i kod PreparedStatement objekata. Metoda registerOutParameter je namenjena za definisanje tipa izlaznih argumenata uskladištene procedure/funkcije i za definisanje tipa rezultata funkcije. U ovom primeru je rezultat funkcije označen kao vrednost celobrojnog tipa. Oznake tipova nalaze se u klasi java.sql.Types kao konstante. Treba obratiti pažnju na to da se kao redni broj parametra navodi redni broj upitnika koji se javlja u pozivu funkcije, uključujući i upitnik koji predstavlja rezultat funkcije. Sada je moguće i uputiti poziv funkcije, na sledeći način: cstmt.executeQuery();

Rezultat izvršavanja uskladištene funkcije dobija se sledećim pozivom: cstmt.getInt(1)

Sledi primer programa koji poziva uskladištenu funkciju povezi prikazanu u prethodnim primerima. import java.sql.*; public class Demo3 { public static void main(String args[]) { try { // učitavanje 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(

Page 95: 68261077 Java i Internet Programiranje1

90

"{? = 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 režimu rada: potvrda uspešnog završetka transakcije (commit) će se slati automatski nakon svake poslate SQL naredbe. Kada se konekcija ne nalazi u auto-commit režimu transakcija se mora “ručno” potvrditi ili opozvati. Potvrda transakcije se vrši pozivom metode commit nad Connection objektom: conn.commit();

Opoziv transakcije se vrši pozivom metode rollback nad Connection objektom: conn.rollback();

Promena režima rada se postiže pozivom metode setAutoCommit: conn.setAutoCommit(false);

Najčešće se konekcije ne koriste u auto-commit režimu. Tipičan blok programskog koda koji vrši izmene u bazi podataka bi izgledao ovako: try { Statement stmt = conn.createStatement(); // izvrši neke izmene... stmt.close(); conn.commit(); } catch (SQLException ex) { try { conn.rollback(); } catch (Exception e) { } }

Dakle, na kraju svake transakcije koja se tipično nalazi u try/catch bloku mora se pozvati commit metoda. Ukoliko se dogodilo nešto nepredviđeno u toku izvršavanja operacije što je rezultovalo izuzetkom, poziva se metoda rollback u okviru catch sekcije. Metoda rollback takođe može da izazove izuzetak, tako da je i ona morala biti smeštena u poseban try/catch blok.

Page 96: 68261077 Java i Internet Programiranje1

91

6.9 Dodatak: inicijalizacija drajvera Ovaj odeljak je posvećen detaljnijem objašnjenju nekih pojedinosti vezanih za inicijalizaciju JDBC drajvera i korišćenje JDBC klasa i interfejsa. Učitavanje JDBC drajvera, kako je ranije rečeno, obavlja se pozivom Class.forName("oracle.jdbc.driver.OracleDriver");

U pitanju je poziv statičke 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 sadrži metode pomoću kojih se mogu, u toku izvršavanja programa, dobiti informacije o metodama, atributima i ostalim karakteristika-ma neke konkretne klase koja se koristi u programu. Statička metoda forName vraća inicijalizovan objekat klase Class koji odgovara klasi čiji je naziv dat parametrom metode. Inicijalizacija ovakvog Class objekta obuhvata i inicijali-zaciju statičkih atributa klase koja se učitava u JVM. Inicijalizacija statičkih atributa klase može se smestiti u poseban static blok u okviru klase koji ne pripada nijednoj metodi. Sledi primer jedne klase koja sadrži takav blok: class Test { static int attr; static { // ovde se vrši inicijalizacija statičkih atributa attr = 0; } }

Vratimo se sada JDBC drajveru: klasa OracleDriver je osnovna klasa Oracle-ovog JDBC drajvera. Njeno učitavanje pozivom metode forName će izvršiti 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 vraća inicijalizovanu vezu sa bazom podataka predstavljenu Connection objektom. Za uspostavljanje veze koristiće se onaj drajver koji može da interpretira adresu servera navedenu kao parametar metode getConnection. Dakle, ako se metoda getConnection pozove sa sledećim 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 može da uspostavlja konekcije čiji opis počinje sa jdbc:oracle. Zapravo, jdbc je obavezni početak ovakvog stringa, čiji se segmenti razdvajaju dvotačkom. Sledeći segment stringa (oracle) označava drajver koji će obezbediti povezivanje sa bazom podataka. U primeru povezivanja sa SQL Server SUBP, gde je povezivanje izvršeno sa Connection conn = DriverManager.getConnection( "jdbc:AvenirDriver://branko.tmd.ns.ac.yu:1526/VTA", "vta", "vta");

Page 97: 68261077 Java i Internet Programiranje1

92

vidi se da je vrednost drugog segmenta AvenirDriver što je string kojim se registrovao odgovarajući drajver.

Rezultat getConnection metode je inicijalizovani Connection objekat. Connection je sam po sebi interfejs i ne može 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 sadrži kod koji je specifičan za komunikaciju sa Oracle serverima, itd.

Nadalje, kreiranje Statement objekta je rezultat poziva createStatement metode Connection interfejsa. U konkretnom slučaju, kada se metoda createStatement pozove nad instancom klase OracleConnection rezultat će biti instanca klase OracleStatement koja implementira Statement interfejs. Slično važi i za ResultSet interfejs.

Konačni rezultat ovakvog koncepta je program koji operiše 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 učitavanje drajvera i otvaranje konekcije sa SUBP. Ovakva arhitektura je zaslužna što se isti JDBC programski kod može koristiti sa različitim SUBP prostom zamenom drajvera.

Page 98: 68261077 Java i Internet Programiranje1

93

Poglavlje 7

Uvod u višeslojne klijent/server sisteme

7.1 Klasični klijent/server sistemi U klasičnim sistemima za obradu podataka po klijent/server modelu mogu se uočiti tri klase komponenti: serveri, klijenti i mreža. Namena servera je, pre svega, optimalno upravljanje zajedničkim resursima, što su najčešće podaci. Server obavlja upravljanje bazom podataka kojoj pristupa više korisnika, vrši kontrolu pristupa i bezbednosti podataka i centralizovano obezbeđuje integritet podataka za sve aplikacije. Klijenti omogućavaju korisnicima pristup do podataka. Klijent-aplikacije vrše upravljanje korisničkim interfejsom i izvršavaju deo logike aplikacije. Računarska mreža i komunikacioni softver omogućavaju prenos podataka između klijenta i servera. Slika 7.1 prikazuje skicu klijent/server sistema.

Server

Klijent

Klijent

Klijent

Mreža

Slika 7.1. Skica klasičnog klijent/server sistema

Jedna od osnovnih karakteristika klijent/server sistema je distribuirana obrada podataka – logika aplikacije je podeljena između klijenta i servera tako da obezbedi optimalno korišćenje resursa. Na primer, prezentacija podataka i provera ulaznih podataka su sastavni deo klijent-aplikacije, dok se rukovanje podacima, u smislu njihovog fizičkog smeštaja i kontrole pristupa, vrši na serveru.

Neke od prednosti ovakvog modela obrade podataka su centralizovano uprav-ljanje resursima sistema i jednostavnije obezbeđivanje sigurnosti podataka.

Page 99: 68261077 Java i Internet Programiranje1

94

U ovakvim sistemima se najčešće nalazi samo jedan server. Sa aspekta računarskog hardvera to može biti više računara povezanih na odgovarajući način radi povećanja pouzdanosti sistema (otkaz jednog server-računara ne izaziva otkaz celog sistema) ili brzine odziva (kroz paralelizovan rad više serverskih računara). Osnovni problem koji se ovde javlja je nedostatak skalabilnosti. Pod skalabilnošću se podrazumeva osobina sistema da omogući efikasan rad velikom broju korisnika, i da dalje povećavanje broja korisnika ne izaziva drastičan pad performansi sistema. Povećavanje propusne moći servera u pogledu broja korisnika koji mogu efikasno da rade ili količine podataka koja se obrađuje je izuzetno skupo jer zahteva velika ulaganja u serverske računare visokih performansi.

Klijent-aplikacije u ovakvim sistemima su programi pisani za konkretnu raču-narsku 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 održavati na svakom klijent-računaru, što u velikim mrežama predstavlja mnogo veći izdatak od inicijalne nabavke opreme, na primer.

7.2 WWW i Java kao platforma za klijente Široka prihvaćenost Interneta, odnosno World Wide Web-a, je proizvela, između ostalog, i jedan izuzetno važan efekat: okruženje Web čitača (browsera) je postalo poznato za većinu korisnika računara. Informacioni sistemi kod kojih se komunikacija sa korisnikom odvija kroz Web čitač u velikoj meri eliminiše potrebu za dugotrajnom i skupom obukom korisnika.

Sa druge strane, Java je programski jezik čija je osnovna karakteristika potpuna nezavisnost od fizičke platforme na nivou prevedenog programskog koda. Java programi se, u formi apleta, mogu ugrađivati u Web stranice i na taj način distribuirati korisnicima. Posledica ovoga je mogućnost automatske distribucije i instalacije klijent-aplikacija na mreži, bez obzira na konkretnu fizičku platformu klijenta – dovoljan je Web čitač sa podrškom za Javu.

Kombinacija WWW i Java tehnologija je omogućila implementaciju klijent/ser-ver informacionih sistema koje, za razliku od klasičnih sistema, karakterišu sledeće osobine:

• jednostavan i široko prihvaćen oblik korisničkog interfejsa (Web čitač); • automatska distribucija i instalacija klijent-aplikacija; • jednostavnije održavanje sistema, naročito u heterogenim mrežama.

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

Page 100: 68261077 Java i Internet Programiranje1

95

Web server + SUBP

Web čitač

Web čitač

Web čitač

Mreža

Slika 7.2 Sistem zasnovan na WWW i Java tehnologijama

7.3 Troslojni klijent/server sistemi Informacione sisteme koji su do sada razmatrani možemo 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 sledeći podsistemi:

1. podsistem za interakciju sa korisnikom (implementira funkcije korisničkog interfejsa);

2. podsistem za implementaciju osnovnih funkcija sistema (implementira tzv. “poslovnu logiku”);

3. podsistem za rukovanje podacima, pri čemu se pre svega misli na fizički smeštaj 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 između podsistema za interakciju sa korisnikom i podsistema za rukovanje podacima. Zbog ovakvog međusobnog odnosa, ovi podsistemi se nazivaju i slojevi.

Klijentaplikacija

Aplikacioniserver SUBP

Slika 7.3. Elementi troslojne arhitekture sistema

Za razliku od dvoslojnog modela obrade podataka, gde je logika aplikacije bila podeljena između klijenta i servera, u ovom modelu ona se nalazi koncentrisana u tzv. aplikacionom serveru – serveru čija je namena da izvršava programski kod koji implementira logiku aplikacije. Klijent aplikacija je namenjena samo za implementaciju korisničkog interfejsa, a funkcija sistema za upravljanje bazom podataka je isključivo fizičko rukovanje podacima (u prethodnom slučaju je, pored toga, izvršavao i deo logike aplikacije).

Page 101: 68261077 Java i Internet Programiranje1

96

Ovakav koncept je doveo do podele programskog koda na segmente koji imple-mentiraju tačno određene funkcije sistema. Tako organizovan sistem je jednostavniji za održavanje, jer je moguće nezavisno razvijati korisnički interfejs, i logiku aplikacije. Za potrebe fizičkog rukovanja podacima najčešće se koristi neki od komercijalno dostupnih servera za tu namenu.

Troslojne arhitekture informacionih sistema podrazumevaju oslanjanje na standarde u odgovarajućom oblastima. Najčešće su u pitanju sistemi zasnovani na Internet tehnologijama. Oslanjanje na standarde omogućava integraciju informacionih sistema heterogenih u pogledu korišćene hardverske i softverske opreme. Na primer, računarska mreža ovakvog sistema može biti zasnovana na TCP/IP familiji protokola. Serveri u mreži mogu biti od različitih proizvođača, sve dok obezbeđuju standardne servise predviđene protokolom.

Druga važna karakteristika troslojnih sistema je skalabilnost. Pre svega, povećavanje broja klijenata je jednostavno. Povećavanje propusne moći servera srednjeg sloja je moguće kroz dodavanje novih serverskih mašina. Analogno tome moguće je povećati i propusnu moć zadnjeg sloja. Slika 7.4 prikazuje jednu od mogućih konfiguracija ovakvog sistema. Ovde je važno primetiti da se povećanje brzine odziva serverskog sloja može postići dodavanjem novih serverskih mašina uz korišćenje postojećih. Na taj način može se iskoristiti i oprema koja ne mora imati vrhunske performanse. Sistem sa više servera karakteriše i povećana pouzdanost i fleksibilnost. Logika aplikacije se može menjati i u toku rada sistema. Pored toga, moguće je efikasno vršiti balansiranje opterećenja serverskog podsistema.

Daljim proširivanjem koncepta troslojnih sistema dolazi se do pojma višeslojnih sistema (multitier architecture), gde se vrši dalja podela na komponente u okviru srednjeg sloja sa ciljem još većeg povećanja skalabilnosti, odnosno performansi.

Klijent

SUBP

Aplikacioni server

Aplikacioni server

Aplikacioni server

Klijent

Klijent

Klijent

SUBP

Klijent Slika 7.4. Skica konfiguracije sistema sa troslojnom arhitekturom

Page 102: 68261077 Java i Internet Programiranje1

97

Jedna moguća arhitektura višeslojnih sistema je prikazana na slici 7.5. Srednji sloj je podeljen na dva sloja: jedan je namenjen za opsluživanje Web klijenata, a drugi sadrži komponente koje implementiraju poslovnu logiku sistema.

Web browser

Web browser

Web browser

Web browser

App server

DBMS

Web Server

DBMS

App server

App server

Web Server

Web Server

Slika 7.5. Jedna moguća arhitektura višeslojnog sistema

Za ovakve “četvoroslojne” sisteme postoje odgovarajuće tehnologije koje omogućavanju njihovu izgradnju. Slika 7.6 prikazuje skup ovakvih tehnologija definisanih oko programskog jezika Java.

RDBMS

RDBMS

HTML

HTTP

Servlets / JSP

RMI / IIOP

EJB / CORBA

JDBC

Slika 7.6. Java tehnologije za izgradnju višeslojnih sistema

Interakciju sa korisnikom u ovakvom sistemu obavljaju klijenti koji imaju standardan Web interfejs. U pitanju su Web čitači koji prikazuju HTML stranice. Komunikacija između Web čitača i Web servera se odvija putem standardnog HTTP protokola, uz dodatak cookie podataka kojima se prati korisnička sesija dok se on kreće po Web sajtu. Stranice koje prikazuju klijenti su najčešće generisane dinamički, tj. po prijemu zahteva za nekom stranicom. Dinamičko generisanje Web sadržaja na osnovu podataka iz ostatka sistema vrše 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 između ova dva

Page 103: 68261077 Java i Internet Programiranje1

98

sloja je JRMP (Java Remote Method Protocol), protokol za komunikaciju između distribuiranih Java objekata, ili IIOP (Internet Inter-ORB Protocol) ekvivalentan protokol vezan za CORBA tehnologiju. CORBA/EJB komponente za potrebe skladištenja podataka u bazi podataka pristupaju serveru za upravljanje bazama podataka preko standardnog JDBC interfejsa.

Naredna poglavlja će opisati ovde pomenute tehnologije.

Page 104: 68261077 Java i Internet Programiranje1

99

Poglavlje 8

Dinamičko generisanje HTML-a i servleti

8.1 HTTP protokol Web čitači su namenjeni za prikazivanje Web stranica koje im isporučuju odgovarajući Web serveri. Struktura i izgled samih stranica se opisuje jezikom HTML. Komunikacija između Web klijenta (tj. čitača) 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 lakše razumela materija u nared-nim odeljcima; detaljima ovog protokola se nećemo baviti.)

HTTP klijent HTTP server

GET /docs.html HTTP/1.0User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)Accept-Cookies: yesHost: branko.tmd.ns.ac.yu...

Slika 8.1. Slanje zahteva HTTP klijenta

Zahtev koji klijent upućuje serveru je tekstualna poruka koja sadrži više redova. Prvi red poruke je najvažniji: on sadrži komandu koju klijent upućuje serveru (u ovom slučaju to je GET komanda kojom se zahteva određena 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 može da iskoristi za svoje potrebe. U ovom primeru, polje User-Agent predstavlja opis klijentskog softvera (tip i verzija Web čitača i operativnog sistema), polje Host predstavlja simboličku adresu klijenta, itd. GET je samo jedna od komandi koje može da uputi klijent. Ujedno je to i daleko najčešće upotrebljavana komanda. U nastavku teksta biće reči i o nekim drugim komandama.

Zadatak servera je da po prijemu ovakvog zahteva odgovori na njega. U ovom slučaju odgovor servera treba da sadrži traženu datoteku (docs.html), pri čemu je format odgovora takođe definisan HTTP protokolom. Slika 8.2 prikazuje situaciju kada server šalje odgovor klijentu.

Page 105: 68261077 Java i Internet Programiranje1

100

HTTP klijent HTTP serverHTTP/1.0 200 OKContent-Type: text/html

<HTML><HEAD>...

Slika 8.2. Slanje odgovora HTTP klijentu

Prvi red odgovora sadrži oznaku protokola, trocifreni broj koji predstavlja status izvršene operacije (u ovom slučaju to je 200), i tekstualni opis tog statusa (OK). Konstanta 200 označava da je zahtev uspešno izvršen i da se tražena datoteka nalazi u nastavku poruke. Druge konstante koje se češće sreću su 404 (tražena datoteka nije pronađena), 407 (pristup datoteci nije dozvoljen), i 302 (datoteka premeštena na drugo mesto). Konstante su definisane HTTP protokolom.

Sledeći red odgovora (Content-Type) je oznaka tipa sadržaja koji se vraća. U pitanju su standardizovane oznake propisane u odgovarajućim RFC dokumen-tima. 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 sadržaja datoteke koja se šalje nalazi se jedan prazan red koji razdvaja zaglavlje odgovora od samog sadržaja datoteke.

Ukupna sekvenca aktivnosti klijenta i servera u HTTP komunikaciji je sledeća:

1. klijent otvara konekciju sa serverom 2. klijent šalje zahtev serveru 3. server vraća odgovor 4. zatvara se konekcija.

Vidimo da je komunikacija između klijenta i servera zasnovana na zahtev/odgovor principu. Svaki par zahtev/odgovor smatra se nezavisnim od ostalih. Recimo, u slučaju da prvi klijent pošalje zahtev serveru i dobije odgovor, zatim drugi klijent pošalje zahtev i dobije odgovor, pa potom ponovo prvi klijent pošalje novi zahtev, nema načina da se ustanovi da je prvi klijent poslao dva zahteva (prvi i treći). Server svaki zahtev opslužuje nezavisno od ostalih zahteva. U tom smislu, HTTP je stateless protokol: ne omogućava praćenje stanja korisničke sesije između slanja više različitih zahteva.

Važno je primetiti još nešto: jedino što klijent može da zatraži od servera je datoteka. Na serveru je da tu datoteku pronađe (eventualno i modifikuje!) i pošalje klijentu. Web sadržaji koji se smeštaju na server vidljivi su klijentima kao pojedine datoteke. Te datoteke mogu biti unapred pripremljene (npr. u editoru kakav je Microsoft FrontPage) i smeštene u fajl-sistem Web servera. Mogu biti i generisane “u letu” po prijemu zahteva klijenta na neki poseban način; klijent ne zna da li je datoteka koju je tražio generisana statički ili dinamički. U tom smislu, Web sadržaje (zapravo, datoteke) možemo podeliti na statičke i dinamičke.

Page 106: 68261077 Java i Internet Programiranje1

101

8.2 Statički i dinamički Web sadržaji Statički Web sadržaji su datoteke koje su unapred smeštene u odgovarajući direktorijum fajl-sistema Web servera i spremne su za isporuku klijentima po njihovom zahtevu. Slika 8.3 prikazuje sekvencu događaja kada klijent zatraži ovakvu datoteku.

HTTP klijent HTTP server

1) klijent zahteva fajl

2) server učitava fajl iz fajl-sistemai šalje ga klijentu

Slika 8.3. Isporuka statičkih sadržaja

Sa slike se vidi da, po prijemu zahteva klijenta, server učitava traženu datoteku iz svog fajl-sistema i šalje je nazad klijentu preko mreže.

Dinamički sadržaji nisu uskladišteni unapred već se generišu za svaki zahtev klijenta posebno. Sekvenca događaja kada klijent zatraži neki dinamički generisanu datoteku je prikazana na slici 8.4.

HTTP klijent HTTP server

1) klijent zahteva fajl

2) server generiše fajl i šaljega klijentu; ne snima ga u svoj fajl-sistem

Slika 8.4. Isporuka dinamičkih sadržaja

U ovom slučaju server neće tražiti datoteku u okviru fajl-sistema; na neki način server “zna” da je u pitanju dinamički generisana datoteka i poziva odgovarajući potprogram koji će je generisati. Najčešće nema potrebe ovako generisanu datoteku čuvati na serveru; ona se zato neće čuvati u okviru fajl-sistema servera.

8.3 Servleti Servleti su jedna od tehnologija za generisanje dinamičkih Web sadržaja. Da bi se servleti mogli koristiti, Web server mora da ima odgovarajuću podršku za servlete. Pisanje servleta je moguće samo u programskom jeziku Java, tako da je za njihovo izvršavanje potrebna i JVM (koju najčešće obezbeđuje Web server).

Servlet je, zapravo, Java klasa koja nasleđuje 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 napišemo klasu koja nasleđuje HttpServlet i ne dodaje ili redefiniše ništa; takav servlet bio bi funkcionalan ali ne bi radio ništa korisno. Dodavanje funkcionalnosti u servlet postiže se redefinisanjem sledećih metoda (spisak nije potpun, ovde su pobrojane samo najčešće korišćene metode):

• init • destroy • doGet • doPost

Page 107: 68261077 Java i Internet Programiranje1

102

Važno je imati na umu da se ove metode, u principu, nikada ne pozivaju direktno. Njih će pozivati Web server u odgovarajućim trenucima.

8.3.1 Metoda init Metoda init je namenjena za inicijalizaciju servleta pre njegove prve upotrebe. Poziva se tačno jednom. Nema prepreke da servlet klasa sadrži i svoj kon-struktor u kome će obaviti deo inicijalizacije, ali je na raspolaganju i ova metoda.

8.3.2 Metoda destroy Metoda destroy se poziva prilikom uništavanja servleta. Namenjena je za obavljanje zadataka koje je neophodno obaviti pre nego što se završi sa radom (oslobađanje resursa koje je servlet zauzimao: otvorenih datoteka, konekcija sa bazom podataka, itd). To se najčešće dešava prilikom zaustavljanja Web servera ili u slučaju da Web server iz nekog razloga mora da uništi servlet objekat pre nego što nastavi sa radom.

8.3.3 Metoda doGet Metoda doGet se poziva za svaki GET zahtev klijenta koji je tražio datoteku za čije generisanje je zadužen dati servlet. Metoda ima dva parametra: public void doGet(HttpServletRequest request, HttpServletResponse response);

prvi parametar (request) sadrži informacije o zahtevu klijenta na koji se odgovara, a drugi parametar (response) je namenjen da prihvati generisani odgovor servleta.

Dakle, tipična sekvenca poziva metoda servleta je sledeća:

1. poziv metode init prilikom pokretanja Web servera 2. višestruki 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 redefiniše samo doGet metodu. U okviru doGet metode generiše se HTML stranica koja će biti poslata klijentu. U pitanju je stranica koja sadrži 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)

Page 108: 68261077 Java i Internet Programiranje1

103

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 sadržaja koji će se vratiti klijentu. U ovom slučaju to je text/html. Ova vrednost će se direktno ugraditi u zaglavlje odgovora HTTP protokola, tako da je poziv ove metode obavezan.

Sledeći je poziv metode getWriter. Rezultat ove metode je inicijalizovan izlazni stream (objekat klase PrintWriter) u koji se upisuje generisani sadržaj. U toku formiranja rezultujuće HTML stranice najčešće se poziva metoda println koja dodaje fragmente stranice u izlazni stream.

Redosled pozivanja ovih metoda je važan: setContentType je neophodno pozvati pre metode getWriter. Drugo, setContentType se obično poziva na samom vrhu doGet metode, pre bilo kog drugog poziva.

Prevođenjem ovakvog servleta dobija se .class fajl koji se smešta na odgovara-juće mesto u okviru instalacije Web servera. Način kako pristupiti servletu iz Web čitača zavisi od korišćenog Web servera. Najčešće se takvom servletu može pristupiti pomoću URL-a koji ima sledeći oblik: http://ime-hosta/servlet/HelloWorld

Poslednji segment URL-a predstavlja naziv servlet klase. Ovo se, kod većine Web servera, može konfigurisati na određeni način. Upućivanjem Web čitača na ovakvu adresu dobiće se rezultat kao na slici 8.5.

Slika 8.5. Rezultat rada HelloWorld servleta

8.5 Analiza zaglavlja HTTP zahteva Sledeći primer ilustruje mogućnost pristupa svim podacima iz HTTP zahteva klijenta na koji se odgovara. Pristup tim podacima se vrši preko request objekta koji je parametar doGet metode.

Page 109: 68261077 Java i Internet Programiranje1

104

import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.Enumeration; import java.util.Vector; /** Servlet koji ispisuje sadržaj 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 sadržaj [naziv zaglavlja: vrednost]. Metoda getHeaderNames objekta request je vraća listu naziva zaglavlja koje se nalaze u okviru zahteva. Za svaki takav naziv zaglavlja može se dobiti sadržaj pozivom metode getHeader. Rezultujuća HTML stranica sadrži redove teksta koji prikazuju sadržaje zaglavlja zahteva.

8.6 Konkurentni pristup servletu Za svaku servlet klasu koja se pravilno instalira Web server će kreirati tačno jednu instancu ove klase. Ova instanca se koristi za obrađivanje svih zahteva koji pristignu od klijenata.

Page 110: 68261077 Java i Internet Programiranje1

105

Server će, tipično, 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 znači da se može 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 sledeći primer. Servlet klasa AccessTest sadrži atribut hitCount koji sadrži broj pristupa datom servletu. Brojanje pristupa se obavlja inkrementiranjem ovog brojača 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 biće upućivani na isti servlet objekat, pa će se samim tim koristiti i isti atribut koji služi kao brojač. Konačan efekat je da će svaki korisnik u stranici koju je tražio dobiti izveštaj 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 Praćenje sesije korisnika U odeljku 8.1 prikazan je HTTP protokol i tamo je naglašeno kako on ne omogućava praćenje sesije korisnika. Kako bi se ovaj cilj ipak postigao definisano je pomoćno rešenje. Radi se o mehanizmu slanja “kolačića” (cookies) između klijenta i servera koga je uveo Netscape Navigator, a kasnije je postao standardan mehanizam za ovu namenu podržan od svih Web čitača i servera.

Page 111: 68261077 Java i Internet Programiranje1

106

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č podešen tako da radi sa cookie-ima, on će u svim sledećim zahtevima koje bude slao tom serveru uključiti i cookie, tako da će server moći da prepozna klijenta koga je već ranije opsluživao. Šta je jedan cookie zapravo? Možemo ga shvatiti kao string od tipično 20-30 nerazumljivih znakova koji je namenjen za jednoznačno identifikovanje korisnika na serveru.

HTTP klijent HTTP server

1) zahtev

2) odgovor + cookie

HTTP klijent HTTP server

1) zahtev + cookie

2) odgovor + cookie

a)

b) Slika 8.6. a) slanje prvog zahteva i prijem odgovora koji uključuje cookie

b) svi sledeći zahtevi sadrže cookie

Iako je moguće pristupiti cookie informacijama iz servleta, to nije potrebno činiti radi praćenja sesije korisnika. Za tu svrhu na raspolaganju su funkcije višeg nivoa koje će biti ilustrovane na sledećem primeru.

Posmatrajmo servlet čiji je zadatak da generiše stranicu sa izveštajem o broju pristupa toj stranici od strane svakog korisnika. Dakle, potrebno je uočiti razliku između više korisnika, što primer u odeljku 8.6 nije činio. Realizacija je jednostavna kada se koristi klasa HttpSession, koja opisuje korisničku sesiju. Ona služi kao kontejner za objekte koji opisuju datog korisnika. U taj kontejner se mogu smestiti objekti pod nekim imenom, a kasnije se tim objektima može pristupiti na osnovu tog imena. Za potrebe brojanja pristupa nekoj stranici koristićemo 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.*;

Page 112: 68261077 Java i Internet Programiranje1

107

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>"); // ispiši ID sesije out.println("<b>Sesija ID:" + session.getId() + "</b>"); // ako je getValue vratio null, onda je ovo prvi // pristup toj stranici, inače je u pitanju neki sledeći 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 dešava 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 obrađujemo – za to se pobrinuo Web server pomoću cookie mehanizma. Zatim se nad tim objektom poziva metoda getValue, što predstavlja pokušaj da se pristupi objektu koji je ranije smešten u kontejner pod nazivom “brojac”. U pitanju je objekat klase SessionCounter. Ukoliko poziv metode getValue ima za rezultat null, to znači da niko ranije nije već smestio objekat u kontejner pod datim imenom. Drugim rečima, u pitanju je prvi pristup servletu. U tom slučaju se kreira novi SessionCounter objekat, koji se smesti u kontejner pod imenom “brojac”. U suprotnom slučaju koristi se postojeći SessionCounter objekat, izvrši se njegovo inkrementiranje i u stranicu se ugradi izveštaj o broju pristupa.

Slično ovom primeru, svi servleti kod kojih je potrebno pristupati informa-cijama o korisničkoj sesiji imali bi rukovanje HttpSession objektom kao što je ovde prikazano. Ista korisnička sesija bi delila isti kontejner za objekte u različitim servletima.

Page 113: 68261077 Java i Internet Programiranje1

108

8.8 Preuzimanje podataka sa HTML formi HTML forme su jedini način da korisnik unosi neke podatke koristeći 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 upućuje Web čitač prilikom klika na dugme “Pošalji”.

a)

b)

Slika 8.7. a) stranica sa HTML formom b) stranica na koju se Web čitač upućuje nakon klika na dugme “Pošalji”

Prva stranica je statička HTML stranica, čiji sadržaj sledi (elementi forme su naglašeni, 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 preći kada korisnik klikne na dugme “Pošalji”. U pitanju je naziv servleta koji će prikazati narednu stranicu. Zahtev koga će Web

Page 114: 68261077 Java i Internet Programiranje1

109

čitač poslati u tom trenutku imaće ugrađene i podatke iz forme. Podaci će biti navedeni u sledećem obliku: ime=Branko&prezime=Milosavljevic

Dakle, vrednosti u pojedinim poljima se navode kao parovi ime=vrednost, gde je ime naziv polja, a vrednost sadržaj koji je korisnik uneo. Više ovakvih parova se razdvaja ampersand znakom (&). Može se reći da stranica na koju se upućuje 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 više načina da se parametri, tj. sadržaj 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. Sledeći primer predstavlja zahtev čitača u tom slučaju. GET /kurs/FormTest?ime=Branko&prezime=Milosavljevic HTTP/1.0 ...

Druga mogućnost je da se parametri pošalju kao deo zahteva koji sadrži POST komandu: POST /kurs/FormTest HTTP/1.0 ... ime=Branko&prezime=Milosavljevic

Razlika je, sa stanovišta korisnika, vidljiva u adresi stranice koja se vidi u okviru čitača. 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

Page 115: 68261077 Java i Internet Programiranje1

110

Podaci uneti u formi su vidljivi u adresi stranice kada se koristi GET zahtev, što može biti neprikladno u slučaju da su podaci poverljivi. Iz tog razloga se najčešće 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 može imati vrednosti get i post, a ako se izostavi podrazumeva se vrednost get. Sledi primer: <form action="FormTest" method="post">

Sadržaj popunjenih polja forme se šalje, kroz parametre, novoj stranici. Ta stranica može biti statička HTML stranica, ali ona tada ne može imati nikakve koristi od tih parametara; da bi se parametri na neki način interpretirali potrebna je dinamička stranica koju možemo generisati odgovarajućim servletom. Stranica sa slike 8.7.b je upravo takva stranica koju je generisao sledeći 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 može pristupiti pomoću metode getParameter objekta request. Vrednosti tih parametara mogu se koristiti kasnije

Page 116: 68261077 Java i Internet Programiranje1

111

na proizvoljan način. Ovde su upotrebljene tako da su ugrađene u tekst rezultujuće HTML stranice; na primer, moguće je smestiti ove podatke u tekuću sesiju pomoću HttpSession objekta, koji je opisan u odeljku 8.7.

Ovaj servlet redefiniše metode doGet i doPost, kako bi mogao da odgovori na obe vrste zahteva. Metoda doGet samo poziva metodu doPost, čime se postiže 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. Korišćenje JDBC interfejsa unutar servleta ne razlikuje se ni na koji način od korišćenja unutar klasične Java aplikacije. Sledi primer servleta koji pristupa bazi podataka radi listanja svih registrovanih nastavnika (u pitanju je šema baze podataka korišćena u poglavlju 6). Rezultat rada servleta je HTML stranica sa listom svih nastavnika pobrojanih u tabeli nastavnici. import javax.servlet.*; import javax.servlet.http.*; import java.sql.*; import java.util.*; import 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(); } } /** Uništavanje servleta: zatvaranje veze sa bazom */ public void destroy() { try { conn.close(); } catch (SQLException ex) { ex.printStackTrace(); } } public void doGet(HttpServletRequest req,

Page 117: 68261077 Java i Internet Programiranje1

112

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 greške 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 učitava JDBC drajver i otvara konekcija sa bazom podataka. Ova konekcija je atribut klase, tako da je dostupna iz svih metoda, uključujući i doGet. U okviru metode destroy konekcija se zatvara. Ovakav koncept znači da će jedan servlet koristiti tačno jednu konekciju za sve zahteve. To može biti rešenje koje je zgodno naročito kada se softver za upravljanje bazom podataka licencira po aktivnoj konekciji – u ovom slučaju neograničen broj korisnika Web sajta sa ovim servletom imao bi

Page 118: 68261077 Java i Internet Programiranje1

113

pristup bazi podataka preko samo jedne konekcije. Međutim, treba imati u vidu da su moguće situacije kada se ista konekcija koristi za operacije nad bazom podataka iz više programskih niti. Neki JDBC drajveri, kao što je Oracle, ne dopuštaju izvršavanje DML operacija iz više niti istovremeno preko iste konekcije, tako da ovo rešenje ima smisla samo kada se postavljaju upiti (oni se mogu paralelno izvršavati iz više niti).

Alternativa ovom rešenju 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 rešenje ovog problema biće prikazano u okviru poglavlja 11.

Page 119: 68261077 Java i Internet Programiranje1

114

Poglavlje 9

Java Server Pages

9.1 JSP koncept Prethodno poglavlje predstavlja kratak pregled mogućnosti i načina upotrebe servlet tehnologije za dinamičko generisanje HTML stranica. Koncept servleta ima dobrih osobina, npr. prenosivost servleta na različite računarske platforme i Web servere, efikasno izvršavanje, itd. Međutim, osnovna mana servleta je što je generisanje HTML teksta smešteno unutar servlet klase. To znači da je teško razdvojiti posao Web dizajnera i programera: obe vrste autora moraju da modifikuju iste fajlove sa Java izvornim kodom. Veliki broj Web dizajnera nije vičan programiranju u Javi (ili programiranju uopšte), što drastično otežava 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 generiše smešten unutar servleta, to svaka izmena dizajna stranice zahteva izmenu i ponovno prevođenje servleta što dodatno komplikuje njihovo korišćenje.

Ideja koja se nalazi u osnovi Java Server Pages (JSP) tehnologije je da se omogući paralelan, ili bar nezavisan, rad Web dizajnera i programera. JSP stranice su tekstualne datoteke koje sadrže HTML dokumente. Pri tome se za pisanje takvih dokumenata mogu koristiti standardni HTML elementi, ali i posebni tagovi koji omogućavaju dodavanje dinamičkih elemenata u stranicu. Sledi primer fragmenta jedne takve stranice (dinamički elementi su naglašeni). <html> ... <h4>Dobrodošli, <%= username %></h4> Danas je <%= new java.util.Date() %>. ... </html>

Na mestu gde se nalazi specijalni tag <%=username%> biće ugrađeno korisničko ime tekuće prijavljenog korisnika, na primer.

Realizacija JSP tehnologije je posebno zanimljiva: ovako napisana JSP stranica će poslužiti da se na osnovu nje generiše servlet koji će proizvesti upravo onakvu HTML stranicu kako je to specificirano u JSP stranici. Takav servlet se

Page 120: 68261077 Java i Internet Programiranje1

115

generiše kao klasična datoteka sa izvornim Java kodom. Nakon generisanja servleta, vrši se prevođenje servlet klase u Java byte-code. Tako prevedeni servlet se može koristiti na poznati način da proizvede rezultujuću HTML stranicu.

Sekvenca događaja je, tačnije, sledeća: autor JSP stranice smešta datu stranicu u odgovarajući direktorijum Web servera. Kada neki Web klijent prvi put zatraži datu stranicu, Web server će na osnovu nje automatski generisati odgovarajući servlet, prevesti ga i zatim pokrenuti. Prilikom narednih zahteva za tom JSP stranicom biće dovoljno samo pozvati odgovarajuću metodu servleta. Da li će izvorni kod generisanog servleta biti dostupan ili ne zavisi od Web servera.

9.2 Vrste dinamičkih elemenata Ovaj odeljak prikazuje nekoliko vrsta dinamičkih elemenata koje uvodi JSP.

9.2.1 Izrazi JSP izrazi su elementi koji se nalaze između znakova <%= i %>. Sadržaj ovog elementa može biti proizvoljan Java izraz. Treba obratiti pažnju na to da se izrazi ne završavaju znakom tačka-zarez. Izrazi mogu biti proizvoljnog tipa. Za izraze koji nisu tipa String poziva se metoda toString kako bi se dobila odgovarajuća 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 tekući datum i vreme u trenutku kreiranja. Kako je u pitanju izraz koji nije tipa String, prilikom ugradnje ove vrednosti u stranicu biće pozvana metoda toString klase Date koja će obezbediti odgovarajuću string reprezentaciju ovog objekta. Konačan rezultat je stranica koja će prilikom svakog pristupa prikazati datum i vreme kada je pristup izvršen.

9.2.2 Skriptleti JSP skriptleti su elementi koji se pojavljuju između znakova <% i %>. Njihov sadržaj je proizvoljan segment Java programskog koda. Ovako ugrađen programski kod direktno se ugrađuje u kod generisanog servleta na odgova-rajuće mesto! Sledi primer jedne JSP stranice: <html> ... <% if (Math.random() < 0.5) { %> Dobar dan! <% } else { %>

Page 121: 68261077 Java i Internet Programiranje1

116

Dobro veče! <% } %> ... </html>

Iz primera se vidi da segment Java koda koji je sadržaj skiptleta ne mora predstavljati validan Java iskaz, već samo njegov fragment. Evo kako bi se ovakva JSP stranica prevela u servlet (ugrađeni fragmenti su naglašeni): class GenerisaniServlet extends HttpServlet { public void doGet(...) { ... out.println("<html>"); ... if (Math.random() < 0.5) { out.println("Dobar dan!"); } else { out.println("Dobro veče!"); } ... out.println("</html>"); } }

Može se reći da se svaki red JSP stranice koji sadrži samo klasične HTML oznake konvertuje u jedan out.println(...) iskaz u servletu. JSP skriptleti se ugrađuju u servlet na odgovarajućem mestu direktnim kopiranjem datog teksta.

Jedna od posledica ovakvog tretiranja skriptleta je i da opseg važenja promenlji-vih definisanih u skriptletima može da se proteže kroz više 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>

Page 122: 68261077 Java i Internet Programiranje1

117

<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 između znakova <%! i %>. Deklaracije sadrže tekst koji će u rezultujući servlet biti smešten na mestu atributa ili metode servlet klase. Na primer, deklaracija <%! int hitCount = 0; %>

proizvešće sledeći atribut servlet klase: class GenerisaniServlet extends HttpServlet { ... int hitCount = 0; }

Ovakav atribut servlet klase je dostupan u višestrukim pozivima doGet ili doPost metode servleta. Primer u odeljku 8.6 ilustrovao je upotrebu ovakvog atributa. Ovde navodimo primer koji sličan efekat postiže u JSP okruženju. <%! 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 jedno-stavnosti. Metoda inc primera iz odeljka 8.6 se takođe može dodati kao JSP deklaracija. Položaj JSP deklaracije u okviru JSP stranice nije bitan.

9.2.4 Direktive JSP direktive su elementi smešteni između znakova <%@ i %>. Namenjene su za obavljanje nekih specifičnih zadataka u okviru JSP stranice. Ovde ćemo navesti neke primere. <%@ page import="java.util.*" %>

Page 123: 68261077 Java i Internet Programiranje1

118

Dodavanje odgovarajuće import deklaracije na početak generisanog servleta. Nakon ovakve direktive ne mora se više 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" %>

Uključuje kompletan sadržaj stranice included.jsp u tekuću stranicu. Uključena stranica može biti JSP stranica, pri čemu će se ona posebno interpretirati i takav sadžaj biti uključen u tekuću stranicu. Može biti i statička HTML stranica.

9.3 Predefinisane promenljive U okviru JSP stranica moguće 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

Najčešće 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 način kako se to čini i unutar klasičnog servleta.

9.4 Skladištenje podataka i JavaBeans Podaci iz HTML formi koje unosi korisnik prenose se na način kako je to opisano u odeljku 8.8. JSP omogućava jednostavno preuzimanje parametara stranice i njihovo smeštanje u svojstva (properties) neke JavaBean komponente.

Posmatrajmo HTML stranicu sa formom kao na slici 9.1.

Page 124: 68261077 Java i Internet Programiranje1

119

Slika 9.1 HTML forma

Forma sadrži dva polja čiji su nazivi, recimo, username i password. Sadržaj ovih polja biće prenet kroz parametre novoj stranici nakon klika na dugme Pošalji. U okviru te nove stranice možemo smestiti ove podatke u sledeću 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 Pošalji: <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, deklariše objekat sa nazivom user klase somepackage.User. Sledeći specijalni tag, jsp:setProperty, postavlja vrednost svojstva username objekta user na vrednost parametra stranice koji se zove username. Treći specijalni tag radi istu stvar za svojstvo password.

Objekat user je dostupan u ostatku stranice (slično kao da je u pitanju JSP deklaracija). Na primer: <html> ... <% if (user.login()) { %> Uspešno ste se prijavili! <% } else { %> Niste se uspešno prijavili!

Page 125: 68261077 Java i Internet Programiranje1

120

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

Primer pretpostavlja da klasa User ima definisanu metodu login koja vraća logičku vrednost. Metoda login, na primer, može da proveri u bazi podataka da li korisnik sa datim imenom i lozinkom postoji. Na osnovu takve provere u stranici može da se ispiše odgovarajući tekst.

9.5 Opseg vidljivosti JavaBean komponenti U prethodnom primeru komponenta user je korišćena samo u okviru jedne stranice. To ne mora biti uvek slučaj. Opseg vidljivosti komponente može biti različit, što se specificira atributom scope taga jsp:useBean. Sledi primer: <jsp:useBean id="user" class="somepackage.User" scope="session"/>

Vrednost session atributa scope znači da je komponenta user vezana za sesiju korisnika. Tada ovakvu deklaraciju možemo da postavimo u svaku stranicu gde se koristi komponenta user. Prilikom izvršavanja stranice izvršiće se provera da li postoji komponenta user vezana za tekuću sesiju korisnika. Ako ne postoji, kreiraće se nova instanca klase i vezati za sesiju. U suprotnom koristiće se postojeća instanca. Ovakvo ponašanje je opisano i u odeljku 8.7, gde je u primeru korišćena klasa SessionCounter na isti način kako se ovde koristi komponenta user. Tabela 9.2 navodi moguće 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. Moguće vrednosti atributa scope taga jsp:useBean.

Česta upotreba session-scoped komponenti je upravo za čuvanje informacija o korisniku kome pripada tekuća sesija. Na primer, instanca klase User iz prethodnog primera se mogla definisati kao session-scoped komponenta. Tada se u vrhu svake JSP stranice može naći sledeći segment: <jsp:useBean id="user" class="somepackage.User" scope="session"/> <% if (!user.isLoggedIn()) response.sendRedirect(response.encodeURL("login.jsp")); %>

Prethodni segment će, u slučaju da je korisnik pokušao da pristupi stranici bez prethodnog prijavljivanja (to se proverava pomoću metode isLoggedIn), Web čitaču biti vraćena poruka da se preusmeri na stranicu login.jsp. U suprotnom stranica će biti regularno prikazana.

Page 126: 68261077 Java i Internet Programiranje1

121

9.6 Definisanje sopstvenih tagova Iz do sada izloženog vidi se da se praktično sva dinamika u JSP stranicama opisuje skriptletima. Korišćenje skriptleta donosi veliku fleksibilnost, ali nije jednostavno za autora koji nije vičan Java i servlet programiranju. Viši nivo upotrebljivosti JSP tehnologije postiže se korišćenjem biblioteka dodatnih tagova koji donose novu funkcionalnost.

Sledeći primer prikazuje upotrebu tagova koji imaju istu funkcionalnost kao i klasična 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 uključiti u stranicu odgovarajućom 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 počinjati u okviru tekuće stranice – u primeru je to string mytags.

Tag if predstavlja okvir celokupne konstrukcije. On obuhvata tri taga: condition, then i else. Sadržaj condition taga je Java logički izraz. Sadržaj then taga će biti uključen u stranicu ako je vrednost datog logičkog izraza true. U suprotnom će u stranicu biti uključen sadržaj taga else.

Drugi primer korišćenja dodatnih tagova sadrži tagove koji omogućavaju postavljanje upita u bazi podataka i preuzimanje rezultata. Ispis rezultata se može formatirati na proizvoljan način. <%@ 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>

Page 127: 68261077 Java i Internet Programiranje1

122

Biblioteka u prethodnom primeru ima identifikator sqltags, a njeni tagovi u ovoj stranici biće korišćeni sa prefiksom sql. Tag SQLQuery obuhvata celu konstrukciju. Njegov atribut sqlQuery sadrži SQL naredbu kojom se postavlja upit. Ovaj tag sadrži podtagove empty, outputStart, outputEnd i output. Sadržaj taga empty biće uključen u stranicu u slučaju da rezultat upita ne sadrži nijedan red. Sadržaj taga outputStart biće uključen u stranicu ako rezultat sadrži bar jedan red, i to pre svih ostalih tagova. Analogno njemu, outputEnd biće uključen nakon svih ostalih tagova. Tag output biće uključen onoliko puta koliko ima redova u rezultatu upita. U okviru njega može se pristupiti parametru resultRow (poziv pageContext.getAttribute) koji sadrži tekući red rezultata upita. U pitanju je objekat klase Hashtable koji sadrži mapiranje [naziv kolone rezultata, njena vrednost].

Pisanje biblioteka dodatnih tagova je, u principu, najkomplikovaniji posao vezan za razvoj JSP stranica. Dodatni tagovi se definišu pomoću odgovarajućih Java klasa. Prilog na kraju praktikuma sadrži programski kod klasa koje implementiraju ovde prikazane tagove. Za detalje o pisanju novih JSP tagova treba konsultovati referentnu literaturu.

Page 128: 68261077 Java i Internet Programiranje1

123

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 može da “postoji” na nekom računaru i da drugi programi, odnosno objekti, koji se izvršavaju na drugim računarima mogu da mu pristupaju. Pristup tom objektu zapravo znači mogućnost pozivanja metoda i pristupa njegovim atributima.

Pristup tom “serverskom” objektu bi, sa strane ostalih učesnika (“klijenata”), trebalo da bude što jednostavniji. U idealnom slučaju 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 izvršavanje te metode na onom računaru na kome se nalazi sam objekat. To znači da se klijentski program inicijalno izvršava na više računara – inicijalno na onom na kome je pokrenut, ali i na svim računarima na kojima se nalaze serverski objekti koje on koristi. Slika 10.1 ilustruje situaciju kada klijentski program pozove metodu serverskog objekta.

klijent serverskiobjekat

1) poziv metode

2) izvršavanje metode

3) rezultat Slika 10.1. Poziv metode serverskog objekta

Ovakva komunikacija može 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 mrežne i druge servise koji su neophodni za ovakav serverski objekat. Slika 10.2 prikazuje odnos klijentskog i serverskog objekta.

Page 129: 68261077 Java i Internet Programiranje1

124

kontejnerklijent aplikacija

klijent serverskiobjekat

Slika 10.2. Klijentski i serverski objekat u okviru svojih programa

Posmatrano sa stanovišta autora klijentske aplikacije, i klijentski i serverski objekat su sastavni deo jednog programa. Osobina tog programa je da se izvršava na više računara u mreži. Serverski program, logički posmatrano, i ne postoji; postoji samo serverski objekat koji se ponaša/koristi kao da je deo klijentskog (jedinog) programa.

10.2 RMI Remote Method Invocation (RMI) je osnovna Java tehnologija za pisanje programa sa distribuiranim objektima. RMI polazi od toga da klijentski objekat ne “vidi” serverski objekat, nego samo interfejs koga ovaj implementira (slika 10.3). Ovaj interfejs se često naziva RMI interfejs.

klijentobjekat

serverobjekat

inte

rfejs

Slika 10.3. Pristup RMI serverskom objektu preko interfejsa

Komunikacija između klijentskog i serverskog programa odvija se po JRMP (Java Remote Method Protocol) protokolu. Za implementaciju ovog protokola zadužene su posebne, automatski generisane, klase: tzv. stub na klijentu i skeleton na serveru. Slika 10.4 prikazuje stvarne učesnike u RMI komunikaciji.

klijentobjekat

RMIinterfejs

clientstub

serverskeleton

RMIobjekat

JVM 1 JVM 2

Slika 10.4. Učesnici 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 upućuje klijentski objekat prosledi preko mreže. Na serverskoj strani skeleton klasa ima ekvivalentnu funkciju: da pozive pristigle preko mreže pretvori u pozive stvarnog serverskog objekta. Stub i skeleton klase se generišu posebnim alatom iz JDK paketa, rmic kompajlerom.

10.2.1 Faze u pisanju RMI programa Pisanje RMI programa se može podeliti u nekoliko faza:

1. pisanje RMI interfejsa 2. pisanje RMI serverskog objekta 3. pisanje RMI klijenta

Page 130: 68261077 Java i Internet Programiranje1

125

4. generisanje stub i skeleton klasa

Slika 10.5 prikazuje međusobni odnos pojedinih faza u pisanju RMI programa.

Definisanje RMI interfejsa

Implementiranje RMIinterfejsa - definisanje RMI

klase

javac

rmic

Implementiranje klijentskeklase

javac

(.java)

(.java)

(.class)(.java)

klijent(.class)

(.class)

client stub(.class)

server skeleton(.class)

RMI objekat(.class)

RMI interfejs(.class)

(.class)(.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:

• nasleđuje 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 način kao da se nalazi na istom računaru, postoji jedna vrlo važna razlika između pozivanja metoda lokalnih i RMI objekata: kod lokalnih objekata parametri metoda koji su objekti (a ne primitivni tipovi) se ponašaju kao da se prenose po adresi, dok se kod poziva metoda RMI objekata svi parametri prenose po vrednosti. U prethodnom primeru to znači i da se parametar x prenosi po vrednosti. Takođe se i rezultat metode prenosi po vrednosti.

Da bi se objekat-parametar mogao preneti po vrednosti, mora postojati način da se njegov sadržaj prosledi serverskom programu. U pitanju je postupak serijalizacije objekta: na osnovu njegovog sadržaja generiše se stream bajtova koji se može preneti preko mreže i tamo ponovo restaurirati u identičnu kopiju objekta. Slika 10.6 prikazuje proces serijalizacije i deserijalizacije objekta.

Page 131: 68261077 Java i Internet Programiranje1

126

JVM 2JVM 1

objekat bytestreamserijalizacija byte

streamkopija

objektadeserijalizacija

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 definiše nijednu metodu! Sam proces serijalizacije će, u ovom slučaju, 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 sledeće uslove:

• implementira svoj RMI interfejs • nasleđuje 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. Međutim, da bi se serverski objekat mogao koristiti, potrebno ga je registrovati pod nekim imenom kod servera koji služi kao katalog ovakvih objekata. Klijenti koji žele da pristupaju nekom serverskom objektu će od ovog servera zahtevati referencu na odgovarajući objekat putem njegovog imena pod kojim je registrovan.

Serverski program koji obezbeđuje usluge registrovanja serverskih objekata naziva se RMI registry. Ovaj program mora biti aktivan na nekom računaru da bi pristup serverskom objektu bio moguć. Program se pokreće komandom rmiregistry

sa opcionim parametrom koji predstavlja TCP port na kome će očekivati klijente. Podrazumevani port koji se koristi je 1099.

Zadatak serverskog objekta je da se registruje na odgovarajući način kod RMI registry-ja. To se tipično obavi unutar metode main serverskog objekta. Sledi primer takve metode za klasu ServerImpl iz prethodnog primera: public static void main(String[] args) {

Page 132: 68261077 Java i Internet Programiranje1

127

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 sluša na portu 1099.

RMI registry se može pokrenuti, umesto iz komandne linije, i iz Java programa, sledećim iskazom: LocateRegistry.createRegistry(1099);

Parametar metode createRegistry je port na kome će slušati server.

10.2.5 RMI klijent RMI klijent, da bi pristupio serverskom objektu mora da uradi sledeće dve pripremne radnje:

1. postavi novi security manager za tekući Java program: System.setSecurityManager(new RMISecurityManager());

2. pronađe 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 može obraćati serverskom objektu na uobičajen način: server.metoda(...).

10.2.6 Primer RMI programa Ovaj odeljak sadrži 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 tekući broj vraća 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 java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*; import java.net.*; public class Server extends UnicastRemoteObject implements ServerI {

Page 133: 68261077 Java i Internet Programiranje1

128

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 pokreće na sledeći način:

1. prevedu se svi izvorni Java fajlovi: javac *.java

2. generišu se stub i skeleton klase: rmic Server

3. startuje se RMI registry: start rmiregistry (Win32) rmiregistry & (Unix)

Page 134: 68261077 Java i Internet Programiranje1

129

4. startuje se server: java Server

5. startuje se klijent: java Client

10.2.7 RMI i multithreading Pokretanjem klijenta iz prethodnog više puta vidi se da će više klijenata deliti istu instancu serverskog objekta (svaki novi klijent će ispisati novu – inkremen-tiranu – vrednost rezultata). Ovo ponašanje 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 brojača koje ponovo počinju od 1. U pitanju je nova instanca serverskog objekta koja se registrovala pod “starim imenom”.

Kako svi klijenti dele istu instancu serverskog objekta (kada zatraže objekat registrovan pod istim imenom), istovremeni pozivi metoda serverskog objekta biće, u okviru serverskog programa, obrađeni kroz više niti, po jedna za svaki takav poziv metode. Pozivanje metoda serverskog objekta iz više 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 zamišljena tako da bude nezavisna od korišćenog programskog jezika (moguća je komunikacija između objekata pisanih u različitim jezicima). Pored toga, CORBA je nezavisna i od konkretnog proizvođača softvera: ona zapravo predstavlja standard OMG (Object Mana-gement Group) grupe. Proizvođači softvera mogu da nude svoje implementacije zajedničkog standarda. Ujedno je i najsloženija tehnologija koja se bavi distribuiranim objektima, jer podržava najširi spektar zahteva u okviru ovog domena.

Nešto manja dobra osobina CORBA je što je u pitanju standard, koji se sporo usvaja. Zbog složenosti i velikog broja mogućnosti relativno je komplikovana za učenje. U ranijim verzijama CORBA standarda moguća je bila i nekompa-tibilnost između proizvoda različitih proizvođača.

Polazna ideja je identična kao i kod RMI tehnologije: klijentski objekat poziva metode serverskog objekta na isti način kao da je u pitanju lokalni objekat. (U CORBA terminologiji serverski objekat se naziva servant).

Klijent i servant međusobno ne komuniciraju direktno, već preko posrednika nazvanih ORB (Object Request Broker). ORB je softverski modul koji je namenjen za mrežnu komunikaciju. ORB-ovi između sebe komuniciraju po IIOP (Internet

Page 135: 68261077 Java i Internet Programiranje1

130

Inter-ORB Protocol). Slika 10.7 prikazuje komunikaciju između klijenta i servanta posredstvom ORB-ova.

klijent servant

ORB ORB IIOP

Slika 10.7. Komunikacija klijenta i servanta preko ORB-ova

10.3.2 IDL Kako CORBA omogućava korišćenje objekata pisanih u različitim programskim jezicima, mora postojati univerzalan način za specificiranje interfejsa serverskih objekata kojima klijenti mogu da pristupe. Za tu namenu CORBA definiše svoj Interface Definition Language (IDL) jezik.

Na osnovu specifikacije interfejsa u IDL jeziku odgovarajućim alatom se generiše 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; slično važi i za metode i atribute. Sledi primer jednog IDL interfejsa i odgovarajućeg 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 međusobni odnos klijent, servant, stub, skeleton i ORB modula.

klijent servant

ORB ORB IIOP

client stub serverskeleton

Slika 10.8. Međusobni odnos klijent, servant, stub i skeleton klasa

Page 136: 68261077 Java i Internet Programiranje1

131

10.3.3 CORBA Naming Service CORBA Naming Service je poseban CORBA servis koga definiše standard. Namenjen je za katalogiziranje servant objekata – dakle, ima funkciju sličnu RMI registry-ju. Registrovani objekti su organizovani u stablo, nalik direkto-rijumima i datotekama. U tom smislu, ovaj servis je nalik DNS servisu u TCP/IP mrežama. JDK paket sadži poseban program koji implementira ovaj servis (tnameserv). CORBA klijenti koriste ovaj servis za pronalaženje servant objekata.

Program tnameserv se pokreće kao u sledećem primeru: tnameserv –ORBInitialPort 900

Parametrom ORBInitialPort definiše se port na kome će ovaj server očekivati 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 generička biblioteka za pristup različitim 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 može se podeliti u nekoliko faza (slično kao kod RMI tehnologije):

1. pisanje IDL interfejsa 2. generisanje Java koda (idlj) 3. implementiranje servera 4. implementiranje servanta 5. implementiranje klijenta

Ovu proceduru ćemo prikazati na primeru. Definišimo prvo IDL interfejs (naziv fajla u kome se nalazi definicija nije važan, ekstenzija je obično .idl): counter.idl

module counter { interface Count { attribute long sum; long increment(); }; };

Interfejs sadrži jedan atribut, sum, i jednu metodu, increment. Sada treba generisati odgovarajući Java kod za dati IDL interfejs. Komanda kojom se to postiže glasi: idlj –fall counter.idl

Parametar –fall naglašava da se generišu klase i za klijent i za server. Rezultat izvršavanja je skup od šest Java izvornih fajlova koji se nalaze u paketu counter (zato što IDL specifikacija navodi modul counter). Njihova namena je sledeća:

Page 137: 68261077 Java i Internet Programiranje1

132

• Count.java: predstavlja Java verziju datog interfejsa. Koristi se u okviru klijent aplikacije za pristup objektu.

• CountOperations.java: interfejs koji sadrži samo operacije navedene u IDL interfejsu. Njega nasleđuje Count interfejs i dodaje mu CORBA mogućnosti.

• _CountStub.java: klijentski stub koji implementira Count interfejs i definiše njegove CORBA funkcije.

• CountHelper.java: implementira dodatne funkcije, pre svega metodu narrow koja se koristi umesto klasičnog kastovanja.

• CountHolder.java: obezbeđuje manipulaciju parametrima metoda, u slučajevima za out i inout argumente.

• _CountImplBase.java: Klasa koju treba da nasledi servant klasa. Imple-mentira 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

CountHelper.java

CountHolder.java

_CountImplBase

.javaCount.java

CountOperations.

java

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

Page 138: 68261077 Java i Internet Programiranje1

133

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 klasičnog 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 beskonačno 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 klasičnog kastovanja, ovde se poziva narrow NamingContext ncRef = NamingContextHelper.narrow(objRef);

Page 139: 68261077 Java i Internet Programiranje1

134

// definiši name component NameComponent nc = new NameComponent("Counter", ""); NameComponent path[] = {nc}; // pronađi objekat pod nazivom "counter" i // kastuj ga pomoću 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, može da se pokrene i klijent: java CounterClient -ORBInitialHost localhost -ORBInitialPort 900

Parametri koji se navode u komandnoj liniji se zapravo prosleđuju ORB-u prilikom njegove inicijalizacije. Parametri predstavljaju adresu računara i port na kome se izvršava Naming Service.

10.3.5 CORBA izuzeci IDL interfejs može da sadrži 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 definiše novi tip izuzetka – badRead. Metoda read ovog interfejsa može da izazove taj izuzetak. IDL izuzetak badRead je predstavljen klasom reader.ReaderPackage.badRead. Klase iz ovog paketa su takođe dobijene upotre-bom idlj alata. Slika 10.10 prikazuje lokaciju dodatnik klasa vezanih za izuzetak badRead.

Page 140: 68261077 Java i Internet Programiranje1

135

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 nećemo navoditi. Klasa ReaderClient pristupa servant objektu na isti način 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 odgovarajućeg 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(); } } }

Page 141: 68261077 Java i Internet Programiranje1

136

10.3.6 Pozivi unatrag Poziv unatrag (callback) se koristi kada je potrebno da server pošalje poruku klijentu. Da bi to bilo moguće, i klijent i server moraju imati servant objekte koji će biti pozivani. To bi značilo da bi i klijent i server bili jednaki po svojoj konstrukciji. Međutim, 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 način.

Pogledajmo primer sledećeg 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 sadrži 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 sledeće dve klase:

CallbackServer je osnovna serverska klasa – registruje servanta i čeka na pozive. Njen kod se suštinski ne razlikuje od sličnih klasa iz prethodnih primera, pa ga ovde nećemo navoditi.

ServerServant implementira serverskog servanta: definiše 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 takođe 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";

Page 142: 68261077 Java i Internet Programiranje1

137

} }

CallbackClient je klasa koja na uobičajen način pronalazi serverskog servanta koristeći naming service i poziva njegovu metodu sendMessage. Nema potrebe ovde je navoditi jer je skoro jednaka odgovarajućim klasama iz prethodnih primera.

Sekvenca događaja prilikom pokretanja ovog primera je sledeća:

1. server registruje svog servanta 2. klijent traži serverskog servanta po imenu 3. klijent poziva serverskog servanta i šalje mu referencu na svog servanta 4. serverski servant poziva klijentskog servanta

Slika 10.11 ilustruje ovu sekvencu događaja.

server appclient app

clientservant

serverservant

namingservice

1)2)

3)

4)

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 mogućnosti kooperacije ovih dvaju tehnologija, odnosno da li je moguće iz RMI klijenata pristupati CORBA serverima i obrnuto. Ovako kako su opisani, saradnja RMI i CORBA programa nije moguća. Naime, RMI i CORBA koriste različite (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 mrežnu komunikaciju. Korisnik tu novinu vidi kao prošireni RMI kompajler rmic koji sada može da generiše stub i skeleton klase za IIOP i IDL interfejs za dati RMI interfejs. Na taj način je moguće pristupati CORBA serverima iz RMI klijenata. Pri tome se, umesto RMI registry-ja, mora koristiti CORBA Naming Service. Rad u ovakvom okruženju, zapravo, obezbeđuje “presek skupova mogućnosti” dvaju tehnologija.

10.4 Enterprise JavaBeans Enterprise JavaBeans (EJB) je specifikacija koju definiše Sun Microsystems. Za razliku od RMI tehnologije čija implementacija je sastavni deo Java jezika, EJB specifikacija ne propisuje nijednu određenu implementaciju. Različiti proizvo-đači mogu da proizvode svoje nezavisne implementacije. Specifikacija je dovolj-no precizno definisana tako da obezbedi da se softver koji koristi EJB tehno-

Page 143: 68261077 Java i Internet Programiranje1

138

logiju može instalirati u različita EJB okruženja bez modifikacije. EJB specifi-kacija je prošla kroz nekoliko verzija (1.0, 1.1, 2.0), gde je svaka od verzija dodavala nove mogućnosti. U pitanju je tehnologija koja se implementira isključivo u jeziku Java.

Sama EJB specifikacija definiše model serverskih komponenti za razvoj više-slojnih arhitektura sa distribuiranim objektima. Važno je primetiti da se EJB ne bavi klijentskom stranom. EJB definiše okruženje u kome “žive” EJB kompo-nente. Okruženje čine EJB server i EJB kontejner. U tekućoj verziji specifikacije funkcije EJB servera i EJB kontejnera nisu strogo razdvojene. U principu je zamišljeno da EJB server i kontejner budu nezavisni jedan od drugog što bi omogućilo izgradnju EJB okruženja kombinovanjem komponenti različitih proizvođača. Za sada to u praksi nije moguće, tako da svaki proizvođač nudi svoj par server/kontejner koji se ne može razdvojiti. Slika 10.12 prikazuje međusobni odnos EJB servera, kontejnera i komponenti.

EJB server

EJB kontejner

EJB komponenta

EJB komponenta

Slika 10.11. Međusobni odnos EJB servera, kontejnera i komponenti

Autor EJB komponenti ne mora da poznaje način funkcionisanja EJB servera i kontejnera u velikoj meri. Svi EJB serveri koji se drže specifikacije će raditi sa pravilno napisanim EJB komponentama.

EJB komponente su klasifikovani u nekoliko grupa, o kojima će u nastavku biti više reči:

• 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 izvršavaju za potrebe tačno jednog klijenta. Treba uočiti razliku između 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 određenom session bean komponentom, EJB server/kontejner će kreirati novu instancu kompo-nente i dodeliti je na upotrebu samo tom klijentu. Osnovna namena ovakvih komponenti je da implementiraju operacije koje operišu nad podacima sistema. U pitanju su, najčešće, operacije nad podacima u bazi podataka celo-kupnog sistema.

Page 144: 68261077 Java i Internet Programiranje1

139

Session beans su podeljeni u dve podvrste:

• Stateful session beans: komponente koje čuvaju svoje stanje između višestrukih poziva klijenta koji ih koristi. Možemo ih shvatiti kao objekte koji imaju svoje atribute čija vrednost se čuva između višestrukih poziva njihovih metoda.

• Stateless session beans: komponente koje ne čuvaju svoje stanje. U tom smislu, komponenta je omogućava klijentima da pozivaju njene metode, ali izvršavanje tih metoda ne utiče na stanje komponente, te su pozivi njenih metoda međusobno 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 podržavaju istovremeni pristup od strane više klije-nata. Namenjene su, pre svega za predstavljanje podataka u sistemu. Može se reći da su namenjene za implementaciju modela podataka objektno-orijenti-sanog sistema. Životni vek ovih komponenti je vezan za životni vek podataka koje predstavljaju. U tom smislu, ove komponente moraju imati mogućnost da “prežive” i prestanak rada servera iz bilo kog razloga, odnosno mogućnost da restauriraju svoje stanje iz neke baze podataka sa trajnim skladištenjem podataka.

Entity beans komponente se dele u dve podvrste, zavisno od načina kako se obezbeđuje njihova trajnost:

• Bean-managed persistence: komponenta sama implementira operacije koje obezbeđuju trajno čuvanje podataka koje ona sadrži. Najčešće se za tu namenu koristi baza podataka kojoj se pristupa preko JDBC interfejsa. Ove operacije inicira EJB kontejner u odgovarajućim trenucima.

• Container-managed persistence: operacije koje obezbeđuju čuvanje podataka koje komponenta sadrži implementira EJB kontejner, koji se sam brine o njihovom iniciranju u odgovarajućim trenucima. Ovo je (za sada) retka osobina među postojećim EJB serverima, a postala je obavezna u verziji 2.0 EJB specifikacije.

S obzirom na ove osobine session i entity beans komponenti, generalna preporu-ka je da klijenti direktno komuniciraju samo sa session beans komponentama, koje implementiraju potrebne operacije. Podaci kojima te operacije rukuju predstavljeni su odgovarajućim entity beans komponentama, koje za potrebe trajnog čuvanja svog stanja koriste bazu podataka. Slika 10.12 prikazuje ovaj međusobni odnos klijenata, session i entity bean komponenti i baze podataka u okviru EJB okruženja prema njihovoj zamišljenoj nameni.

Page 145: 68261077 Java i Internet Programiranje1

140

EJB server

EJB kontejner

sessionbean

sessionbean

entitybean

entitybean

klijent

klijent

DB

Slika 10.12. Međusobni odnos klijenata, session i entity bean komponenti i baze podataka

10.4.3 Komunikacija klijenata sa EJB komponentama EJB specifikacija ne insistira na korišćenju jedne tehnologije za komunikaciju klijenata sa EJB serverom. Specifikacija navodi kao obavezne podršku za komunikaciju putem RMI i RMI-over-IIOP tehnologija. Dakle, u startu je obezbeđena podrška za komunkaciju sa RMI i CORBA klijentima. Proizvođači konkretnih EJB servera mogu da dodaju podršku za dodatne protokole za komunikaciju, na primer Microsoft-ovu COM+. Slika 10.13 ilustruje mogućnost pristupa EJB komponentama od strane klijenata koji koriste različite tehnologije distribuiranih objekata.

EJB server

EJB kontejner

EJB komponenta

EJB komponenta

RMI klijent

CORBA klijent

COM+ klijent

Slika 10.13.Pristup EJB serveru korišćenjem različitih tehnologija

10.4.4 Struktura EJB komponente Pisanje EJB komponente obuhvata definisanje tri stvari:

• Bean klasa: implementira odgovarajuću komponentu u užem smislu • Home interfejs: interfejs koji definiše metode za kreiranje, pronalaženje i

uklanjanje komponente iz svog kontejnera • Remote interfejs: definiše metode koje su dostupne klijentima

Klijent komunicira sa komponentom isključivo preko njenih home i remote inter-fejsa. Međusobni odnos ovih delova EJB komponente prikazuje slika 10.14.

Page 146: 68261077 Java i Internet Programiranje1

141

EJB server

EJB kontejner

homeinterfejs

remoteinterfejs

beanklasa

klijent

Slika 10.14. Međusobni odnos delova EJB komponente

Remote interfejs je Java interfejs za koga važi:

• nasleđuje javax.ejb.EJBObject interfejs • sve metode koje definiše 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 važi:

• nasleđuje javax.ejb.EJBHome interfejs • definiše create metode za kreiranje komponente (tip rezultata koji vraćaju

te metode mora biti odgovarajući 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 važi:

• implementira javax.ejb.SessionBean ili javax.ejb.EntityBean interfejs, zavisno od vrste komponente

• ne implementira ni svoj home ni svoj remote interfejs! • mora da sadrži implementacije svih metoda iz svog remote interfejsa

(mada ne postoji formalno jezičko ograničenje koje bi autora komponente primoralo na to)

• za svaku metodu create svog home interfejsa definiše metodu ejbCreate koja se slaže po parametrima sa odgovarajućom metodom create

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

Page 147: 68261077 Java i Internet Programiranje1

142

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, praktično ne razlikuje od klasičnog RMI klijenta. Jedino ostaje izbor načina 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");

Page 148: 68261077 Java i Internet Programiranje1

143

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

Page 149: 68261077 Java i Internet Programiranje1

144

Poglavlje 11

Vežba: Web shop aplikacija

Zadatak. Potrebno je implementirati Web aplikaciju koja predstavlja sajt za kupovinu prozvoda. Sistem funkcioniše kao posrednik između kupaca (koji posećuju Web sajt radi kupovine) i dobavljača. Sistem čine dva odvojena sajta:

• Front office: sajt koji je dostupan svim korisnicima; omogućava pregle-danje kataloga proizvoda i njihovo naručivanje.

• Back office: sajt koji je dostupan samo administratorima sistema; omogu-ćava izvršavanje administrativnih funkcija.

Front office funkcije su sledeće:

• Registracija i autentifikacija korisnika • Pregled ponuđenih proizvoda, organizovanih hijerarhijski u kategorije • Izbor proizvoda za kupovinu i njihovo naručivanje (“shopping cart/

potrošačka korpa” sistem)

Back office funkcije su sledeće:

• Registracija i autentifikacija korisnika • Ažuriranje baze dobavljača, proizvoda i kategorija

Arhitektura sistema je prikazana na slici 11.1. Klijenti u sistemu su, zapravo, Web čitači. Dinamički sadržaj kojeg oni prikazuju generišu Web serveri pomoću servleta i JSP stranica. Za potrebe trajnog skladištenja podataka koristi se baza podataka.

HTML ServletsJSP

DB

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

Page 150: 68261077 Java i Internet Programiranje1

145

odgovara Oracle SUBP je dat na slici 11.3. Ovaj model podataka dele front office i back office aplikacije.

Is OrderedContains ItemsPlaces

Supplies

Has Images

Is Parent To

Contains Products

UserUser IDUsernamePaswordFirst NameLast NameUser AddressEmailReceive news

ProductProduct IDProduct NameVendorDescriptionPrice

CategoryCategory IDCategory NameCategory Desc

Product ImageImage IDImage TitleImage HeightImage WidthContent TypeImage Data

SupplierSupplier IDSupplier NameSupplier Address

OrderOrder IDOrder Date

Ordered ItemQuantity

AdminAdmin IDUsernamePasword

Conceptual Data Model

Project : Web ShopModel : Web ShopAuthor : Branko Milosavljevic Version 1.0 11.4.2001

Slika 11.2. ER model

PRODUCT_ID = PRODUCT_IDORDER_ID = ORDER_ID

USER_ID = USER_ID

SUPPLIER_ID = SUPPLIER_ID

PRODUCT_ID = PRODUCT_ID

CATEGORY_ID = PARENT_CATEGORY_ID

CATEGORY_ID = CATEGORY_ID

USERSUSER_ID INTEGER not nullUSERNAME VARCHAR2(20) not nullPASWORD VARCHAR2(20) not nullFIRST_NAME VARCHAR2(25) not nullLAST_NAME VARCHAR2(35) not nullUSER_ADDRESS VARCHAR2(100) not nullEMAIL VARCHAR2(50) not nullRECEIVE_NEWS NUMBER(1) not null

PRODUCTSPRODUCT_ID INTEGER not nullCATEGORY_ID INTEGER not nullSUPPLIER_ID INTEGER not nullPRODUCT_NAME VARCHAR2(100) not nullVENDOR VARCHAR2(100) not nullDESCRIPTION VARCHAR2(1000) not nullPRICE DECIMAL(9,2) not null

CATEGORIESCATEGORY_ID INTEGER not nullPARENT_CATEGORY_ID INTEGER nullCATEGORY_NAME VARCHAR2(50) not nullCATEGORY_DESC VARCHAR2(500) null

PRODUCT_IMAGESPRODUCT_ID INTEGER not nullIMAGE_ID INTEGER not nullIMAGE_TITLE VARCHAR2(50) nullIMAGE_HEIGHT INTEGER not nullIMAGE_WIDTH INTEGER not nullCONTENT_TYPE VARCHAR2(30) not nullIMAGE_DATA LONG RAW null

SUPPLIERSSUPPLIER_ID INTEGER not nullSUPPLIER_NAME VARCHAR2(100) not nullSUPPLIER_ADDRESS VARCHAR2(100) not null

ORDERSORDER_ID INTEGER not nullUSER_ID INTEGER not nullORDER_DATE DATE not null

ORDERED_ITEMSORDER_ID INTEGER not nullPRODUCT_ID INTEGER not nullQUANTITY INTEGER not null

ADMINSADMIN_ID INTEGER not nullUSERNAME VARCHAR2(20) not nullPASWORD VARCHAR2(20) not null Physical Data Model

Project : Web ShopModel : Web ShopAuthor : 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 pred-stavlja vezu između kategorije-roditelja i potkategorije (deteta). Entitet Product predstavlja proizvod, koji pripada određenoj kategoriji, ima svog dobavljača (Supplier), i ima nula ili više svojih slika (Product Image). Korisnik može da kreira narudžbe (Order) koje se sastoje iz više stavki (Ordered Item). Svaka stavka predstavlja određeni proizvod u nekoj količini. Entitet Admin predstavlja admi-nistratora (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

Page 151: 68261077 Java i Internet Programiranje1

146

predstavljaju strukturu front office i back office sajta. (Pod strukturom sajta podrazumevamo graf koji opisuje moguće kretanje između stranica sajta).

index

register productcategory

purchase

login

submit

login successful

fromPurchase oldPurchase

submit

submit

Slika 11.4. Struktura front office sajta

Stranica index.jsp je početna stranica kojoj se pristupa. Stranica register.jsp omogućava registraciju novih korisnika (koju oni obavljaju samostalno). Na njoj se nalazi forma za unos podataka o novom korisniku. Nakon registracije korisnik se upućuje na osnovnu stranicu i automatski je prijavljen za rad. Stranica login.jsp je namenjena za prijavljivanje postojećih korisnika. Na nju se može stići klikom na odgovarajući link na osnovnoj stranici. Po uspešnom prijavljivanju korisnik se upućuje na osnovnu stranicu. Ukoliko je prijavljivanje neuspešno, korisnik ostaje na login.jsp stranici. U okviru index.jsp stranice postoje linkovi na kategorije prvog nivoa, za čije prikazivanje je zadužena stranica category.jsp. Stranica product.jsp je namenjena za prikazivanje podataka o konkretnom proizvodu. Pored toga, ova stranica omogućava dodavanje trenutno prikazanog proizvoda u “potrošačku korpu” (u datoj količini). Ova funkcija se obavlja pomoću forme koja upućuje korisnika na istu product.jsp stranicu. Stranica purchase.jsp omogućava potvrdu ili opoziv narudžbe, kao i linkove na oldPurchase.jsp stranicu namenjenu za pregled starih narudžbi.

U okviru back office sajta, stranica index.jsp je početna stranica kojoj se pristupa. Za sve stranice, pa i za ovu, važi 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 dobavljača, nove kategorije, novog proizvoda i nove slike (vezane za postojeći 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.

Page 152: 68261077 Java i Internet Programiranje1

147

indexregister

suppliers categories

products

addPicturelogin

submit submit

submit

savePicturesubmit

redirect

Slika 11.5. Struktura back office sajta

Tabela 11.1 sadrži listu stranica front office sajta sa njihovim parametrima. Stranica Parametar Značenje index.jsp category.jsp catID ID kategorije u bazi podataka

catID ID kategorije kojoj proizvod pripada prodID ID proizvoda u bazi podataka

product.jsp

quantity količina naručenog proizvoda; definisan je prilikom ulaska u stranicu putem forme za dodavanje proizvoda u korpu

purchase definisan kada je korisnik potvrdio porudžbinu purchase.jsp cancel definisan kada je korisnik opozvao porudžbinu

oldPurchase.jsp orderID ID porudžbine koju treba prikazati username uneto korisničko ime password uneta lozinka

login.jsp

fromPurchase ima vrednost yes kada se u stranicu ulazi sa purchase.jsp username korisničko ime novog korisnika password lozinka firstName ime korisnika lastName prezime korisnika address adresa korisnika email email adresa korisnika

register.jsp

receiveNews oznaka da korisnik želi da prima obaveštenja sa sajta Tabela 11.1. Parametri stranica front office sajta

Tabela 11.2 sadrži opis parametara stranica back office sajta. Stranica Parametar Značenje index.jsp

name Naziv nove kategorije desc Opis nove kategorije

categories.jsp

parent ID roditeljske kategorije; 0 ako nova kategorija nema roditelja name Naziv novog proizvoda desc Opis novog proizvoda vendor Naziv proizvođača category ID kategorije kojoj proizvod pripada supplier ID dobavljača

products.jsp

price prodajna cena proizvoda name Naziv novog dobavljača suppliers.jsp address Adresa novog dobavljača

addPicture.jsp savePicture.jsp productID ID proizvoda kome se dodaje slika

Page 153: 68261077 Java i Internet Programiranje1

148

height visina slike u pikselima width širina slike u pikselima title naziv slike

uploaded slika koja se dodaje username korisničko ime administratora koji se prijavljuje login.jsp password lozinka administratora koji se prijavljuje username korisničko ime administratora koji se registruje register.jsp password 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.

+register() : void+login() : void

-adminID : int = 0-username : String = ""-password : String = ""

AdminUser

+add() : void

-categoryID : int = 0-name : String = ""-desc : String = ""

Category

+add() : void

-productID : int = 0-name : String = ""-vendor : String = ""-desc : String = ""

Product

+register() : void+login() : void

-userID : int = 0-username : String = ""-password : String = ""-firstName : String = ""-lastName : String = ""-address : String = ""-email : String = ""-receiveNews : Boolean = false

User

1

*

+add() : void

-orderID : int = 0-orderDate : Date

Order

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

Item

+add() : void

-supplierID : int = 0-name : String = ""-address : String = ""

Supplier

*

1*

1

1

*

1 *

+add() : void

-imageID : int = 0-title : String = ""-height : int = 0-width : int = 0-contentType : String = "image/gif"-data : Byte

ProdImage

1

*

1

*

Slika 11.6. Dijagram klasa sistema

Prikazane klase se praktično direktno poklapaju sa entitetima u modelu poda-taka sistema.

11.4 Dodatna razmatranja

11.4.1 Rukovanje konekcijama sa bazom podataka U odeljku 8.9 bilo je reči o pristupanju bazi podataka iz servleta. Dva moguća rešenja su tamo pomenuta. Jedna mogućnost je bila da se konekcija sa bazom podataka otvori prilikom inicijalizacije servleta, i da se zatvori prilikom uništa-vanja servleta. Obrada HTTP zahteva svih klijenata koristi istu konekciju za pristup bazi podataka. Ovakvo rešenje može da se upotrebi samo u slučaju da svi pristupi bazi podataka obuhvataju samo upite; operacije koje modifikuju podatke u bazi ne mogu koristiti konekciju na ovakav način.

Page 154: 68261077 Java i Internet Programiranje1

149

Druga mogućnost je da se prilikom obrade svakog HTTP zahteva otvara i zatvara posebna konekcija. Ovakvo rešenje nije zadovoljavajuće sa stanovišta performansi, jer je otvaranje konekcije sa bazom podataka dugotrajna operacija.

Uobičajen način za rešavanje ovog problema je korišćenje skupa raspoloživih konekcija (connection pool). Zadatak ovog skupa je da upravlja konekcijama tako da njegovi korisnici u najvećem broju slučajeva dobijaju unapred otvorenu konekciju kada je zatraže. Nad skupom konekcija definisane su dve operacije – preuzimanje konekcije iz skupa (radi njene upotrebe) i vraćanje konekcije u skup (nakon korišćenja).

Ovde je predstavljen pool koji rukuje konekcijama na sledeći način:

• Prilikom svoje inicijalizacije unapred otvori određen broj konekcija i smesti ih u skup slobodnih konekcija

• Na zahtev za dobijanje konekcije, izdvaja prvu konekciju iz skupa slo-bodnih konekcija, smešta je u skup zauzetih konekcija i vraća korisniku; ukoliko nema slobodnih konekcija otvara novu konekciju, osim u slučaju da je dostignuta granica maksimalnog broja otvorenih konekcija kada čeka na oslobađanje neke konekcije

• Na zahtev za vraćanje konekcije, premešta konekciju iz skupa zauzetih konekcija u skup slobodnh konekcija. Ukoliko je broj slobodnih konekcija veći od nekog unapred određenog broja, zatvara potreban broj konekcija i uklanja ih iz skupa.

Pored toga potrebno je obezbediti da svi korisnici pristupaju istoj instanci pool-a. To se može obezbediti na taj način što će klasa ConnectionPool koja predstavlja pool sadržati statički atribut klase ConnectionPool. Taj atribut je, zapravo, jedina instanca klase koja će biti kreirana. Ova instanca se inicijalizuje u okviru odgovarajućeg static bloka. Klasa ne sadrži nijedan javni konstruktor, kako bi se onemogućilo kreiranje objekata ove klase. Posebna metoda getConnectionPool vraća statički atribut klase. Jedini način da korisnici dobiju objekat ove klase je pomoću 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 [email protected] * * @version 1.1 */ public class ConnectionPool {

Page 155: 68261077 Java i Internet Programiranje1

150

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

Page 156: 68261077 Java i Internet Programiranje1

151

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

Page 157: 68261077 Java i Internet Programiranje1

152

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

Page 158: 68261077 Java i Internet Programiranje1

153

Literatura

Korišćena literatura u okviru ovog praktikuma nije posebno referencirana. U ovom odeljku je dat spisak korišćene literature grupisan po tematskim celinama. Pored toga, Web sajt koji prati kurs sadrži veći deo ovih materijala.

Programski jezik Java • Arnold K., Gosling J., Holmes D., Programski jezik Java, treće 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

Page 159: 68261077 Java i Internet Programiranje1

154

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.

Page 160: 68261077 Java i Internet Programiranje1

155

Prilozi

Vežba: chat aplikacija Rešenje 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 java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; import 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();

Page 161: 68261077 Java i Internet Programiranje1

156

} }); 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); } /** Vrši 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 uspešno, * 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();

Page 162: 68261077 Java i Internet Programiranje1

157

} public synchronized String getMessage() { try { wait(); } catch (Exception ex) { } return message; } private String message; } chat\client\LoginDlg.java

package chat.client; import java.awt.*; import java.awt.event.*; import javax.swing.*; import 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); } /** Vraća uneto korisničko ime. */ public String getUsername() { return tfUsername.getText().trim(); } /** Vraća 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;

Page 163: 68261077 Java i Internet Programiranje1

158

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 Socket sock; private BufferedReader in; private ChatData chatData; private 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;

Page 164: 68261077 Java i Internet Programiranje1

159

import java.awt.*; import java.awt.event.*; import javax.swing.*; /** Osnovna klasa servera: pokreće 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();

Page 165: 68261077 Java i Internet Programiranje1

160

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

Page 166: 68261077 Java i Internet Programiranje1

161

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 {

Page 167: 68261077 Java i Internet Programiranje1

162

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 Socket sock; private BufferedReader in; private ActiveClient client; private JTextArea ta; }

Dodatni JSP tagovi korišćeni u toku kursa Slede klase koje implementiraju if-then-else tagove čije korišćenje je prikazano u poglavlju 9. tags\iftag\IfTag.java package tags.iftag; import java.io.*; import javax.servlet.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*;

Page 168: 68261077 Java i Internet Programiranje1

163

/** Osnovni tag koji služi 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 java.io.*; import javax.servlet.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** Tag koji služi 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 java.io.*; import javax.servlet.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** Tag koji služi 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; }

Page 169: 68261077 Java i Internet Programiranje1

164

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 java.io.*; import javax.servlet.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; /** Tag koji služi 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 korišće-nje 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:

Page 170: 68261077 Java i Internet Programiranje1

165

* <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 [email protected] * @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; }

Page 171: 68261077 Java i Internet Programiranje1

166

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

Page 172: 68261077 Java i Internet Programiranje1

167

* * @see SQLQueryTag * @see OutputEndTag * @see OutputTag * @see EmptyTag * @author Branko Milosavljevic [email protected] * @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. *

Page 173: 68261077 Java i Internet Programiranje1

168

* @see SQLQueryTag * @see OutputStartTag * @see OutputEndTag * @see EmptyTag * @author Branko Milosavljevic [email protected] * @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

Page 174: 68261077 Java i Internet Programiranje1

169

* @see OutputTag * @see EmptyTag * @author Branko Milosavljevic [email protected] * @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

Page 175: 68261077 Java i Internet Programiranje1

170

* @see SQLQueryTag * @author Branko Milosavljevic [email protected] * @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; }