Dependency Injection
January 10, 2017 | Author: necr084 | Category: N/A
Short Description
Download Dependency Injection...
Description
UNIVERZITET « MEDITERAN » PODGORICA Fakultet za informacione tehnologije-Podgorica
Veselin Raković Integracija softverskih komponenata pomoću Dependency Injection paterna ZAVRŠNI RAD
Podgorica, 2011.
UNIVERZITET « MEDITERAN » PODGORICA Fakultet za informacione tehnologije - Podgorica
Integracija softverskih komponenata pomoću Dependency Injection paterna ZAVRŠNI RAD
Predmet: Projektovanje i razvoj softverskih aplikacija Mentor: doc. dr Dragan Đurić
Student: Veselin Raković Smjer: Softverski inženjering Matični broj: 1302984220014
Podgorica, oktobar, 2011.
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
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
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
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
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
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
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 couplinga, 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
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
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
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
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. prijavljuje greš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
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
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 @Property private Courses courses; @Property private Course currentCourse; @Inject @Property private Applications applications; @Property @Persist private 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
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 { ... @InjectPage private 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 { ... @InjectPage private 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
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 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 users; public UsersBean(){ this(new HashMap()); } public UsersBean(Map users) { this.users = users; } public Collection 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
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; @Inject private Users users; @Persist("flash") @Property private Role role; @Component private Form form; @SetupRender public 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
} } Obratimo pažnju na: @Inject private 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: @Inject private 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 { ... @Inject private 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
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 users = new HashMap(); private UsersBeanAlternative(){ } public static UsersBeanAlternative getInstance(){ if(usersBeanAlternative == null) { usersBeanAlternative = new UsersBeanAlternative(); } return usersBeanAlternative; } public Collection 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; @Component private Form form; @SetupRender public void createObject() { user = new UserBean(); user.setRole(Role.STUDENT); }
18
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 @Property private Users users; @Property private 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: Username Full Name User Role Edit user Delete user ${currentUser.userName}
19
${currentUser.fullName} ${currentUser.Role} Edit Delete 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.: 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
} } } 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 retreiveUsers() { Collection 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 = 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
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; @BeforeMethod public void setUp(){ usersHibernateUnderTest = UsersServiceHibernateAlternative.getInstance(); userMock = Mockito.mock(User.class); }
22
@Test public 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 here tx.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; @Transactional public Collection retreiveUsers() { return em.createQuery("from UserBean").getResultList(); } @Transactional public User retreiveSingleUser(String userName) { Query query = em.createQuery("from UserBean as u where u.userName = :userName"); query.setParameter("userName", userName); List user = query.getResultList(); if(!user.isEmpty()) return user.get(0); else return null; } @Transactional public User save(User user) { User savedUser = em.merge(user); return savedUser;
23
} @Transactional public 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: EntityManagerFactory je sada komponenta za koju je odgovoran Spring: property name="persistenceXmlLocation" value="src/main/resources/persistence.xml" /> 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: 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: 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:
24
Sada umjesto koda koji se ponavljao: EntityTransaction tx = null; try { tx = em.getTransaction(); tx.begin(); //do something here tx.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: @PersistenceContext private 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: 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: 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: 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; @Transactional public Content save(Content content) { em.persist(content);
25
return content; } @Transactional public Collection retreive() { List 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: 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: @Inject private 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
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 usersReturn; @BeforeMethod public 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(); usersReturn.add(userMock); Mockito.when(queryMock.getResultList()).thenReturn(usersReturn); } @Test public void testSave() { Mockito.when(entityManagerMock.merge(userMock)).thenReturn(userMock); User savedUser = usersHibernateUnderTest.save(userMock); assertEquals(savedUser, userMock); } @Test public void retreiveUsers() { when(entityManagerMock.createQuery("from UserBean")).thenReturn(queryMock); when(entityManagerMock.createQuery("from UserBean") .getResultList()).thenReturn(usersReturn); Collection retreivedUsers = usersHibernateUnderTest.retreiveUsers();
27
assertTrue(retreivedUsers.contains(userMock)); } @Test public void testRetreiveSingleUser() { Mockito.when(entityManagerMock. createQuery("from UserBean as u where u.userName = :userName")).thenReturn(queryMock); assertEquals(usersHibernateUnderTest.retreiveSingleUser("test"), userMock); } @Test public 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: @BeforeMethod public 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 @Property private String userNameAuth; @Property private 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
public class Index { @SessionState private 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 { @SessionState private String userName; private boolean userNameExists; Object onActivate() { if (!userNameExists) { return Login.class; } else return null; @Inject private 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
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: Welcome teacher! [Add course] [View Teaching Courses] 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; @SessionState private String userNameAuth; private boolean userNameAuthExists; public boolean isUserLoggedIn(){ return userNameAuthExists; } public String authenticate() throws IOException { if (!isUserLoggedIn()) { response.sendRedirect("login"); } return ""; } } Tempejt komponente: ${authenticate()} 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
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: 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 { @Inject private ComponentResources resources; @Inject private Response response; @Inject private Users users; @SessionState private 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
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: @Inject private Users users; @SessionState private 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 @Property private 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: The currently logged user is: ${authenticator.user.fullname}.
t:page="UserProfile"
Welcome teacher! [Add course] [View Teaching Courses] Ovaj dio : koristi isUserTeacher() metodu preko geter metode authenticator atributa, koja je generisana pomoću @Property anotacije.
32
Komponentu Authenticator ne bi bilo moguće ubrizgati u logički dio putem @InjectComponent anotacije da u templejtu nije dodato sledeće: 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: @Inject private 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 configuration) { ApplicationStateCreator creator = new ApplicationStateCreator() { public String create() { return new String(); } }; configuration.add(String.class, new ApplicationStateContribution("session", creator)); }
33
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 { @Transactional public 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: 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 filter) { RequestFilter openSessionInViewFilter = (RequestFilter) context.getBean("OpenSessionInViewFilter"); filter.add("osvf",openSessionInViewFilter,"before:Ajax"); } OrderedConfiguration ć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
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) 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 filter, RequestFilter openSessionInView) { filter.add("osvf",openSessionInView,"before:Ajax"); }
35
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 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
// 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("
View more...
Comments