dependency injection

45
UNIVERZITET « MEDITERAN » PODGORICA Fakultet za informacione tehnologije-Podgorica Veselin Raković Integracija softverskih komponenata pomoću Dependency Injection paterna ZAVRŠNI RAD Podgorica, 2011.

Upload: necr084

Post on 08-Aug-2015

88 views

Category:

Documents


3 download

DESCRIPTION

Dependency Injection paper

TRANSCRIPT

Page 1: Dependency Injection

UNIVERZITET « MEDITERAN » PODGORICAFakultet za informacione tehnologije-Podgorica

Veselin RakovićIntegracija softverskih komponenata pomoću Dependency Injection paterna

ZAVRŠNI RAD

Podgorica, 2011.

Page 2: Dependency Injection

UNIVERZITET « MEDITERAN » PODGORICAFakultet za informacione tehnologije - Podgorica

Integracija softverskih komponenata pomoću Dependency Injection paterna ZAVRŠNI RAD

Predmet: Projektovanje i razvoj softverskih aplikacijaMentor: doc. dr Dragan Đurić

Student: Veselin Raković Smjer: Softverski inženjering

Matični broj: 1302984220014

Podgorica, oktobar, 2011.

Page 3: Dependency Injection

1. Uvod

1.1. Dependency Injection

Poslovne aplikacije su kreirane sa ciljem da riješe određene probleme u poslovanju. Vremenom je tehnologija napredovala što je rezultovalo razvoju poslovnih aplikacija koje su rješavale veće i kompleksnije probleme. Zbog toga su i same aplikacije postale veće i kompleksnije. Povećanje kompleksnosti aplikacije je iznjedrilo nove probleme koje je trebalo riješiti da bi softverski inženjeri mogli da izađu u susret sve većim zahtjevima svijeta biznisa. Brojni su primjeri kada je bilo lakše izgraditi sistem od početka nego nadograditi i održavati postojeći. Rješenja koja su tada bila aktuelna nisu bila dovoljno dobra za razvoj kompleksnih aplikacija koje su lake za nadogradnju i održavanje. Zato su bila potrebna nova i bolja rješenja. Jedno od tih rješenja je Dependecy Injection mehanizam.

Korijeni Dependency Injectiona su vezani za programski jezik Java. Zapravo, Java je kolijevka Dependency Injectiona. Krajem devedesetih se pojavilo EJB standard koji je omogućavao pisanje složenih Java Enterprise aplikacija. Prihvaćen je zato što je nudio rješenja za probleme koja dotad nisu postajala – jedno od najvažnijih je automatska obrada transakcija. Međutim, nova rješenja su proizvela nove probleme. Da bi se iskoristile mogućnosti EJB tehnologije bilo je potrebno naslijediti pojedine klase ili implementirati određene interfejse. U tim uslovima je bilo teško pisati objektno orijentisan kod što je rezultovalo aplikacijama koje je teško testirati i održavati. Zbog toga su i nazvane “teške” (heavyweight) aplikacije. Rješenje za taj problem je Dependency Injection (DI) tj. frejmvorci koji automatski vrše Dependency Injection. Ukratko, frejmvork je softver koji radi određeni dio posla umjesto programera. Ispostavilo se da je Dependency Injection toliko dobro rješenje da je naknadno i EJB (verzija 3.0) počeo da ga primjenjuje. Jedan od prvih DI frejmvorka je bio Spring frejmvork koji je i dan danas veoma popularan. Zapravo Spring je dugo bio sinonim za Dependency Injection. Kreiran je od strane grupe programera na čelu sa Rod Johnoson-om.

Dependency Injection je omogućio pisanje enterprise aplikacija na objektno-orijentisan način (Plain Old Java Object - POJO programiranje). Umjesto glomaznih klasa sistem je moguće podijeliti na manje dijelove što najbolje ilustruju sledeće slike:

VS

Slika 1. Poređenje sistema sklopljenih od velikih i malih dijelova

Sistem sklopljen od većih dijelova, predstavljen na lijevoj slici, je teži za razumijevanje i održavanje. Dependency Injection je omogućio razvoj aplikacija koje se sastoje od manjih cjelina koje su, logično, razumljivije. Zbog toga su i nazvane lagane (lightweight) aplikacije. Pored toga Dependency Injection je učinio aplikaciju fleksibilnijom – njeni dijelovi su se lako mogli zamijeniti sa drugim, sličnim, dijelovima. [12]

Dependency Injection odnosno DI frejmvork nije rješenje za cjelokupan poslovni problem. On predstavlja veoma važnu kariku koja povezuje ostale ali ga nije moguće koristiti u izolaciji. Tada bi bio beskoristan.

1

Page 4: Dependency Injection

1.2. Poglavlja rada

Poslije uvodnog dijela i sadržaja slijedi poglavlje o osnovnim pojmovima koji se koriste u radu. Biće riječi o programskom jeziku Java, njegovoj istoriji i upotrebi. Biće objašnjeni pojmovi kao što su: softverska komponenta, moduli, servisi. Zatim će biti opisani paterni među koje spada i sam Dependency Injection. U poglavlju će takođe biti riječi o Dependency Injection-u, odnosno načinu na koji on funkcioniše u sprezi sa paradigmom “Programiranje preko interfejsa”, koja će takođe biti pojašnjena. Takođe će biti pojašnjen pojam Inversion of Control (Inverzija odgovornosti) koji se vezuje uz Dependency Injection. Zatim će detaljnije biti objašnjen pojam frejmvorka i biće opisani sledeći frejmvorci:

• Apache Tapestry – komponentno orijentisan frejmvork za razvoj web aplikacija. Interesantno je da Tapestry ima svoj Dependency Injection frejmvork (Tapestry IoC) kojem će biti posvećen dobar dio ovog rada.

• Spring – prvi i jedan od najpopularnijih Dependency Injection frejmvorka. Pored Dependency Injectiona Spring može obavljati i druge funkcije. Neke od njih će biti korišćene u četvrtom poglavlju.

• Hibernate – omogućava pamćenje podataka korišćenjem objektno relacionog mapiranja. Hibernate se može koristiti u sprezi sa Java Persistence API koji je takođe frejmvork koji koristi objektno relaciono mapiranje. Hibernate – JPA kombinacija će biti korišćena u praktičnim primjerima koji se nalaze u trećem poglavlju.

• TestNG – frejmvork za automatsko testiranje.

U poglavlju o osnovnim pojmovima je objašnjeno takođe šta je automatsko testiranje i koje vrste testova postoje, šta su transakcije i šta je perzistenicija.

Zatim slijedi četvrto poglavlje koje obuhvata najobimniji dio rada. U njemu su dati praktični primjeri iz aplikacije za demonstraciju Dependency Injectiona. kao i frejmvorka koji automatizuju Dependency Injection: Tapestry IoC i Spring. Aplikacija je implementirana pomoću sledećih tehnologija: Apache Tapestry 5.2.4, Spring 3.0.5, Hibernate 3.6.0 final i TestNG 5.13.1. Primjeri su sledeći:

• Prvi primjer se odnosi na stranicu koja ispisuje poruku o greški ukoliko korisnik nije dobro uradio određeni zadatak. Funkcionalnost je implementirana upotrebom Dependency Injection-a i Tapestry IoC-a a razmotren je i slučaj u kojem DI ne bi bio korišćen.

• Drugi primjer je znatno obimniji - sastoji se od nekoliko potprimjera. Oni obuhvataju kreiranje i konfigurisanje servisa za korisnike, preko Tapestry IoC-a. Biće pokazano da Dependency Injection omogućava laku zamjenu postojećeg korisničkog servisa sa kompleksnijim servisom (zbog veze sa bazom podataka) konfigurisanim u Spring frejmvorku i Tapestry IoC-u. Biće takođe prikazane mane i nedostaci njihovih alternativa – servisa koji ne koriste Dependency Injection.

• Treći primjer pokazuje kako se mogu koristiti Tapestry komponente u sprezi sa Tapestry IoC-om za zaštitu stranica od neovlašćenog pristupa. Prikazani su nedostaci rješenja su u kojem se ne koristi Dependecy Injection za zaštitu od neovlašćenog pristupa.

• U četvrtom primjeru su korišćene napredne mogućnosti Tapestry IoC i Spring frejmvorka za implementaciju Open Session In View filtera. Izvršeno je i poređenje sa primjerom u kojem se ne koristi Dependency Injection.

• U petom primjeru je prikazano kako je moguće uticati na Tapestry konfiguraciju preko contribute mehanizma po kojem se Tapestry IoC razlikuje od ostalih DI frejmvorka.

• Na kraju četvrtog poglavlja je dat primjer Late Binding-a kojeg omogućava Dependency Injection.

Na kraju rada se nalazi zaključak, popis slika, kao i spisak literature koja je korišćena za pisanje ovog rada.

2

Page 5: Dependency Injection

2. Sadržaj

1 Uvod……………………………………………………………...........................................................................................1

1.1 Dependency Injection..........................................................................................................................................1

1.2 Poglavlja rada.....................................................................................................................................................2

2 Sadržaj……………………………………………….........................................................................................................3

3 Osnovni pojmovi………………………………………………………...............................................................................5

3.1 Java......................................................................................................................................................................5

3.2 Softverske komponente i servisi...........................................................................................................................6

3.3 Moduli..................................................................................................................................................................6

3.4 Softverski paterni.................................................................................................................................................6

3.4.1 Dizajn i arhitekturalni paterni....................................................................................................................6

3.4.2 MVC – Model-View-Controll patern..........................................................................................................6

3.4.3 Dependency Injection.................................................................................................................................7

3.5 Programiranje preko interfejsa...........................................................................................................................8

3.6 Automatsko testiranje softvera............................................................................................................................9

3.6.1 Unit testiranje.............................................................................................................................................9

3.6.2 Integracioni testovi.....................................................................................................................................9

3.6.3 Funkcionalni testovi....................................................................................................................................9

3.6.4 Testovi prihvatljivosti...................................................................................................................................9

3.6.5 Testovi performansi....................................................................................................................................10

3.7 Frejmvorci..........................................................................................................................................................10

3.7.1 Spring frejmvork........................................................................................................................................14

3.6.2 Apache Tapestry frejmvork.........................................................................................................................11

3.7.3 ORM frejmvorci – Hibernate i JPA............................................................................................................11

3.7.4 Frejmvorci za automatsko testiranje..........................................................................................................12

3.8 Perzistentni objekti............................................................................................................................................12

3.9 Transakcije.........................................................................................................................................................12

3

Page 6: Dependency Injection

4. Studijski primjeri……………………………………………….......................................................................…………13

4.1 Prvi primjer.......................................................................................................................................................13

4.2 Drugi primjer.....................................................................................................................................................15

4.3 Treći primjer......................................................................................................................................................28

4.4 Četvrti primjer...................................................................................................................................................34

4.5 Peti primjer........................................................................................................................................................38

4.6 Šesti primjer.......................................................................................................................................................40

5. Zaključak…………………………………………..................................................................................................……41

6. Reference…………………………………………..................................................................................................……42

7. Popis slika................................................................................................................................................................43

4

Page 7: Dependency Injection

3. Osnovni pojmovi

3.1 Java

Java je besplatan, objektno-orijentisani programski jezik otvorenog koda koji se pojavio 1995. godine. Razvio ga je James Gosling za potrebe firme Sun Microsystems. Na Javin razvoj su uticali popularni programski jezici C i C++ kao i Smalltalk, jedan od prvih objektno-orijentisanih jezika.

Ono što posebno izdvaja Javu (naročito u vrijeme kada se pojavila) je njena nezavisnost od platforme na kojoj se izvršava. To se postiže pomoću Javine Virtuelne Mašine. Naime, Javine klase se kompajliraju u bajtkod a taj bajtkod se izvršava tj. interpretira na Java Virtuelnoj Mašini - JVM. Dovoljno je imati JRE (sadrži JVM) za pokretanje programa koji pisan u Javi, uopšte nije bitno na kom je operativnom sistemu on napisan.

Java ima svoj garbage kolektor koji vrši automatsko čišćenje memorije što ju je takođe posebno izdvajalo od ostalih konkurentnih jezika u vrijeme kada se pojavila. U međuvremenu su se pojavili programski jezici koji se izvršavaju na JVM - Scala, Clojure i Groovy. Oni se mogu koristiti u kombinaciji sa Javom.

Smatra se da je jedan od najlakših i najjednostavnijih objektno-orijentisanih jezika. Java je, zahvaljujući svojoj otvorenosti, poslužila kao osnov za brojne popularne frejmvorke i alate. Oni su dobrim dijelom zaslužni za to što je Java danas jedan od najpopularnijih programskih jezika. Zbog te popularnosti na Internetu se može naći ogromna dokumentacija koja olakšava učenje Jave. Pored toga njeno korišćenje i učenje olakšava veliki broj raspoloživih biblioteka.

Programski jezik Java je dio Java platforme. Njena izdanja su:

• Java Standard Edition ili Java SE (J2SE) – za razvoj desktop aplikacija

• Java Enterprise Edition ili Java EE (J2EE ) - za razvoj enterprise aplikacija

• Java Micro Edition ili Java ME (J2ME) – za razvoj aplikacija za mobilne telefone, PDA uređaje i ostale koje ne mogu da podrže J2SE ili J2EE zbog hardverskih ograničenja.

Njihov međusoban odnos najbolje ilustruje sledeća slika:

Slika 2. Izdanja Java platforme i njihov međusoban odnos

5

Page 8: Dependency Injection

3.2 Softverske komponente i servisi

Softverska komponenta je osnovna jedinica građe softverske aplikacije. Aplikaciju možemo posmatrati kao jednu cjelinu koja se sastoji od elemenata koji su međusobno povezani. Ti elementi su softverske komponente. Izvorni kod softverske komponente se ne mijenja ali se ona može nadograditi tj. unaprijediti na način na koji su to predvidjeli kreatori softverske komponente. Jedna softverska komponenta se može sastojati od drugih softverskih komponenata. Softverske komponente mogu biti upotrijebljene više puta za rješavanje različitih problema unutar aplikacije.

Servisi su softverske komponente koji se upotrebljavaju pomoću neke vrste interfejsa za daljinsko upravljanje poput soketa. [1]

3.3 Moduli

Softverska aplikacija može biti podijeljena na module od kojih će svaki biti zadužen za jednu funkciju (ili za jednu grupu funkcija). Modul je softverska komponenta čija je namjena obavljanje jedne funkcije. Modul sadrži sve što je potrebno za obavljanje te funkcije i ima neku vrstu monopola nad funkcijom koju obavlja - samo je on (modul) za nju zadužen. [2]

3.4 Softverski paterni

Tokom rada programeri su nailazili na brojne probleme koje je trebalo riješiti. Primjetili su da se neki od tih problema ponavljaju i da se rješenja koja su korišćena za rješavanje jednog problema mogu ponovo iskoristiti za rješavanje drugog problema, istog tipa. Rješenje neće biti identično ali će njegova suština biti ista pa se ta suština može označiti kao uzor ili šablon (pattern) po kojem će biti implementirano rješenje.

Paterni su plod kolektivne inteligencije programera. To znači da su brojni programeri godinama tragali za što boljim rješenjima za pojedine probleme. Paterni kakve danas imamo su rezultat njihovih zajedničkih napora da dođu do što kvalitetnijeg i univerzalnijeg rješenja. Paterni se uveliko koriste za projektovanje savremenih softverskih aplikacija pa poznavanje paterna nam pomaže u razumijevanju implementacije tih softverskih aplikacija. [3]

3.4.1 Dizajn i arhitekturalni paterni

Treba razlikovati arhitekturalne paterne od dizajn paterna. Arhitekturalni patterni se bavi problemom kako cijeli sistem podijeliti i organizovati u logičke cjeline na najbolji mogući način. Pošto se radi od rješavanju problema u kojem se polazi od sistema tj. od samog vrha kaže se da arhitekturalni paterni su rješenja za probleme najvišeg nivoa. S druge strane dizajn paterni funkcionišu na nižem nivou. Ako npr. sistem podijelimo na tri sloja, neki od dizajn paterna nam mogu riješiti problem koji se nalazi unutar nekog od ta tri sloja. Dakle, aritekturalni paterni se bave strukturom sistema a dizajn paterni rješavanjem problema unutar te strukture. [4]

3.4.2 MVC – Model-View-Controller patern

MVC je arhitekturalni pattern koji dijeli sistem na tri dijela:

• View predstavlja ono što krajnji korisnik vidi.

• Model je sloj orijentisan ka podacima (baza podataka npr) – zadužen je za čuvanje podataka.

• Controller je spona između Modela i View-a.

6

Page 9: Dependency Injection

Slika 3. Model-View-Controller Kada korisnik zahtijeva izmjenu nekih podataka, Controller obrađuje taj zahtjev i na osnovu njega manipuliše Modelom. Zahvaljujući Observer paternu View, koji je zadužen za prikaz podataka, biva obaviješten da je došlo do promjene. U nekim novijim frejmvorcima poput Tapestry-ja ne postoji nikakva direktna veza između View-a i Modela. Za komunikaciju između View-a i modela se ne koristi se Observer pattern već Controller komponenta koja, u tom slučaju, predstavlja jedinu vezu između View-a i Modela. [5]

3.4.3 Depencency Injection

Dependency Injection je arhitekturalni patern koji umanjuje složenost sistema na taj način što omogućava loose coupling – labavo povezivanje. Ali šta je ustvari loose coupling? Uzmimo za primjer jedan običan računar. On se sastoji od računarskih komponenata koje se mogu popraviti, nadograditi, zamijeniti. To je primjer loose coupling-a, a njegova suprotnost - tight coupling (veza između komponenata je čvrsta) je kada ne bismo mogli da zamijenimo, popravimo i nadogradimo komponente. U čemu je problem tight coupling-a? Pa zamislimo da se pokvari jedna komponenta računara bez koje njegovo funkcionisanje nije moguće. Zbog tight coupling-a ne bismo mogli da zamijenimo komponentu, opravka bi bila teška, možda i nemoguća, te bi bili prinuđeni na kupovinu novog računara. Ili recimo imate dual-core procesor i odlučite da ga zamijenite za neki quad-core. Ako je veza između komponenata čvrsta (tight coupling) onda to ne bi bilo moguće uraditi zato što bi matična ploča i procesor bili neraskidivo povezani. Šta ovo znači u svijetu programiranja? Recimo da imamo klasu koja predstavlja matičnu ploču:

public class Motherboard {

private DualCoreProcessor processor;

public Motherboard() {processor = new DualCoreProcessor();

}….

}

Klasa Motherboard je direktno zavisna od klase DualCoreProcessor. Zbog toga je klasu Motherboard nemoguće testirati u izolaciji. To možemo riješiti tako što ćemo klasu Motherboard izmijeniti na sledeći način:

public class Motherboard {

private Processor processor;

public Motherboard(Processor processor) {this.processor = processor;

}}

7

Page 10: Dependency Injection

Ostale klase:

public class DualCoreProcessor implements Processor {…}

public class Pentium4Processor implements Processor {…}

public class QuadCoreProcessor implements Processor {…}

Klasa Motherboard nije više zadužena za kreiranje klase DualCoreProcessor već njenu instancu prima preko svog konstruktora. Treba primjetiti još jednu važnu stvar – primjenjena je paradigma “programiranje preko interfejsa” pa klasa Motherboard nije više direktno zavisna od klase DualCoreProcessor nego od interfejsa Processor. Zahvaljujući tome ona sada može ostvariti vezu sa bilo kojom klasom koja implementira interfejs Processor (QuadCoreProcessor, DualCoreProcessor, Pentium4Processor) i to je primjer loose couplinga. Testiranje u izolaciji je sada u moguće – treba samo proslijediti Mock objekat klase sa kojom je Motherboard povezana.Pošto Motherboard prima instancu klase sa kojom je povezana preko konstruktora riječ je o Constructor Injection-u i to je jedan od oblika Dependency Injection-a (u prevodu - ubrizgavanje zavisnosti). Pored Construction Injection-a postoje Setter Injection, Field Injection i Interface Injection.

Instancirajmo sada klasu DualCoreProcessor i Motherboard:

public static void main (String[] args) {Processor processor = new DualCoreProcessor();Motherboard motherboard = new Motherboard (processor);

…}

Ovo je primjer ručnog Dependency Injection-a a moderne aplikacije koriste frejmvorke koji automatizuju Dependency Injection. Frejmvorci koji automatizuju Dependency Injection su karakteristika svih laganih (lightweight) frejmvorka. Prvobitni naziv za Dependency Injection je Inversion of Control – IoC. Ustanovljeno je da IoC ima šire značenje pa je zbog toga prihvaćen termin Dependency Injection. Ako bi pojam IoC predstavljao jedan skup, DI bi bio njegov podskup. Primjere za IoC možemo naći u svakodnevnom životu - za IoC se vezuje holivudski princip: “Ne zovite vi nas, mi ćemo vas”.

Dependency Injection frejmvorci se još zovu i IoC kontejneri. Poznati IoC kontejneri su: Spring, Guice, PicoContainer, Avalon, Apache HiveMind. [1]

3.5 Programiranje preko interfejsa

Svrha programiranja preko interfejsa je da omogući programeru da jedan objekat lako zamijeni drugim.[6] To se postiže preko interfejsa. Recimo da imamo sledeću metodu:

public void go (Yugo45 yugo45) {yugo.start();

}

Klasa Yugo45:

public class Yugo45 {public void start() {...}

}

Šta ako želim da automobil Yugo zamijenim sa nekim drugim? Nažalost metoda go nas trenutno ograničava samo na Yugo45. Problem se rješava na sledeći način:

public interface Car () {void start();

}

8

Page 11: Dependency Injection

public class Yugo45 imlements Car {public void start() {...}

}

public class Zastava101 imlements Car {public void start() {...}

}

public class RenaultClio imlements Car {public void start() {...}

}

Naravno, svaka od ovih klasa može da implementira metodu start na drugačiji način. A sada ono glavno – metoda go:

public void go (Car car) {car.start();

}

Metoda go više nije ograničena na klasu Yugo45 već može primiti (kao argument) instancu bilo koje klase koja implementira interfejs Car.

3.6 Automatsko testiranje softvera

Automatsko testiranje je proces u kojem se pomoću koda namijenjenog za testiranje testira jedan dio aplikacije. Ručno testiranje je testiranje aplikacije od strane čovjeka. Testiranja se vrše da bi se greške u softveru na vrijeme otkrile i ispravile. Automatsko testiranje se najviše koristi kod modernih metodologija razvoja softvera koje podrazumijevaju da se aplikacija u razvoju često testira.[7] Postoje brojni frejmvorci za automatsko testiranje kao npr: TestNG, Junit, Mockito, Selenium... Njihova svrha je da olakšaju programeru pisanje koda za testiranje. Postoji nekoliko vrsta ovakvih testova.

3.6.1 Unit testiranje

Unit testiranje je testiranje komponenata u izolaciji od drugih komponenata. Preduslov da računar radi kao cjelina je da su sve njegove pojedinačne komponente ispravne. Unit testiranje bi u tom slučaju bilo kada bi se svaka komponenta testirala pojedinačno – u izolaciji od ostalih. U programiranju se ta izolacija postiže tako što klasi nad kojom se vrši Unit testiranje proslijede mock (lažirani) objekti klasa sa kojima je povezana.[8]

3.6.2 Integracioni testovi

Pomoću integracionih testova se provjerava da li su klase ili komponente dobro povezane. Npr. da li je procesor dobro povezan sa matičnom pločom, monitor sa grafičkom karticom itd. Prilikom ovakvih testiranja se ne koriste Mock objekti.[9]

3.6.3 Funkcionalni testovi

Pišu se testovi koji testiraju sistem na način na koji bi ga koristili obični korisnici. Ne testira se unutrašnjost aplikacije nego se aplikacija pokrene i provjerava se da li npr. sve njene opcije i dijelovi zaista rade onako kako bi trebalo. [10]

3.6.4 Testovi prihvatljivosti (acceptance tests)

Testovi prihvatljivosti su funkcionalni testovi koji provjeravaju da li su zadovoljeni korisnički zahtjevi. Jedna aplikacija može sadržati gomilu opcija koje rade ali je pitanje da li rade ono što korisnik želi. Acceptance testovi upravo to provjeravaju.

9

Page 12: Dependency Injection

3.6.5 Testovi performansi

Testovi performansi provjeravaju da li se aplikacija (ili njeni dijelovi) dovoljno brzo izvršava i da li pritom troši odgovarajuću količinu resursa. [7]

3.7 Frejmvorci

Frejmvork (framework) je softver koji olakšava programerima da realizuju aplikaciju koja će raditi posao i biti pogodna za nadogradnju i održavanje. Sam frejmvork je obično zasnovan na nekom od programskih jezika. Frejmvork se može posmatrati kao osnovica sa kojom programer radi a aplikacija nastaje proširivanjem te osnovice. To proširivanje se vriši programiranjem, podešavanjem opcija i konfiguracionih fajlova i to na način na koji frejmvork dozvoljava.

Da nije današnjih frejmvorka programer bi morao da npr iskuca gomilu i gomilu klasa da bi sebi obezbijedio funkcionalnost koju mu frejmvork nudi. Frejmvork je karakterističan i po inverziji odgovornosti (IoC). On je taj koji je zadužen za upravljanje jednim dijelom aplikacije koja se piše a ne aplikacija. Dakle, programer, za dio za koji je zadužen frejmvork, ne piše kod nego na određeni način prepušta taj posao frejmvorku.[11]

3.7.1 Spring frejmvork

Spring je u osnovi DI frejmvork ali ima i druge funkcije – jedna od najbitnijih je AOP. Podijeljen je u nekoliko modula i svaki od njih je zadužen za po jednu funkciju.

Core je zadužen za Dependency Injection. Ovaj modul ustvari predstavlja IoC kontejner. Zahvaljujući ovom modulu, moguće je povezati različite komponente i klase a da pritom nije potrebno da one naslijede neku klasu iz frejmvorka ili implementiraju neki njegov interfejs. Podržava Setter Injection i Constructor Injection. Način na koji će klase biti povezane je definisan unutar XML konfiguracionog fajla. Od verzije 2.5 Spring povezivanje se može konfigurisati pomoću anotacija tako da XML fajlovi više nisu neophodni. Moguće je kombinovati anotacijsko podešavanje i podešavanje putem XML-a.Sledeća slika ilustruje komponente povezane unutar Spring IoC kontejnera:

Slika 4. Spring IoC kontejner

AOP modul je zadužen za Aspektno orijentisano programiranje. AOP omogućava da se na određenom mjestu u metodi “ubrizga” odgovarajući kod. Npr. kod za transakcije je uvijek isti i ako je potrebno implementirati recimo 10 metoda od kojih se svaka odvija unutar jedne transakcije, to znači da kod za transakcije je potrebno pisati 10 puta. Zahvaljujuću AOP-u i frejmvorcima koji ga implementiraju nije potrebno pisati dosadan, repetitivan kod. Taj posao može obaviti frejmvork. U navedenom primjeru on će “obmotati” metode odgovarajućim kodom za transakcije.

10

Page 13: Dependency Injection

Data access and integration modul omogućava lakše povezivanje sa bazom podataka preko JDBC. Takođe omogućava integraciju Spring-a sa alatom/frejmvorkom za objektno-relaciono mapiranje.

Web and remoting modul omogućava pravljenje kompletne Web aplikacije koja se zasniva na MVC paternu. Pored toga modul obezbjeđuje servise koji olakšavaju rad sa udaljenim aplikacijama.

Testing modul je za automatsko testiranje. Sadrži brojne Mock objekte koji olakšavaju unit testiranja. [13]

3.7.2 Apache Tapestry framework

Apache Tapestry je frejmvork za izradu web aplikacija. Kreiran je od strane Howard Lewis Ship-a i zasniva se na programskom jeziku Java. On predstavlja pandan JSF tehnologiji. Tapestry je lightweight frejmvork– izgrađen je na bazi svog IoC kontejnera. On se sastoji iz nekoliko modula koji čine Tapestry Registry. Jedan od tih modula je i modul za aplikaciju koju pravimo pomoću Tapestry frejmvorka. Tapestry aplikacija koristi MVC patern.

Tapestry aplikacija se sastoji iz stranica koje se opet sastoje od komponenata. U osnovi svega su komponente – zato se i kaže da je Tapestry komponentno orijentisan frejmvork. Veza između komponenata je labava zahvaljujući Tapestry IoC kontejneru. Programer može koristiti gotove Tapestry komponente, modifikovane Tapestry komponente a može ih i sam kreirati. Jedna komponenta može sadržati nekoliko drugih komponenata.

Stranica je razdvojena na templejt i klasu. Templejt je prezentacijski dio (ono što korisnik vidi) a u klasi se nalazi logički dio tj. ono što je “ispod haube”. Tempejt kod je sličan html kodu pa ga zbog toga vole dizajneri – mogu se baviti dizajnom stranice bez uplitanja u programski dio.

Struktura Tapestry aplikacije je na slici:

Slika 5. Struktura Tapestry aplikacije

Tapestry posjeduje zanimljive mogućnosti poput live class reloading-a i sistema koji detaljno prikazuje tj. prijavljujegreške u aplikaciji. Live class reloading omogućava programeru da vidi odmah rezultate izmjena koje je napravio u kodu, bez potrebe da restartuje aplikaciju. Error reporting je veoma koristan jer će, kada dođe do greške, markirati dio koda u kojem je došlo do greške i o njoj ispisati razumljivu poruku. [5]

3.7.3 ORM frejmvorci - Hibernate i JPA

Savremene aplikacije karakteriše objektno-orijentisani dizajn. Kao rezultat toga, podaci koje treba sačuvati se nalaze u objektima a baze podataka u kojima se čuvaju podaci su relacione. Dakle na jednoj strani imamo objekte i njihove veze tj. objektni model a na drugoj tabele i relacije – relacioni model. Ta dva modela se umnogome razlikuju. Zbog toga je potrebno da programer piše kod koji će premostiti jaz između ta dva modela. Problem je što je taj kod obiman i repetitivan. Frejmvorci poput Hibernatea oslobađaju programera ovog posla i to zahvaljujući objektno-relacionom mapiranju – ono omogućava preslikavanje objektnog modela u relacioni. Zahvaljujući tome, programer ne mora mnogo da razmišlja o relacionom modelu odnosno o konverziji objektnog modela u relacioni.

11

Page 14: Dependency Injection

Hibernate je jedan od najpopularnijih frejmvorka za perzistenciju podataka putem objektno-relacionog mapiranja. Hibernate ima HQL (Hibernate Query Language) – objektno orijentisani upitni jezik. Iako Hibernate ima podršku za SQL preporučljivo je u aplikaciji koristiti HQL. Za razliku od SQL-a, HQL može da radi sa perzistentnim objektima. Drugi problem sa SQL-om je što postoji ne postoji samo u jednoj već u više varijanti koje se međusobno razlikuju što može predstavljati problem prilikom prelaska na bazu koja koristi drugačiji SQL.

EJB3 tehnologija ima svoj ORM frejmvork koji se zove Java Persistence API – JPA. JPA ima svoj Entity Manager koji je neka vrsta posrednika između baze podataka i aplikacije, dok je kod Hibernate-a za taj posao zadužen Session Factory. Interesantno je da Hibernate, od verzije 2 ima podršku za JPA. Hibernate recimo može biti podešen tako da koristi svoj HQL i JPA EntityManager! [4]

3.7.4 Frejmvorci za automatsko testiranje

Frejmvorci za automatsko testiranje su alati koji omogućavaju i olakšavaju posao automatskog testiranja. Jedan od najpopularnijih frejmvorka za automatsko testiranje je TestNG. Može se koristiti u kombinaciji sa bibliotekama Mockito i EasyMock koje olakšavaju Unit Testiranje na taj način što kreiraju “lažirane” (Mock) objekte. TestNG nam omogućava da razvrstamo testove u različite grupe, naznačimo šta će se izvršavati prije a šta poslije testa, omogućava nam da snadbijemo metodu koja se testira sa potrebnim objektima. TestNG se može upariti sa Selenijum frejmvorkom koji služi za funkcionalno testiranje. Selenium radi tako što pokreće web aplikaciju i vrši testiranje preko browsera. [15]

3.8 Perzistentni objekti

Zamislimo aplikaciju napisanu u nekom objektno orijentisanom jezikom koja ima mogućnost da pamti podatke o svojim korisnicima u bazi podataka. Zamislimo takođe da se to odvija pomoću metode save (user) gdje je user objekat koji predstavlja korisnika. Ukoliko će podaci o korisniku biti dostupni nakon što u potpunosti izgasimo program onda je objekat user perzistentan. Ukoliko bismo umjesto baze podataka koristili Javinu memoriju onda objekat ne bi bio perzistentan.

3.9 Transakcije

Transakcija je vrsta radnje koja se obavlja nad bazom podataka i koja zadovoljava ACID (Atomicity, Consistency, Isolation, Durability) uslove.

Atomicity (Atomnost). Ukoliko se ne izvrši sve ono što je predviđeno transakcijom ona se poništava. Ovaj princip se još zove “sve ili ništa”.

Consistency (Konzistentnost). Ako klijent ima na računu 100 eura i skine sa računa 30 eura to mora biti evidentirano u bazi podataka. Dakle, ako je nakon toga klijentu na računu ostalo više ili manje 70 eura, onda je konzistentnost narušena.

Isolation (Izolacija). Jedna transakcija se odvija nezavisno od druge transakcije.

Durability (Trajnost). Promjene koje su se desile kao rezultat transakcije moraju biti trajno zapamćene u bazi podataka.

12

Page 15: Dependency Injection

4. Studijski primjeri

4.1 Prvi primjer:

Student želi da prijavi kurs koji će da pohađa. Nakon što pristupi odgovarajućoj stranici odabira željeni kurs. Ukoliko je došlo do greške prilikom prijavljivanja biće prebačen na stranicu na kojoj će biti ispisana poruka da je došlo do greške. Evo kako izgleda logika jedne takve stranice:

public class ViewCourses {

@Inject@Propertyprivate Courses courses;

@Propertyprivate Course currentCourse;

@Inject@Propertyprivate Applications applications;

@Property@Persistprivate Application application;

Object onActionFromApply(String courseName) {Course courseToApply = courses.retreiveSingleCourse(courseName);

try {applications.apply(getUser(),courseToApply);

} catch (RedudantAppliacationException e) {return ErrorDuringApplication.class;

}return ViewStudentApplications.class;

}

Dakle ako dođe do greške nakon apply metode, student će biti prebačen na stranicu ErrorDuringApplication. Ukoliko je sve u redu biće prebačen na stranicu ViewStudentApplications. Ali pretpostavimo da je došlo do greške i da je student prebačen na stranicu ErrorDuringApplication. Evo kako izgleda kod njenog logičkog dijela:

public class ErrorDuringApplication {

private String message;

public String getMessage() {return "Error, you are already applied to this course!";

}

}

Kao što se da vidjeti stranica je zadužena za kreiranje poruke koja će biti prikazana korisniku. Ali šta ako korisnik želi npr. da se odjavi sa kursa? Onda mu treba prikazati drugu poruku a za to je potrebno napraviti novu stranicu. A šta ako imamo dvadesetak sličnih situacija? Onda bi trebalo napraviti dvadesetak klasa. Daleko bolje je sledeće rješenje:

13

Page 16: Dependency Injection

public class Info {

@Persist private String message;

public void setMessage(String message) {this.message = message;

}

public String getMessage() {return message;

}

}

Klasa ErrorDuringApplication je preimenovana u Info. Izvršen je loose coupling jer stranica više nije odgovorna za kreiranje poruke već joj se poruka proslijeđuje preko setera – Setter Injection. Nije potrebno praviti pomenutih dvadesetak klasa od kojih će svaka biti odgovorna za kreiranje sopstvene poruke – dovoljna je samo jedna klasa kojoj će biti moguće proslijediti poruku. Ali to nije sve. Drugi problem je: kako proslijediti stranici poruku? Trenutno samo moguće usmjeriti korisnika na Info stranicu, ne i proslijediti toj stranici poruku. Za to je potrebno izvršiti odgovarajuće promjene u klasi ViewCourses:

public class ViewCourses {...@InjectPageprivate Info infoPage;

Object onActionFromApply(String courseName) {Course courseToApply = courses.retreiveSingleCourse(courseName);

try {applications.apply(getUser(),courseToApply);

} catch (RedudantAppliacationException e) {infoPage.setMessage(e.getMessage());return infoPage;

}return ViewStudentApplications.class;

}

Pomoću anotacije @InjectPage je “ubrizgana” stranica Info i na njoj su izvršene promjene – proslijeđena (setovana) joj je odgovarajuća poruka. Tapestry IoC nam je omogućio da na jednoj stranici “ubrizgamo” drugu stranicu i da pritom izvršimo željene promjene na toj drugoj stranici. Info page nije samo koristan samo za ovu stranicu. Evo kako je Info page iskorišćen na drugoj stranici:

public class ViewStudentApplications {...

@InjectPageprivate Info infoPage;

...Object onActionFromCancel(String id) {

applications.cancel(id);infoPage.setMessage("You have canceled your application");return infoPage;

}

U ovom primjeru je dva puta korišćen Dependency Injection: ručni Setter Injection (setMessage), i “ubrizgavanje” stranice pomoću frejmvorka.

14

Page 17: Dependency Injection

4.2 Drugi primjer:

Potrebno je napraviti servis koji:

• čuva podatke o korisniku• briše podatke o korisniku• vraća podatke o jednom korisniku• vraća podatke o svim korisnicima

Interfejs Users:

public interface Users {

Collection<User> retreiveUsers();

User save(User user);

User retreiveSingleUser(String userName);

User delete(String userName);}

Implementacija željenog servisa - klasa UsersBean implementira interfejs Users i omogućava čuvanje podataka u Javinoj memoriji:

public class UsersBean implements Users {

private Map<String, User> users;

public UsersBean(){this(new HashMap<String, User>());

}

public UsersBean(Map<String, User> users) {this.users = users;

}

public Collection<User> retreiveUsers() {return users.values();

}

public User save(User user) {return users.put(user.getUserName(), user);

}

public User retreiveSingleUser(String userName) {return users.get(userName);

}

public User delete(String userName) {return users.remove(userName);

}

}

Za realizaciju primjera biće korišćene mogućnosti Tapestry IoC-a. U klasi AppModule.java potrebno je dodati sledeću metodu:

15

Page 18: Dependency Injection

public class AppModule {….

public static Users buildUsers() { return new UsersBean();}

…}

AppModule.java je konfiguracioni fajl modula Aplikacije koji je jedan od Tapestry modula. Tapestry IoC, za razliku od drugih frejmvorka, ne koristi XML fajlove ili druge za konfigurisanje već običnu Java klasu koja se kompajlira i čiju ispravnost provjerava kompajler prilikom kompajliranja.

Metoda buildUsers je veoma jednostavna i služi za instanciranje singleton objekta klase UsersBean preko interfejsa Users. Singleton objekat omogućava da podaci o korisnicima budu sačuvani na jednom mjestu pomoću jednog i samo jednog objekta. Da nisu sačuvani na jednom mjestu onda bi bilo teško npr. povratiti podatake o jednom korisniku. Šta ako se kreira i koristi više objekata a jedan od njih završi u Garbage Collectoru? U tom slučaju bi korisni podaci bili izgubljeni.

Metoda buildUsers je kreirana sa ciljem da omogući programeru da na svim Tapestry stranicama može da pristupi jedinstvenom objektu klase Users. Ali kako mu on pristupa tom objektu? Pogledajmo kod logičkog dijela stranice za Registraciju korisnika:

public class Registration {

@Property@Persist("flash")private User user;

@Injectprivate Users users;

@Persist("flash")@Property

private Role role;

@Componentprivate Form form;

@SetupRenderpublic void createObject() {

user = new UserBean();user.setRole(Role.STUDENT);

}

void onValidateForm(){if (userNameExists()) {

form.recordError("user name already exists, chose another one");}

}

Object onSuccess() {users.save(user);return Index.class;

}

private boolean userNameExists() {return users.retreiveSingleUser(user.getUserName()) != null;

16

Page 19: Dependency Injection

}}

Obratimo pažnju na:

@Injectprivate Users users;

I na ono što je napisano u AppModule.java klasi:

public static Users buildUsers() {return new UsersBean();

}

Ovo je primjer Field Injection-a (ubrizgavanje zavisnosti preko atributa) - u atribut users je pomoću Tapestry IoC-a ubrizgan objekat klase UsersBean. Ako u klasi komponente deklarišemo varijablu i ako iznad nje postavimo anotaciju @Inject onda će Tapestry IoC u tu varijablu ubrizgati najprikladniji objekat tj. komponentu. Preduslov je da je ta komponenta već konfigurisana za Tapestry IoC. U ovom slučaju konfiguracija je obavljena pomoću buildUsers() metode u AppModule.java klasi. Jedan od načina za konfigurisanje servisa u Tapestry IoC-u je kreiranje statičke metode čiji će naziv počinjati sa “build”, a drugi način je korišćenjem statičke metode bind:

public static void bind(ServiceBinder binder) {binder.bind(Users.class, UsersBean.class);

}

Obratimo pažnju na ulazni argument metode bind – binder je referenca ka objektu koji će biti ubrizgan putem Tapestry IoC-a! U AppModule.java se nalazi konfiguracija samo jednog malog dijela Tapestry IoC-a. Binder je unaprijed konfigurisan u nekoj drugoj klasi Tapestry frejmvorka i stoji programeru na raspolaganju – samo je potrebno zatražiti njegovo ubrizgavanje preko bind metode.

Zahvaljujući:

@Injectprivate Users users;

moguće je pristupiti servisu za korisnike na bilo kojoj stranici. Evo kako izgleda kod logičkog dijela stranice EditUser:

public class EditUser {...

@Injectprivate Users users;

public void onActivate(String userName) {userEdit = users.retreiveSingleUser(userName);this.userName = userName;

}

public String onPassivate() {return userName;

}

public Object onSuccess() {users.save(userEdit);return ViewUsers.class;

}

I ovdje je u polje users ubrizgan singleton objekat klase UsersBean. Anotacija @Inject omogućava da se na jednostavan način pristupi istom objektu kojem je pristupljeno i na stranici za registraciju.

17

Page 20: Dependency Injection

A šta ukoliko se ne koristi Dependency Injection? Kako bi kod izgledao? Jedno od rješenja:

public class UsersBeanAlternative {

private static UsersBeanAlternative usersBeanAlternative;private Map<String, User> users = new HashMap<String, User>();

private UsersBeanAlternative(){

}

public static UsersBeanAlternative getInstance(){if(usersBeanAlternative == null) {

usersBeanAlternative = new UsersBeanAlternative();}return usersBeanAlternative;

}

public Collection<User> retreiveUsers() {return users.values();

}

public User save(User user) {return users.put(user.getUserName(), user);

}

public User retreiveSingleUser(String userName) {return users.get(userName);

}

public User delete(String userName) {return users.remove(userName);

}

}

U klasi UsersBeanAlternative je primijenjen singleton patern. Pošto se ne koristi Tapestry IoC u AppModule nije potrebno ništa dodavati ni mijenjati ali je zato potrebno mijenjati kod Tapestry stranica. Evo kako će sada izgledati kod logike Tapestry stranice:

public class Registration {

@Property@Persist("flash")private User user;

@Persist("flash")@Property

private Role role;

@Componentprivate Form form;

@SetupRenderpublic void createObject() {

user = new UserBean();user.setRole(Role.STUDENT);

}

18

Page 21: Dependency Injection

void onValidateForm(){if (userNameExists()) {

form.recordError("user name already exists, chose another one");}

}

Object onSuccess() {UsersBeanAlternative.getInstance().save(user);return Index.class;

}

private boolean userNameExists() {String userName = user.getUserName();boolean exists = UsersBeanAlternative.getInstance().retreiveSingleUser(userName) != null;return exists;

}}

U metodama OnSuccess i userNameExists je došlo do promjena. Zbog toga je potrebno promijeniti svaku stranicu koja koristi ovaj servis. Ukoliko je takvih stranica dvadeset onda bi trebalo mijenjati kod svakoj od njih. A šta ako je potrebno koristiti servis koji je povezan sa bazom podataka? I u tom slučaju bi sve trebalo mijenjati. Izmjene su neophodne zato što su stranice (u ovom slučaju stranica za registraciju) čvrsto vezane (tight coupling) za klasu UsersBeanAlternative. Ukoliko programer želi da koristi neki drugi servis, morao bi prvo da raskine vezu sa UsersBeanAlternative na svakoj stranici i klasi koja koristi taj servis. Te promjene je nekad potrebno izvršiti ne samo u logičkom dijelu koda nego i u samom templejtu. Uzmimo npr. logički dio koda stranice za pregled svih korisnika (ViewUsers):

public class ViewUsers {

...@Inject@Propertyprivate Users users;

@Propertyprivate User currentUser;

void onActionFromDelete(String userName) {users.delete(userName);

}…}

Sledeći dio koda templejta je zadužen za prikaz svih korisnika koji se nalazi u ViewUsers.tml fajlu:

<table width="70%" > <tr> <td> Username </td> <td> Full Name </td> <td> User Role </td> <td> Edit user </td> <td> Delete user </td> </tr> <tr t:type="loop" t:source="users.retreiveUsers()" t:value="currentUser"> <td> ${currentUser.userName}

19

Page 22: Dependency Injection

</td> <td> ${currentUser.fullName} </td> <td> ${currentUser.Role} </td> <td> <a href="#" t:type="PageLink" t:page="EditUser" t:context="currentUser.userName">Edit</a> </td> <td> <t:actionlink t:id="delete" context="currentUser.userName">Delete</t:actionlink> </td> </tr> </table>

Ako bi veza između komponenata bila čvrsta odnosno ako bismo u ovom slučaju koristili UsersBeanAlternative klasu i njene metode, svaki put kada bismo htjeli da zamijenimo servise morali bismo mijenjati ne samo logički dio koda nego i templejt. Npr.:

<tr t:type="loop" t:source="users.retreiveUsers()" t:value="currentUser">

ukoliko bi, u slučaju tight coupling-a, došlo do zamjene servisa, bilo bi potrebno mijenjati dio koji slijedi poslije t:source – metoda users.retreiveUsers().

Za razliku od tight couplinga, loose coupling omogućava da se na lak način zamijene servisi – dovoljno je samo izvršiti određene promjene u konfiguracionom fajlu, a kod stranica (logički dio i templejt) ostaje nepromijenjen.

S porastom kompleksnosti aplikacije, mane tight couplinga još više dolaze do izražaja što će i biti pokazano na sledećem primjeru. U narednom primjeru biće prikazan servis koji je poput UsersBeanAlternative ali sa jednom bitnom razlikom – podaci se čuvaju u pravoj bazi podataka pomoću JPA i Hibernate frejmvorka.

Za pristup bazi podataka je potreban JPA Entity Manager. Preko njega će se čuvati i izvlačiti podaci iz baze podataka. Za instanciranje Entity Managera biće koršćena sledeća klasa:

public class EntityManagerProvider {

private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("vesko-R");private static EntityManager em;

private EntityManagerProvider(){

}

public static EntityManager getEntityManager(){if (em == null) {

return emf.createEntityManager();}return em;

}

public static void closeEntityManagerFactory() { if (emf != null) { emf.close(); emf = null;

20

Page 23: Dependency Injection

}}

}

Kao što vidi, prvo je potrebno kreirati EntityManagerFactory na osnovu konfiguracije iz persistence.xml fajla. EntityManagerFactory služi za instanciranje EntityManagera. U klasi EntityManagerProvider postoje dvije metode. Jedna je zadužena za kreiranje EntityManagera a druga je zadužena za zatvaranje EntityManagerFactory. Metode su statičke (globalne) da bi bile na raspolaganju svim servisima.

Servis UsersServiseHibernateAlternative koristi, između ostalog, metodu getEntityManager:

public class UsersServiceHibernateAlternative {

private static UsersServiceHibernateAlternative usersServiceHibernateAlternative; private EntityManager em = EntityManagerProvider.getEntityManager();

private UsersServiceHibernateAlternative() {super();

}

public static UsersServiceHibernateAlternative getInstance() {if (usersServiceHibernateAlternative == null)

usersServiceHibernateAlternative = new UsersServiceHibernateAlternative();return usersServiceHibernateAlternative;

}

public Collection<User> retreiveUsers() {Collection<User> allUsers = null; EntityTransaction tx = null;try {

tx = em.getTransaction();tx.begin();allUsers = em.createQuery("from UserBean").getResultList();tx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}return allUsers;

}

public User retreiveSingleUser(String userName) {EntityTransaction tx = null;List<User> user = null;try {

tx = em.getTransaction();tx.begin();Query query = em.createQuery("from UserBean as u where u.userName = :userName");query.setParameter("userName", userName);user = query.getResultList();tx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}if(!user.isEmpty())

return user.get(0);

21

Page 24: Dependency Injection

return null;}

public User save(User user) {EntityTransaction tx = null;User userUpdate = null;try {

tx = em.getTransaction();tx.begin();userUpdate = em.merge(user);tx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}return userUpdate;

}

public User delete(String userName) {User userToDelete = retreiveSingleUser(userName);EntityTransaction tx = null;try {

tx = em.getTransaction();tx.begin();em.remove(userToDelete);tx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}return userToDelete;

}}

UsersServiceHibernateAlternative je rađen po uzoru na UsersBeanServiceAlternative. U pitanju je klasa koja koristi Singleton patern i ne koristi Dependency Injection ali, za razliku od UsersBeanServiceAlternative, komunicira sa bazom podataka preko EntityManagera. Zbog toga se može posmatrati kao naprednija odnosno kompleksnija varijanta klase UsersBeanServiceAlternative. Ona takođe ima problem koji je imao UsersBeanServiceAlternative servis - tight coupling na stranicama koje koriste servis. Pored toga, povećanje kompleksnosti je iznjedrilo nove probleme koje treba riješiti.

Jedan od problema je što se klasa ne može testirati u izolaciji. Sledeći kod predstavlja pokušaj da se uradi unit test klase UsersServiceHibernateAlternative:

public class UsersServiceHibernateAlternativeTest {

private UsersServiceHibernateAlternative usersHibernateUnderTest;private User userMock;

@BeforeMethodpublic void setUp(){

usersHibernateUnderTest = UsersServiceHibernateAlternative.getInstance();userMock = Mockito.mock(User.class);

}

22

Page 25: Dependency Injection

@Testpublic void save(){

User savedUser = usersHibernateUnderTest.save(userMock);Assert.assertEquals(savedUser, userMock);

}}

Klasu nije moguće testirati u izolaciji zato što se unutar nje instancira Entity manager, odnosno, problem je u tome što je ona čvrsto vezana za Entity Manager. Pravo unit testiranje bi bilo kada bilo moguće proslijediti lažirani (Mock) objekat za Entity Manager. Bez Dependecy Injection-a nije moguće uraditi unit testiranje a bez unit testiranja razvoj savremenih aplikacija je nezamisliv.

Drugi problem sa UsersServiceHibernateAlternative je što je narušen tzv. DRY princip – Don't Repeat Yourself. Kod za transakciju se ponavlja u svakoj metodi:

EntityTransaction tx = null;try {

tx = em.getTransaction();tx.begin();//do something heretx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}

Potrebno je instancirati odgovarajuću klasu za transakciju, započeti transakciju, završiti je ili vratiti se na početno stanje ukoliko je došlo do greške. Ponavljanje ovakvog koda za transakciju (zamislite da imamo oko 100-ak metoda u svim servisima) je dosadan i otupljujuć posao, klase postaju ogromne i teže za razumijevanje.

Rješenje navedenih problema leži u upotrebi Spring frejmvorka. Za primjer uzmimo sledeću klasu:

public class UsersServiceHibernate implements Users {

@PersistenceContext private EntityManager em;

@Transactionalpublic Collection<User> retreiveUsers() {

return em.createQuery("from UserBean").getResultList();}

@Transactionalpublic User retreiveSingleUser(String userName) {

Query query = em.createQuery("from UserBean as u where u.userName = :userName");query.setParameter("userName", userName);List<User> user = query.getResultList();if(!user.isEmpty())

return user.get(0);else

return null;}

@Transactionalpublic User save(User user) {

User savedUser = em.merge(user);return savedUser;

23

Page 26: Dependency Injection

}

@Transactionalpublic User delete(String userName) {

User userToDelete = retreiveSingleUser(userName);em.remove(userToDelete);return userToDelete;

}}

Ova klasa je duplo manja od UsersServiceHibernateAlternative a radi isti posao. Nema više koda za transakciju u metodama. Isto tako, klasa EntityManagerProvider više nije potrebna. Da bi UsersServiceHibernate servis radio potreban je Spring framework podešen na sledeći način:

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <bean id="entityManagerFactory"

class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="src/main/resources/persistence.xml" />

</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactory" />

</bean> <tx:annotation-driven transaction-manager="transactionManager" />

<bean id="edu.rakovic.elearning.hibernate.UsersServiceHibernate"class="edu.rakovic.elearning.hibernate.UsersServiceHibernate"/>

EntityManagerFactory je sada komponenta za koju je odgovoran Spring:

<bean id="entityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

property name="persistenceXmlLocation" value="src/main/resources/persistence.xml" /></bean>

U Spring-ovom modulu za objektno-relaciono mapiranje postoji klasa LocalContainerEntityManagerFactoryBean u paketu org.springframework.orm. Bean komponenta sa identifikatorom “entityManagerFactory” je nastala tako što je u Spring-ovoj klasi LocalContainerEntityManagerFactoryBean pomoću Setter Injectiona ubrizgana putanja persistence.xml fajla koji sadrži podatke neophodne za kreiranje Entity Manager Factory.

Kod za transakcije nije potrebno više pisati zahvaljujući sledećoj komponenti:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactory" />

</bean>

Springov modul za objektno-relaciono mapiranje ima svoj Transaction Manager koji je zadužen za transakcije. Da bi bio iskorišćen, potrebno je povezati prethodno definisanu komponentu koja predstavlja Entity Manager Factory sa Spring-ovom klasom JpaTransactionManager. Povezivanje se vrši na sledeći način:

<property name="entityManagerFactory" ref="entityManagerFactory" />

U pitanju je Setter Injection koji se realizuje pomoću frejmvorka.Na kraju, da bi bilo moguće koristiti Transaction Manager preko anotacija (@Transactional) potrebno je dodati sledeće:

<tx:annotation-driven transaction-manager="transactionManager" />

24

Page 27: Dependency Injection

Sada umjesto koda koji se ponavljao:

EntityTransaction tx = null;try {

tx = em.getTransaction();tx.begin();//do something heretx.commit();

} catch (RuntimeException e) {if ( tx != null && tx.isActive() )

tx.rollback();e.printStackTrace();

}

je dovoljno napisati @Transactional iznad metode.

U klasi UsersServiseHibernate se pomoću Springa “ubrizgava” Entity Manager:

@PersistenceContextprivate EntityManager em;

Entity Manager se, prije “ubrizgavanja” (Field Injection), kreira preko Entity Manager Factory koji je podešen u Springu. Međutim, nije samo to dovoljno. Potrebno je, u Spring konfiguraciji, dodati sledeće:

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

EntityagerManFactory obilato koristi resurse te ga je potrebno zatvoriti kada nije potreban. Ukoliko je konfigurisan preko Springa, a EntityManager se koristi na taj način što se ubrizgava u odgovarajuće polje pomoću @PersistanceContext anotacije, onda će Spring da se pobrine za pravovremeno zatvaranje EntityManagerFactory-ja. U suprotnom bi programer sam morao o tome da brine.

Na kraju je potrebno da se u xml fajlu UsersServiceHibernate konfiguriše kao komponenta:

<bean id="edu.rakovic.elearning.hibernate.UsersServiceHibernate"class="edu.rakovic.elearning.hibernate.UsersServiceHibernate"/>

Drugi način da se ovo uradi je ubacivanje anotacije @Repository u klasu:

@Repository public class UsersServiceHibernate implements Users {….}

Dakle, Spring od verzije 2.5 podržava konfigurisanje pomoću anotacija. Da bi koristili anotacije potrebno je u xml fajl dodati sledeće:

<context:annotation-config/> <context:component-scan base-package="edu.rakovic.elearning"/>

Time se Springu naznačava paket u čijim klasama su korišćene anotacije.Prikazanu konfiguraciju Springa, pored UsersServiceHibernate servisa, mogu koristiti i drugi servisi:

public class ContentsServiceHibernate implements Contents {

@PersistenceContext private EntityManager em;

@Transactionalpublic Content save(Content content) {

em.persist(content);

25

Page 28: Dependency Injection

return content;}

@Transactionalpublic Collection<Content> retreive() {

List<Content> all = em.createQuery("from ContentBean").getResultList();return all;

}}

Zahvaljujući Springu ne treba više pisati kod za transakcije i Entity manager. Naravno, da bi ovaj servis bilo moguće upotrijebiti potrebno je koristiti @Repository anotaciju ili ga konfigurisati u xml fajlu:

<bean id="edu.rakovic.elearning.hibernate.ContentsServiceHibernate"class="edu.rakovic.elearning.hibernate.ContentsServiceHibernate"/>

Ali kako koristiti ove servise? Kako ih instancirati? Konfigurisane komponente se “izvlače” iz Springovog konteksta koji se takođe mora instancirati. Jedan od načina da se to uradi je tako što se prilikom instanciranja konteksta naznačava putanja do xml fajla u kojem je Spring konfigurisan:

ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/resources/applicationContext.xml");

Servis se može instancirati, odnosno izvući iz Springovog konteksta na sledeći način:

UsersServiceHibernate users = context.getBean(UsersServiceHibernate.class.getName());

Pošto UsersServiceHibernate implementira interfejs Users, onda se može uraditi sledeće:

Users users = context.getBean(UsersServiceHibernate.class.getName());

Ko još implementira interfejs Users? Ranije predstavljena UsersBean klasa koja pamti podatke u Javinoj memoriji! Ona se može instancirati na sledeći način:

Users users = new UsersBean();

Ovo se može iskoristiti. Sjetimo se buildUsers():

public static Users buildUsers() { return new UsersBean();}

Ona vraća objekat koji se ubrizgava u stranice pomoću @Inject metode. Taj objekat je instanciran preko interfejsa Users. To znači da je moguće uraditi sledeće:

public static Users buildUsers() {return (Users) context.getBean(UsersServiceHibernate.class.getName());

//return new UsersBean();}

Servise su zamijenjeni izmijenom samo jedne linije koda! Nije potrebno mijenjati kod Tapestry stranice – iznad polja:

@Injectprivate Users users;

je moguće ubrizgati bilo koji objekat koji je instanciran preko interfejsa Users. U ovom slučaju taj objekat je, prije ubrizgavanja pomoću @Inject anotacije, izvađen iz Springovog konteksta a zahvaljujući Springu njemu su ubrizgane druge komponente, potrebne za, između ostalog, rad sa bazom podataka.

26

Page 29: Dependency Injection

Dakle, ukoliko bi komponente (u ovom slučaju Tapestry stranice) bile čvrsto vezane za neki servis onda bi, prilikom zamjene servisa (za neki sličan) bilo potrebno prekidati te veze na svakoj stranici. Međutim, ako su komponente labavo povezane i ako se koristi IoC kontejner, sve što treba je izvršiti određene promjene u konfiguraciji IoC kontejnera. To je lakši posao zato što je lakše izvršiti promjene u jedan ili dva konfiguraciona fajla nego mijenjati logiku i templejt recimo dvadesetak Tapestry stranica.

Dependency Injection može pomoći u Unit testiranju klase UsersServiceHibernate. Da bi Unit testiranje moglo da se odradi potrebno je dodati seter metodu za EntityManager.

public class UsersServiceHibernate implements Users {

@PersistenceContext private EntityManager em;

public void setEm(EntityManager em) {this.em = em;

}….

}

Evo kako sada izgleda Unit test UsersServiceHibernate klase:

public class UsersServiceHibernateTest {

private UsersServiceHibernate usersHibernateUnderTest;private EntityManager entityManagerMock;private User userMock;private Query queryMock;private List<User> usersReturn;

@BeforeMethodpublic void setUp() {

createMocksAndReturnList();usersHibernateUnderTest = new UsersServiceHibernate();usersHibernateUnderTest.setEm(entityManagerMock);

}private void createMocksAndReturnList() {

entityManagerMock = Mockito.mock(EntityManager.class);userMock = Mockito.mock(User.class);queryMock = Mockito.mock(Query.class);usersReturn = new LinkedList<User>();usersReturn.add(userMock);Mockito.when(queryMock.getResultList()).thenReturn(usersReturn);

}

@Testpublic void testSave() {

Mockito.when(entityManagerMock.merge(userMock)).thenReturn(userMock);User savedUser = usersHibernateUnderTest.save(userMock);assertEquals(savedUser, userMock);

}

@Testpublic void retreiveUsers() {

when(entityManagerMock.createQuery("from UserBean")).thenReturn(queryMock);when(entityManagerMock.createQuery("from UserBean")

.getResultList()).thenReturn(usersReturn);Collection<User> retreivedUsers = usersHibernateUnderTest.retreiveUsers();

27

Page 30: Dependency Injection

assertTrue(retreivedUsers.contains(userMock));}

@Testpublic void testRetreiveSingleUser() {

Mockito.when(entityManagerMock.createQuery("from UserBean as u where u.userName = :userName")).thenReturn(queryMock);assertEquals(usersHibernateUnderTest.retreiveSingleUser("test"), userMock);

}

@Testpublic void testDeleteUser() {

Mockito.when(entityManagerMock.createQuery("from UserBean as u where u.userName = :userName")).thenReturn(queryMock);assertEquals(usersHibernateUnderTest.delete("test"), userMock);

}}

Sada je moguće objektu klase UsersServiceHibernate proslijediti lažirani (Mock) objekat za Entity Manager:

@BeforeMethodpublic void setUp() {

createMocksAndReturnList();usersHibernateUnderTest = new UsersServiceHibernate();usersHibernateUnderTest.setEm(entityManagerMock);

}

Metoda createMocksAndReturnList() je zadužena za kreiranje svih Mock objekata koji su potrebni za test. UsersHibernateUnderTest se instancira pomoću new operatora onda mu se, preko setera, proslijeđuje Mock EntityManager objekat. Riječ je o ručnom Setter Injecton-u koji ne bi bio moguć da u klasi UsersServiceHibernate nije napisana seter metoda za EntityManager odnosno atribut em.

4.3 Treći primjer:

Jedan od načina za spriječavanje neovlašćenog pristupa Tapestry stranicama je korišćenje Session State objekta. Dio logike Login stranice:

@SessionState@Propertyprivate String userNameAuth;

@Propertyprivate String userName;

void onValidateForm() {if (authenticate(userName, password)) {

userNameAuth = userName;} else {

loginForm.recordError("We couldn't authenticate you. Try again or register.");}

}

Session State objekat je String userNameAuth. Ukoliko je logovanje uspješno on će sadržati korisničko ime a u suprotnom njegova vrijednost će biti null. Session state objektu možemo pristupiti na svakoj stranici korišćenjem @SessionState anotacije. Primjer – stranica Index, odnosno njen logički dio:

28

Page 31: Dependency Injection

public class Index {

@SessionStateprivate String userName;

private boolean userNameExists;

Object onActivate() {if (!userNameExists) {

return Login.class;} else

return null;}

}

Ovaj kod provjerava da li userName ima vrijednost. Ukoliko nema to znači da se korisnik nije ulogovao i da ga treba preusmjeriti na stranicu za logovanje. Problem je što ovaj kod potrebno iskucati na svakoj stranici kojoj je potrebno pristupiti kao ulogovan korisnik. Na taj način se krši DRY – Don't Repeat Yourself princip. Ukoliko je potrebno provjeriti da li je npr. ulogovani korisnik administrator, onda će repetitivan kod biti još duži. Primjer je Main stranica:

public class Main {

@SessionStateprivate String userName;

private boolean userNameExists;

Object onActivate() {if (!userNameExists) {

return Login.class;} else return null;

@Injectprivate Users users;

private User user;

public User getUser() {return users.retreiveSingleUser(userName);

}

public boolean isUserAdmin(){if(authenticator.getUser().getRole() == Role.ADMINISTRATOR) return true;return false;

}

public boolean isUserTeacher(){if(authenticator.getUser().getRole() == Role.TEACHER) return true;return false;

}

public boolean isUserStudent(){if(authenticator.getUser().getRole() == Role.STUDENT) return true;return false;

}

}

29

Page 32: Dependency Injection

Pored koda za provjeru da li je korisnik ulogovan dodato je još par metoda. Metoda getUser() je potrebna da bi Main stranica mogla da prikaže puno ime ulogovanog korisnika. Metoda getUser() koristi Users servis pa ga je potrebno “ubrizgati”. Metode isUserAdmin(), isUserTeacher(), isUserStudent() provjeravaju tip (ulogu) ulogovanog korisnika. One se mogu iskoristiti na razne načine. Npr. u metodi koja se prva izvršava na stranici – onActivate() - se može dodati kod koji provjerava da li je korisnik administrator i preusmjerava ga na odgovarajuću stranicu ukoliko nije administrator.Metode se mogu iskoristi i u templejtu. Dio Main.tml koda:

<t:if t:test="userTeacher"> <h3>Welcome teacher!</h3> <p> [<t:pagelink t:page="AddCourse">Add course</t:pagelink>]</p> <p> [<t:pagelink t:page="ViewTeachingCourses">View Teaching Courses</t:pagelink>]</p></t:if>

Na stranici Main su linkovi Add Course i View Teaching Courses dostupni samo korisniku koji posjeduje nalog tipa Teacher, što provjerava metoda isUserTeacher().

Pomoću Dependecy Injection-a i Tapestry komponenata je moguće eliminisati repetitivan kod. Prvo je potrebno kreirati Tapestry komponentu koja će služiti za provjeru identiteta korisnika. Tapestry komponenta se nalazi u components paketu, ima svoju logiku i templejt – baš kao i Tapestry stranice. Logički dio komponente koja će provjeravati da li je korisnik ulogovan:

public class Authenticator {

@Inject private Response response;

@SessionStateprivate String userNameAuth;private boolean userNameAuthExists;

public boolean isUserLoggedIn(){return userNameAuthExists;

}

public String authenticate() throws IOException {if (!isUserLoggedIn()) {

response.sendRedirect("login");}return "";

}}

Tempejt komponente:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">${authenticate()}

</html>

Metoda authenticate(), koja se poziva u templejtu, provjerava da li je korisnik ulogovan i ukoliko nije šalje ga na stranicu za logovanje. Usmjeravanje na stranicu za logovanje se vrši pomoću servisa Response odnosno njene metode sendRedirect. Servis Response se ubrizgava u Tapestry komponentu na sledeći način:

@Inject private Response response;

30

Page 33: Dependency Injection

U HTTP protokolu server na osnovu zahtjeva (Request) generiše odgovor (Response). Pomoću Tapestry frejmvorka odgovor se može poslati korišćenjem servisa Response. Potrebno ga je ubrizgati na odgovarajuću stranicu i iskoristiti neke od njegovih metoda.

Komponenta Authenticator se koristi tako što se templejtu Tapestry stranice doda sledeće:

<t:authenticator/>

Više nema potrebe na svakoj stranici pisati kod za provjeru da li je korisnik ulogovan. Authenticator, Tapestry komponenta koja se koristi, automatski poziva metodu authenticate() koja provjerava da li je korisnik ulogovan.

Pomoću Tapestry komponente Authenticator je moguće provjeriti da li je korisnik ulogovan ali ne i tip korisnika. Zbog toga je potrebno dopuniti logički dio koda komponente:

public class Authenticator {

@Injectprivate ComponentResources resources;

@Inject private Response response;

@Injectprivate Users users;

@SessionStateprivate String userNameAuth;private boolean userNameAuthExists;

public User getUser() {return users.retreiveSingleUser(userNameAuth);

}

public boolean isUserLoggedIn(){return userNameAuthExists;

}

public String authenticate() throws IOException {response.disableCompression();

if (!isUserLoggedIn()) {response.sendRedirect("login");

}if (isPageName("ViewUsers") && !isUserAdmin()) {

response.sendError(401, "You are not allowed to view that page!");}if (isPageName("ViewStudentApplications") && !isUserStudent()) {

response.sendRedirect("main");}if (isPageName("AddCourse") && !isUserTeacher()) {

response.sendError(401, "You are not allowed to view that page!");}return "";

}

private boolean isPageName(String pageName) {return resources.getPageName().equalsIgnoreCase(pageName);

}

31

Page 34: Dependency Injection

public boolean isUserAdmin() {return getUser().getRole() == Role.ADMINISTRATOR;

}

public boolean isUserTeacher() {return getUser().getRole() == Role.TEACHER;

}

public boolean isUserStudent() {return getUser().getRole() == Role.STUDENT;

}} Metode isUserAdmin(), isUserTeacher() i isUserStudent() koriste metodu getUser() koja vraća objekat korisnika koji je ulogovan. Metoda getUser() se oslanja na servis za korisnike kao i korisničko ime koje se čuva kao Session State objekat:

@Injectprivate Users users;

@SessionStateprivate String userNameAuth;

public User getUser() {return users.retreiveSingleUser(userNameAuth);

}

Sada je na Main stranici, kao i na svim ostalim stranicama, moguće koristiti metode isUserAdmin(), isUserTeacher() i isUserStudent(). Na logičkom dijelu stranice Main sada ima manje koda:

@InjectComponent@Propertyprivate Authenticator authenticator;

Komponenta Authenticator je ubrizgana da bi se koristile njene metode. Njih je moguće koristiti u logičkom dijelu koda i u templejtu. Da komponenta nije podešena tako da automatski poziva authenticate() metodu onda bi bilo potrebno pozvati je u logičkom dijelu koda:

void onActivate() {authenticator.authenticate();

}

Pored anotacije @InjectComponent je dodata i anotacija @Property (alternativa njoj su seter i geter metode). Bez nje ne bi bilo moguće u templejtu pristupiti metodama komponente Authenticator. Dio koda templejta Main stranice:

<t:authenticator/>

<p> The currently logged user is: <a href="#" t:type="PageLink" t:page="UserProfile" t:context="authenticator.user.userName">${authenticator.user.fullname}</a>.</p>

<t:if t:test="authenticator.userTeacher"> <h3>Welcome teacher!</h3> <p> [<t:pagelink t:page="AddCourse">Add course</t:pagelink>]</p> <p> [<t:pagelink t:page="ViewTeachingCourses">View Teaching Courses</t:pagelink>]</p></t:if>

Ovaj dio :<t:if t:test="authenticator.userTeacher"> koristi isUserTeacher() metodu preko geter metode authenticator atributa, koja je generisana pomoću @Property anotacije.

32

Page 35: Dependency Injection

Komponentu Authenticator ne bi bilo moguće ubrizgati u logički dio putem @InjectComponent anotacije da u templejtu nije dodato sledeće:

<t:authenticator/>

Main stranica sadrži link ka profilu korisnika koji je ulogovan. Profil se prikazuje u zavisnosti od konteksta linka a sam kontekst, preko authenticator getera, koristi getUser metodu i getUserName metodu:

t:type="PageLink" t:page="UserProfile" t:context="authenticator.user.userName"

U klasi Authenticator se takođe nalazi sledeće:

@Injectprivate ComponentResources resources;

private boolean isPageName(String pageName) {return resources.getPageName().equalsIgnoreCase(pageName);

}

Tapestry ima komponentu ComponentResources preko koje je, putem Dependecy Injection-a, moguće pristupiti resursima bilo koje komponente koja se koristi. Između ostalog, moguće je doći i do imena stranice koja koristi komponentu (Authenticator) putem metode getPageName(). Metoda isPageName provjerava da li se string koji je unijet preko njenog ulaznog argumenta poklapa sa imenom stranice na kojoj se nalazi komponenta.

Metoda authenticate(), koja je dopunjena, koristi nove metode:

public String authenticate() throws IOException {…if (isPageName("ViewUsers") && !isUserAdmin()) {

response.sendError(401, "You are not allowed to view that page!");}if (isPageName("ViewStudentApplications") && !isUserStudent()) {

response.sendRedirect("main");}if (isPageName("AddCourse") && !isUserTeacher()) {

response.sendError(401, "You are not allowed to view that page!");}return "";

}

Prvi uslov provjerava da li je korisnik, koji pokušava pristupiti ViewUsers stranici, administrator. Ukoliko nije biće preusmjeren na stranicu koja će obavijestiti korisnika da je došlo do greške i prikazati poruku “"You are not allowed to view that page!". Tu stranicu generiše sam frejmvork. U drugom uslovu je korišćena metoda sendRedirect() pa će korisnik koji nema ovlašćenje za pristup stranici biti preusmjeren na stranicu Main.

Ukoliko se koristi Session State objekat, preporučljivo je konfigurisati ga u AppModulu.

public void contributeApplicationStateManager(MappedConfiguration<Class, ApplicationStateContribution> configuration) {ApplicationStateCreator<String> creator = new ApplicationStateCreator<String>() {

public String create() {return new String();

}};

configuration.add(String.class, new ApplicationStateContribution("session", creator));}

33

Page 36: Dependency Injection

Tapestry frejmvork sadrži Mapiranu konfiguraciju na koju je moguće uticati putem Dependency Injectiona. Metoda contributeApplicationStateManager preko argumenta prima Mapiranu konfiguraciju i doprinosi joj preko add metode. U primjeru je Session State (nekadašnje ime je Application State) podešen za objekte tipa String. Na ovaj način se izbjegavaju greške koje se mogu javiti ukoliko je Session State objekat tipa String. Mapirana konfiguracija se ubrizgava metodi contributeApplicationStateManager preko Tapestry IoC kontejnera. Mapirana konfiguracija se može koristiti na samo u AppModulu nego i u drugim modulima – sve što treba je zatražiti njeno ubrizgavanje. Zahvaljujući tome, velike sisteme je moguće razbiti u male dijelove koji su lakši za razumijevanje i održavanje.

4.4 Četvrti primjer:

Čest problem kod Web aplikacija je što se Hibernate Sesija ili Entity Manager zatvore prije nego što je potrebno. Zbog toga dolazi do LazyInitializationException. Ova greška se javlja zbog pokušaja da se izvrši upit nad bazom podataka nakon što je konekcija je sa bazom zatvorena. Hibernate nije svjestan onoga što se dešava na View nivou i često zatvori Hibernate Sesiju ili Entity Manager prije nego što korisnik završi posao preko korisničkog interfejsa. Rješenje je da se vrijeme njihove aktivnosti produži na taj način što će se HTTP zahtjev, zajedno sa odgovorom na zahtjev, učiniti transakcionim. Onda će se Entity Manager ili Hibernate Sesija zatvoriti tek nakon završetka transakcije. Ovo rješenje je poznato i kao Open Session In View patern. Njegova implementacija u Tapestry aplikaciji:

public class OpenSessionInViewFilter implements RequestFilter {

@Transactionalpublic boolean service(Request request,

Response response, RequestHandler requestHandler) throws IOException {return requestHandler.service(request, response);

}

}

Request filter već postoji u Tapestry frejmvorku i samo ga treba implementirati. Request i Response će se odvijati u okviru transakcije za koju će se brinuti Spring-ov Transaction Manager. On je već konfigurisan u drugom primjeru pa je u Springov konfiguracioni fajl još samo potrebno dodati sledeće:

<bean id="OpenSessionInViewFilter" class="edu.rakovic.elearning.filter.OpenSessionInViewFilter">

</bean>

Komponenta OpenSessionInViewFilter je konfigurisana i biće instancirana preko Springa zbog Transaction Managera.

Tapestry frejmvork je ustvari jedan veliki filter kroz koji prolazi svaki zahtjev (Request) tako da se dobija filtriran odgovor (Response). Zapravo Request se obrađuje pomoću brojnih filtera koje Tapestry posjeduje. Tapestry IoC omogućava programeru da ubaci svoje filtere. OpenSessionInViewFilter može biti jedan od tih filtera. Potrebno je implementirati interfejs RequestFilter i preko contribute metode u AppModulu konfigurisati odnosno ubaciti filter:

public void contributeRequestHandler (OrderedConfiguration<RequestFilter> filter) {RequestFilter openSessionInViewFilter = (RequestFilter) context.getBean("OpenSessionInViewFilter");

filter.add("osvf",openSessionInViewFilter,"before:Ajax");}

OrderedConfiguration<RequestFilter> će biti ubrizgan putem Dependecy Injectiona. Nakon toga se OpenSessionInViewFilter izvlači iz Springovog konteksta i preko metode add dodaje u OrderedConfiguration kao još jedan filter. Filteru je dodat identifikator “osvf” kao i parametar “before:ajax”. Naime, filter će biti ubačen u grupu filtera koji se izvršavaju određenim redosledom što najbolje ilustruje sledeća slika:

34

Page 37: Dependency Injection

Slika 6. Tapestry - procesiranje zahtjeva

Na desnoj strani se nalazi Ajax filter (filteri su označeni žutom bojom). Parametrom “before:ajax” je naznačeno da se dodati OpenSessionInViewFilter izvršava prije Ajax filtera. “Uređena konfiguracija” (OrderedConfiguration<RequestFilter>) je korišćena baš zato što se filteri izvršavaju određenim redosledom. Na taj redosled je moguće uticati što je i pokazano na primjeru.

Konfiguracija OpenSessionInViewFiltera u AppModulu može biti realizovana i na sledeći način:

public static RequestFilter buildOpenSessionInViewFilter() {return (RequestFilter) context.getBean("OpenSessionInViewFilter");

}

public void contributeRequestHandler (OrderedConfiguration<RequestFilter> filter, RequestFilter openSessionInView) {

filter.add("osvf",openSessionInView,"before:Ajax");}

35

Page 38: Dependency Injection

OpenSessionInViewFilter je konfigurisan kao običan servis a metoda contributeRequestHandler je promijenjena tako da joj se neophodan filter proslijeđuje kao ulazni parametar. Tapestry IoC će sam naći OpenSessionInViewFilter (zato je i konfigurisan kao servis) i ubrizgati ga. Servis se naknadno može zamijeniti drugim što je jedna od prednosti Dependency Injectiona. Ukoliko bi u AppModule bio konfigurisan još jedan sličan filter onda bi pomoću anotacije @InjectService bilo potrebno naglasiti koji će filter biti ubrizgan:

public void contributeRequestHandler (OrderedConfiguration<RequestFilter> filter) {@InjectService("OpenSessionInViewFilter")RequestFilter openSessionInView) {

filter.add("osvf",openSessionInView,"before:Ajax");}

A evo primjera implementacije OpenSessionInViewFiltera iz zvanične Hibernate dokumentacije [16]:

public class HibernateSessionConversationFilter implements Filter { private static Log log = LogFactory.getLog(HibernateSessionConversationFilter.class); private SessionFactory sf; public static final String HIBERNATE_SESSION_KEY = "hibernateSession"; public static final String END_OF_CONVERSATION_FLAG = "endOfConversation"; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { org.hibernate.classic.Session currentSession; // Try to get a Hibernate Session from the HttpSession HttpSession httpSession = ((HttpServletRequest) request).getSession(); Session disconnectedSession = (Session) httpSession.getAttribute(HIBERNATE_SESSION_KEY); try { // Start a new conversation or in the middle? if (disconnectedSession == null) { log.debug(">>> New conversation"); currentSession = sf.openSession(); currentSession.setFlushMode(FlushMode.NEVER); } else { log.debug("< Continuing conversation"); currentSession = (org.hibernate.classic.Session) disconnectedSession; } log.debug("Binding the current Session"); ManagedSessionContext.bind(currentSession); log.debug("Starting a database transaction"); currentSession.beginTransaction(); log.debug("Processing the event"); chain.doFilter(request, response); log.debug("Unbinding Session after processing"); currentSession = ManagedSessionContext.unbind(sf);

36

Page 39: Dependency Injection

// End or continue the long-running conversation? if (request.getAttribute(END_OF_CONVERSATION_FLAG) != null || request.getParameter(END_OF_CONVERSATION_FLAG) != null) { log.debug("Flushing Session"); currentSession.flush(); log.debug("Committing the database transaction"); currentSession.getTransaction().commit(); log.debug("Closing the Session"); currentSession.close(); log.debug("Cleaning Session from HttpSession"); httpSession.setAttribute(HIBERNATE_SESSION_KEY, null); log.debug("<<< End of conversation"); } else { log.debug("Committing database transaction"); currentSession.getTransaction().commit(); log.debug("Storing Session in the HttpSession"); httpSession.setAttribute(HIBERNATE_SESSION_KEY, currentSession); log.debug("> Returning to user in conversation"); } } catch (StaleObjectStateException staleEx) { log.error("This interceptor does not implement optimistic concurrency control!"); log.error("Your application will not work until you add compensation actions!"); // Rollback, close everything, possibly compensate for any permanent changes // during the conversation, and finally restart business conversation. Maybe // give the user of the application a chance to merge some of his work with // fresh data... what you do here depends on your applications design. throw staleEx; } catch (Throwable ex) { // Rollback only try { if (sf.getCurrentSession().getTransaction().isActive()) { log.debug("Trying to rollback database transaction after exception"); sf.getCurrentSession().getTransaction().rollback(); } } catch (Throwable rbEx) { log.error("Could not rollback transaction after exception!", rbEx); } finally { log.error("Cleanup after exception!"); // Cleanup log.debug("Unbinding Session after exception"); currentSession = ManagedSessionContext.unbind(sf); log.debug("Closing Session after exception"); currentSession.close(); log.debug("Removing Session from HttpSession"); httpSession.setAttribute(HIBERNATE_SESSION_KEY, null);

37

Page 40: Dependency Injection

} // Let others handle it... maybe another interceptor for exceptions? throw new ServletException(ex); } } public void init(FilterConfig filterConfig) throws ServletException { log.debug("Initializing filter..."); log.debug("Obtaining SessionFactory from static HibernateUtil singleton"); sf = HibernateUtil.getSessionFactory(); } public void destroy() {} }

U primjeru se koristi Hibernate Session koji je ekvivalent JPA Entity Manager-u. U primjeru se ne koriste Spring i Tapestry frejmvorci. Kod :

chain.doFilter(request, response);

je pandan sledećem:

requestHandler.service(request, response);

ali je razlika ostatku koda i razumljivosti istog, velika. Klasa HibernateSessionConversationFilter je ogromna. Njen kod je teško razumjeti što se pokušalo nadoknaditi komentarima i logovima. Komentari su često indikator da je kod loš odnosno nerazumljiv, pa se pišu da bi pomogli u razumijevanju koda[17].

Veličina koda iz HibernateSessionConversationFilter bi se značajno smanjila ukoliko bi se koristio Spring-ov Transaction Manager a Springovi izuzeci bi učinili većinu logova i komentara izlišnim. U prvom primjeru Spring u sprezi sa Tapestry-jem čini većinu koda upotrijebljenog u drugom primjeru, izlišnim! To dobar primjer kako frejmvorci vrše inverziju odgovornosti (Inversion of Control) – rade određeni posao umjesto programera i na taj način mu olakšavaju posao. Dependency Injection je je tu samo jedna, ali veoma važna karika.

4.5 Peti primjer:

Contribute metoda u AppModulu može biti upotrijebljena za konfiguraciju raznih dijelova Tapestry frejmvorka. Npr mogu se ubaciti novi validatori:

public static void contributeValidatorMacro(MappedConfiguration<String, String> configuration) {

configuration.add("username", "required, minlength=3, maxlength=13"); configuration.add("pass", "required, minlength=4, maxlength=14"); }

Drugi način:

@Contribute(ValidatorMacro.class)public static void combineValidators(MappedConfiguration<String, String> configuration) { configuration.add("username", "required, minlength=3, maxlength=13"); configuration.add("pass", "required, minlength=4, maxlength=14"); }

38

Page 41: Dependency Injection

U oba slučaja se dodaju novi parametri konfiguraciji servisa ValidationMacro. Potrebno je imati contribute metodu (ili anotaciju) i naznačiti kojem servisu treba dodati nove parametre putem add metode. Validacija se može koristiti u templejtu:

<input t:id="username" t:type="textfield" t:value="user.userName" t:label="Username:" t:validate="username"/>

<input t:id="password" t:type="passwordfield" t:value="user.password" t:validate="pass"/>

Pored t:validate je potrebno dodati identifikator iz mapirane konfiguracije (pass i username). Drugi način je da se u logički dio koda ubace anotacije:

@Validate(“username”)private String userName;

@Validate(“pass”)private String password;

U aplikaciji je korišćena verzija za templejt. Ukoliko validacija ne bi bila konfigurisana u Tapestry IoC kod u temlejtu bi ovako izgledao:

<input t:id="username" t:type="textfield" t:value="user.userName" t:label="Username:" t:validate=" required, minlength=3, maxlength=13"/>

<input t:id="password" t:type="passwordfield" t:value="user.password" t:validate="required, minlength=4, maxlength=14"/>

Novi način validacije (konfigurisan putem Tapestry IoC-a) je bolji, jer, pored toga što smanjuje količinu koda u temlejtu, moguće ga je upotrijebiti više od jedanput.

Tapestry IoC se razlikuje od ostalih IoC kontejnera po tome što je njegovu konfiguraciju moguće mijenjati preko contribute metode. Konfiguracija koja se putem Dependency Injectiona ubrizgava u contribute metodu može biti:

➢ Neuređena kolekcija. Primjer:

public void contributeHibernateEntityPackageManager(Configuration<String> configuration) {configuration.add("mypackage");

}Neuređena kolekcija se koristi kada nije bitan redosled konfiguracije.

➢ Uređena lista, odnosno uređena konfiguracija:

public void contributeRequestHandler (OrderedConfiguration<RequestFilter> filter) {…}

Uređena konfiguracija je korišćena u primjeru za OpenSessionInView filter kada je bilo potrebno ubaciti filter na odgovarajuće mjesto da bi se izvršavao prije drugih filtera.

➢ Mapirana konfiguracija. Ona se koristi u primjeru za validaciju. Koristi je i metoda koju ima većina Tapestry aplikacija:

public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration) {

configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en");configuration.add(SymbolConstants.PRODUCTION_MODE, "false");

}

39

Page 42: Dependency Injection

Razlika između Mapirane konfiguracije i konfiguracije koja koristi uređenu listu je ista kao i razlika između mape i liste – za razliku od liste, mapa ima jedinstvene ključeve za sve objekte koje sadrži. Metoda contributeAplicationDefaults omogućava podešavanje podrazumijevanih vrijednosti Tapestry aplikacije. Tako npr. aplikacija koja ima metodu iz primjera će koristiti engleski jezik i detaljno prijavljivati greške zato što je Production Mode isključen.

4.6 Šesti primjer:

Servisi koje koristi Tapestry aplikacija se mogu pregledati tako što se u browseru doda putanji “/core/servicestatus”:

Slika 7. Prikaz statusa Tapestry servisa

Stranica prikazuje servise koji se nalaze u Registry-ju kao i njihov status. Servisi mogu imati sledeći status:

• Builtin – ugrađeni servis.

• Defined – servis koji je definisan ali još nije pozvan. Takav status ima Users servis prilikom pokretanja aplikacije. U pitanju je primjer Late Binding-a kojeg omogućava Dependency Injection. Servis se učitava samo onda kada je to potrebno. Na taj način se manje opterećuje server i poboljšavaju se performanse .

• Virtual – servis je učitan ali nijedna njegova metoda nije potrebna. Kada se samo pristupi Login stranici servis Users će dobiti status Virtual zato što se u atribut login stranice ubrizgava servis Users.

• Real – servis je učitan iskorišćen za neku radnju. Nakon uspješnog logovanja korisnika servis Users će dobiti status real zato što je prilikom logovanja korišćena njegova metoda retreiveSingleUser. Late Binding se može izbjeći dodavanjem anotacije @EagerLoad iznad metode koja je zadužena za instanciranje servisa – tada će on dobiti status Real automatski prilikom pokretanja aplikacije. Ovu mogućnost treba obazrivo koristiti jer može doći do pada performansi aplikacije.

40

Page 43: Dependency Injection

5. Zaključak

U ovom radu prikazan je arhitekturalni patern Dependency Injection upotrebom Spring i Tapestry IoC frejmvorka koji ga implementiraju. Pokazano je da:

• Dependency Injection se može obaviti ručno ili automatski preko frejmvorka koji se zovu IoC kontejneri.

• Dependency Injection se javlja u nekoliko oblika: Constructor Injection, Setter Injection, Field Injection. Ako se komponente se ubrizgavaju preko konstruktora klase onda je riječ o Constructor Injection-u. Field Injection označava ubrizgavanje komponente direktno u polje odnosno atribut klase. Kada se komponente ubrizgavaju preko seter metode onda je riječ o Setter Injectionu. Takođe sama metoda ne mora biti seter da bi mogla da primi komponentu kao ulazni parametar.

• Dependency Injection, odnosno labavo povezivanje koje DI omogućava, povećava fleksibilnost aplikacije na taj nacin sto omogućava laku zamjenu servisa. Sve što treba je izvršiti određene promjene u konfiguraciji IoC kontejnera.

• Zahvaljujući Dependency Injectionu izbjegnuto je pisanje pisanje klasa koje rade sličan posao. Na stranici za prijavljivanje grešaka je dovoljno ostaviti mogućnost da se proslijedi odgovarajuća poruka putem Dependency Injectiona.

• Zahvaljujući DI izbjegnuto je pisanje repetitivnog koda koji čini klasu obimnijom i nerazumljivijom. Springov modul za Aspektno orijentisano programiranje u sprezi sa modulom zaduženim za Dependency Injection omogućava ubrizgavanje koda koji se ponavlja u svakoj metodi. Tapestry komponente se ubrizgati u mogu koristiti više puta zahvaljujući Dependency Injectionu. Na taj način se takođe eliminiše kod koji se ponavlja.

• DI čini lakim unit testiranje, bez kojeg je razvoj savremenih aplikacija nezamisliv. Dovoljno je proslijediti Mock (lažirani) objekat i testiranje može da počne. Proslijeđeni Mock objekat mijenja pravi a zamjenu je moguće izvršiti zahvaljujuću Dependency Injectionu.

• Dependency Injection je spona koja povezuje komponente od kojih se aplikacija sastoji - Spring čak ima ORM module koji u sprezi sa Dependency Injectionom omogućavaju integraciju sa frejmvorcima za objektno-relaciono mapiranje (Hibernate i Java Persistence API), odnosno povezivanje sa komponentama drugih frejmvorka.

• Dependency Injection frejmvork odnosno IoC kontejner je poput švedskog stola a Dependecy Injection je poput noža kojim se programer poslužuje na taj način što traži ubrizgavanje određenih komponenata kada su mu potrebne.

• Dependency Injection čini složeni frejmvork pristupačnim i lakim za konfiguraciju i nadogradnju. Moguće je, na relativno lak način, uticati na složen frejmvork, poput Tapestry-ja i to veoma lako. Za to nije potrebno naslijediti klase ili implementirati odgovarajuće interfejse - dovoljno je samo ubrizgati novu konfiguraciju u odgovarajući servis. Konfiguracija Tapestry IoC se vrši u samom programskom jeziku dok se Spring frejmvork konfiguriše preko xml fajla ili anotacija.

• Depencency Injection omogućava nadogradnju aplikacije na način na koji to nije ekplicitno predviđeno i na taj način čini sistem otvorenom a ne zatvorenom cjelinom.

• Performanse sistema se povećavaju ukoliko se koristi Dependency Injection. To je zbog toga što DI omogućava Late Binding – servisi će biti učitani samo ukoliko budu potrebni.

Zaključci vode ka tome da arhitekturalni patern Dependency Injection omogućava lakšu nadogradnju, razumijevanje i održavanje sistema na šta softverski inženjeri potroše najviše vremena i enegrije.

41

Page 44: Dependency Injection

6. Reference

[1] Martin Fowler, “Inversion of Control Containers and the Dependency Injection pattern” , http://martinfowler.com/articles/injection.html, August 2011

[2] Jürgen Haas, "Modular programming”, http://linux.about.com/cs/linux101/g/modularprogramm.htm, August 2011

[3] Brad Appleton, “Patterns and Software: Essential Concepts and Terminology”, http://www.cmcrossroads.com/bradapp/docs/patterns-intro.html, August 2011

[4] Buschmann, F. et al, “Pattern-Oriented Software Architecture, Volume 1”, Wiley, 1996.

[5] Igor Drobizako, “Tapestry 5 in Action, Early Access Edition”, Chapter 1, http://manning.com/drobiazko/, Manning, August 2011.

[6] Wikipedia “Programming against software interfaces”, http://en.wikipedia.org/wiki/Interface_%28computing%29#Programming_against_software_interfaces, August 2011.

[7] Lasse Koskela “Test Driven Development”, Manning, 2007.

[8] Wikipedia “Unit Testing”, http://en.wikipedia.org/wiki/Unit_testing, August 2011.

[9] Wikipedia “Integration Testing”, http://en.wikipedia.org/wiki/Integration_testing, August 2011.

[10] Sue Hildreth “The Basics of Testing”, http://www.informit.com/articles/article.aspx?p=333473&rll=1, August 2011

[11] Wikipedia, “Software framework”, http://en.wikipedia.org/wiki/Software_framework, August 2011.

[12] Chris Richarson, “POJOs in Action”, Manning, 2006.

[13] Craig Walls, “Spring in Action, 3rd edition”, Manning, 2011.

[14] Christian Bauer, Gavin King, “Java Persistence with Hibernate”, Manning, 2006.

[15] Cédric Beust, Hani Suleiman, “Next Generation Java Testing” , Pearson Education Inc., 2007.

[16] Official Hibernate documentation, “Open Session In View Filter”, http://community.jboss.org/wiki/OpenSessionInView September 2011.

[17] Martin Fowler et al, “Refactoring: Improving the Design of Existing Code ”, Addison-Wesley, 2002.

42

Page 45: Dependency Injection

7. Popis slika

Slika 1. Poređenje sistema sklopljenih od velikih i malih dijelova................................................................................1

Slika 2. Izdanja Java platforme i njihov međusoban odnos………….............................................................................5

Slika 3. Model-View-Controller……………………………………....................................................................................7

Slika 4. Spring IoC kontejner……...………………………………..................................................................................10

Slika 5. Struktura Tapestry aplikacije……………………………....................................................................................11

Slika 6. Tapestry - procesiranje zahtjeva……………………………..............................................................................35

Slika 7. Prikaz statusa Tapestry servisa……………………………................................................................................40

43