Lect. Univ. Dr. Gabroveanu Mihai [628525]
UNIVERSITATEA DIN CRAIOVA
FACULTATEA DE ȘTIINȚE
SPECIALIZAREA INFORMATICĂ
LUCRARE DE LICENȚĂ
Îndrumător științific :
Lect. Univ. Dr. Gabroveanu Mihai
Absolvent: [anonimizat]
2019
UNIVERSITATEA DIN CRAIOVA
FACULTATEA DE ȘTIINȚE
SPECIALIZAREA INFORMATICĂ
DEZVOLTAREA APLICAȚIILOR WEB FOLOSIND
SPRING 5
Îndrumător științific :
Lect. Univ. Dr. Gabroveanu Mihai
Absolvent: [anonimizat]
2019
CUPRINS :
3
CAPITOLUL 1
INTRODUCERE
Spring Framework este descris ca fiind un lightweight framework folosit la dezvoltarea
aplicațiilor Java, el furnizând un model programabil și configurabil pentru crearea aplicațiilor
enterprise. La baza lui, Spring oferă un container , care este deseori referit ca și Spring
Application Context , acesta gestio nând crearea și administrarea componentelor aplicației.
Aceste componente, numite și beans, sunt legate împreună în interiorul Application Context -ului
ca să realizeze o aplicație completă.
O primă formă a acestui framework a fost introdusă de către Rod Johnson în cartea
acestuia, Expert One -on-One J2EE Design (Wrox, 2002), urmând ca pe parcursul anilor acesta să
aibă o creștere exponențială în funcționalități, proiecte asociate și suport al comunității. Cartea
oferea o fundație a configurației bean -urilo r, suport pentru AOP, o abstractizare a JDBC ș.a.m.d.
Prima versiune cu documentație oficială a fost realizată în anul 2004, Spring Framework
1.0, framework -ul fiind compus din 7 module : Spring Core (Bean Container) , Spring Context
(Application Context, U I, Validation) , Spring DAO (Infrastructura de tranzacții, Java Database
Connectivity – JDBC), Spring ORM (Hibernate, iBatis), Spring AOP (o implementare a aspect –
oriented programming – AOP), Spring Web (Integrare de bază a caracteristicilor precum
multifun cționalitatea, inițializarea contextului prin servlet listeners și un context al aplicației
orientat web) și Spring Web MVC (Model -View -Controller – MVC framework) .
A doua versiune a adus o configurare XML mai ușoară a aplicațiilor bazată pe XML
Schema , îmbunătățiri pe partea de AOP, introducerea Java Persistence API (JPA) și suport pentru
adnot ările din Java 5, specific pentru @Transactional, @Required și @AspectJ . În versiunea
2.5 au urmat să fie adăugate o nouă adnotare pentru configurare numită @Autowir ed, precum și
alte adnotări stereotip ( @Component, @Repository, @Service, @Controller).
4
A treia versiune a fost prima versiune bazată pe Java 5 și construită ca să se poată folosi de
toate avantajele pe care le oferea Java 5, cum ar fi generics, varargs și alte îmbunătățiri ale
limbajului. Au fost introduse Spring Expression Language (SpEL) și suport pentru JSR -303 (Bean
Validation ). Următoarele subversiuni au adus adnotări echivalente elementelor namespace -urilor
Spring XML precum @ComponentScan, @Enabl eWebMvc, etc. , suport pentru Hibernate 4, un nou
test framework Spring MVC, Jackson JSON 2 suport și multe altele.
A patra versiune a fost un release major și a fost prim a care să ofere suport total pentru
Java 8. Clasele și metodele deprecated au fost în lăturate, modulele rămânând organizate în mare
parte la fel. Subversiunile următoare au adus suport total pentru Hibernate ORM 5.0, îmbunătățiri
pentru JMS, WebSocket și Testing, modelul de programare a fost rafinat și a fost introdus suport
pentru librări i noi.
A cincea versiune a fost de asemenea un release major, întregul cod de bază al framework –
ului bazându -se pe Java 8. Suportul a fost oprit pentru librării precum Portlet, Velocity, Tiles2 și
Hibernate3. Au fost aduse îmbunătățiri exploatând toată put erea caracteristicilor Java 8. A fost
introdus suport pentru Protobuf 3.0, JMS 2.0+ și JPA 2.1+ și au fost aduse îmbunătățiri modulului
de test, J Unit 5 fiind sprijinit și fiind adăugate noi adnotări. Modulele web și core au fost adaptate
pentru model de p rogramare reactive.
Această prezentare se va axa pe Spring 5 și va conține următoarele capitole :
1. Capitolul Introducere reprezintă partea introductivă a lucrării și prezentarea pe
capitole a structurii lucrării.
2. Capitolul Spring Framework conține detalii d espre modulele și arhitectura pe care
este bazat Spring 5 și sunt descrise componentele care au stat la dezvoltarea
framework -ului, și anume : Dependency Injection și Aspect Oriented
Programming (AOP) .
3. Capitolul Spring MVC este dedicat modulului Spring MVC fiind unul dintre cele
mai importante module aparținând Spring Framework. Este bazat pe container -ul
Spring IoC și profită la maxim de caracteristicile container -ului, permițându -i să-și
simplifice configurația.
4. Capitolul Spring JPA descrie un alt modul i mportant al Spring -ului, Spring Data
JPA, care face parte din familia Spring Data și are rolul de a ușura implementarea
repository -urilor bazate pe JPA.
5
5. Capitolul Aplicație conține prezentarea aplicației și descrie modul în care au fost
implementate concep tele prezentate în capitolele anterioare.
6. Capitolul Concluzii cuprinde sinteza informațiilor prezentate în cadrul acestei
lucrări și opinia personală privind utilizarea acestui framework în cadrul
aplicațiilor.
6
CAPITOLUL 2
SPRING FRAMEWORK
Spring pune la dispoziție tot ce ea ce are un dezvoltator nevoie pentru a realiza o aplicație
Java enterprise, acesta oferind suport pentru Groovy și Kotlin ca limbaje alternative pe Java Virtual
Machine (JVM). De la apariția framework -ului Spring 5, Spring neces ită Java Development Kit
8+ (JDK 8+) și dispune de asemenea și de suport pentru JDK 9.
Spring este împărțit în module, aplicațiile deținând libertatea de a alege ce module doresc
să folosească. Modulele de bază sunt reprezentate de către core container , incluzând un model
configurabil și un mecanism de dependency injection. De asemenea, Spring oferă suport pentru
multiple tipuri de arhitecturi ale aplicațiilor.
Principiile îndrumătoare ale framework -ului Spring sunt :
Permiterea realizării de schimbări ale design -ului aplicației la orice nivel. Spre
exemplu, se poate schimba persistence provider -ul prin intermediul
configurației fără a fi nevoie de a efectua modificări în cod.
Spring susține flexibilitatea și nu este unidirecționat , fiindu -i ușor să se adapteze
la perspective multiple.
Menținerea unei compatibilități solide. Spring a evoluat uniform, astfel încât să nu
fie nevoie de schimbări drastice între versiuni.
Realizarea de API -uri intuitive care să persiste pe parcursul anilor.
Setarea unor standarde înalte legate de calitatea codului, Spring accentuând
importanța unui Javadoc elaborat corect.
7
2.1 IoC Container
Inversion of Control (IoC) container este una dintre tehnologiile de bază aparținând
framework -ului Spring. IoC mai este cunoscut și ca dependency injection (DI). DI este caracterizat
ca fiind procesul prin care obiectele își definesc propriile dependențe doar prin intermediul
argumentelor unui constructor, argumentelor unei metode de tip factory sau prin inter mediul
metodelor de tip set ale clasei respective. Atunci când se dezvoltă aplicații Java complexe se
dorește pe cât posibil ca obiectele și clasele să fie cât mai independente unele de altele, astfel
crescând posibilitatea reutilizării acestora și testare a independentă a acestora. Astfel, acest concept
presupune conectarea claselor, dar în același timp, clasele să fie independente una față de cealaltă.
Acest concept poate fi privit ca o asociere între două clase.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a
MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movi eFinder;
}
// business logic that actually uses the injected MovieFinder
is omitted…
}
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
8
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinde r}
După definirea dependen țelor, container -ul injectează aceste dependențe în momentul în
care creează bean -ul.
Pachetele org.springframework.beans și org.springframework.context reprezintă
fundația container -ului IoC. Interfața BeanFactory oferă un mecanism cu o configurație avansată
capabil să administreze orice tip de obiect. ApplicationContext este o sub -interfață a lui
BeanFactory, aceasta adăugând :
O integrare mai ușoară cu proprietățile lui Spring AOP.
O manipulare a mesajelor (folosi tă pentru internaționalizare).
Publicarea evenimentelor.
Contexte speciale precum WebApplicationContext care este folosit în aplicațiile
web.
Pe scurt, BeanFactory oferă configurația și funcționalitatea de bază a unei aplicații, iar
ApplicationContext adau gă mai multe funcționalități orientate enterprise.
În Spring, obiectele care formează scheletul aplicației tale și care sunt administrate de către
IoC container se mai numesc și beans. Un bean este un obiect care este instanțiat, asam blat și
administrat de către IoC. Altfel spus, un bean nu este nimic altceva decât unul dintre obiectele care
se află în aplicația ta.
În figura 2. 1 (Company, 2019) este prezentată schema container -ului IoC.
9
Există implementări variate ale interfeței ApplicationContext care sunt furnizate de către
Spring. Într-o aplicație de sine stătătoare, este obișnuit să se creeze o instanță a
ClassPathXmlApplicationContext sau a FileSystemXmlApplicationContext . Deși XML a
reprezentat metoda tradițională pentru definirea configurației, se poate instrui de asemenea
container -ul ca să utilizeze adnotările Java sau cod ca să definească configurația.
2.2 Validation and Data Binding
Există discuții legate de faptul că validarea face parte din logica de busi ness sau nu, dar
Spring oferă o arhitectură pentru validare care nu exclude care nu exclude nicio variantă. Validarea
nu ar trebui să fie legată de nivelul web și ar trebui să fie relativ ușor de localizat. Datorită acestor
considerații, Spring a produs o interfață numită Validator care poate fi utilizată în orice nivel al
aplicației.
Data Binding permite input -ului introdus de către user să fie legat dinamic de modelul
domeniului care aparține unei aplicații. Spring furnizează DataBinder ca să realizeze ac est lucru.
Pachetul Validation, din care fac parte Validator și DataBinder, este folosit în principal de către
MVC framework.
În figura 2. 4 se poate observa un exemplu de implementare al interfeței Validator, mai
precis o clasă este responsabilă cu validar ea datelor unei persoane.
10
public class PersonValidator implements Validator {
/**
* This Validator validates *only* Person instances
*/
public boolean supports( Class clazz) {
return Person.class.equals(clazz);
}
public void validate( Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty ");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue( "age", "negativevalue ");
} else if (p.getAge() > 110) {
e.rejectValue( "age", "too.darn.old ");
}
}
}
2.3 Spring Expression Language (SpEL)
SpEL este un limbaj puternic bazat pe expresii care asigură interogarea și manipularea
grafului de obiecte în timpul rulării. Sintaxa limbajului este similară cu Unified EL dar are niște
caracteristici suplimentare, remarcându -se invocarea metodelor și funcționalități pe șiruri de
caractere. SpEL furnizează funcționalități precum expresii literare, expresii regulate și expresii de
clasă.
Acest limbaj poate fi utilizat și prin configurare cu fișiere XML, și prin configurare bazată
pe adnotări și clase Java.
11
2.4 Aspect Oriented Programming cu Spring
Aspect Oriented Programming (AOP) vine în completarea programării orientate pe ob iecte
prin furnizarea unei alte perspective de a construi structura unui program. Principalul rol este acela
de a oferi modularitate aplicațiilor dezvoltate în Spring.
Una dintre componentele de bază ale Spring -ului este framework -ul AOP. În timp ce
Spring IoC nu este dependent de AOP (adică utilizatorul nu trebuie să folosească AOP dacă nu
vrea), AOP vine în completarea Spring IoC.
AOP este folosit în Spring pentru a :
Furniza servicii enterprise declarative, în special pentru a înlocui serviciile
declarative EJB. Cel mai important serviciu ar fi gestionarea tranzacțiilor
declarative.
Permite utilizatorilor să implementeze aspecte personalizate.
2.5 Arhitectura Spring Framework
Spring este alcătuit din mai multe caracteristici care sunt grupate în aproximativ 20 de
module. Aceste module sunt organizate astfel :
Core Container
o Beans .
o Core – furnizează partea fundamentală a acestui framework. Acesta conține
și caracteristicile descrie anterior, AOP (Aspect of Programming) și DI
(Dependency Injection).
o SpEL .
o Context – se bazează pe modulele oferite de Core și Beans. Acesta se ocupă
de accesarea obiectelor.
Web
12
o WebSocket – reprezintă modulul responsabil de comunicarea între client și
server utilizând comunicarea în ambele direcții , de la serve r către client și
de la client către server.
o Servlet .
o Web – pune la dispoziție anumite caracteristici precum încărcarea fișierelor
și inițializarea container -ului IoC folosind obiecte de tip Servlet și un con-
text al aplicației orientat web.
o Portlet .
Data Access / Integration
o JDBC – este un modul care abstractizează accesarea datelor dintr -o bază de
date, înlăturând anteriorul și incomodul cod JDBC.
o ORM – furnizează suport pentru integrarea renumitelor Object Relational
Mapping (ORM) API -uri precum JPA și Hibernate.
o OXM – pune la dispoziție suport pentru implementări ale Object/XML
mapping, cum ar fi JAXB și Castor.
o JMS – conține caracteristici care se ocupă de producerea și procesarea
mesajelor .
o Transactions – oferă suport pentru gestionarea tranzacțiilor .
AOP .
Instrumentation .
Aspects – acest modul facilitează integrarea cu AspectJ.
Messaging – conține adnotări pentru maparea mesajelor către metode .
Test – oferă suport pentru unit testing și integration testing a componentelor Spring
folosind Junit sau TestNG.
Modulele care alcătuiesc framework -ul Spring sunt expuse în Figură 1.
13
Figură 1 Spring Framework
14
CAPITOLUL 3
SPRING MVC
Spring web mvc este construit pe API -ul Servlet și a reprezentat încă de la început un
modul de bază al framework -ului Spring. Numele este dat de către modelul sursă, spring-webmvc ,
dar este mai cunoscut ca și Spring MVC.
“Open for extension… ” este un princip iu de bază în Spring Web MVC, “Open for exten-
sion, closed for modification” fiind un principiu de bază comun Framework -ului Spring.
Unele metode din clasele nucleu ale Spring Web MVC sunt declarate ca fiind final. Un
dezvoltator nu poate suprascrie acest e metode pentru a le furniza un comportament propriu .
Acest modul pune la dispoziție un suport consistent șablonului de proiectare Model View
Controller (MVC), oferind caracteristici precum internaționalizarea și validarea care ușurează
implementarea ni velului de prezentare.
3.1 Șablonul de proiectare MVC
Model View Controller (MVC) este un șablon de proiectare folosit pentru a separa aplicația
în trei părți interconectate între ele, scopul fiind de a separa partea logică și reprezentarea
informației de modul în care aceasta este prezentată utilizatorului. Acest șablon de proiectare
decuplează aceste componente majore facilitând reutilizarea eficientă a codului și dezvoltarea în
paralel.
Această arhitectură a devenit pop ulară în realizarea aplicațiilor web, majoritatea limbajelor
populare din ziua de astăzi precum Java, C# și Python deținând framework -uri MVC care sunt
folosite încă de la începutul dezvoltării unei aplicații web.
Componentele acestui șablon de proiectar e sunt :
15
Model – Reprezintă componenta centrală a acestui șablon, fiind o structură de date
dinamică și independentă de interfața cu utilizatorul, care gestionează aceste date și starea
aplicației. Administrează direct datele, logica și structura aplicație i.
View – Constă în orice reprezentare a informației, precum diagrame, tabele sau grafice,
făcând posibilă reprezentarea vizuală în moduri multiple a aceleași informații.
Controller – Acesta gestionează cererile făcute de către utilizator în interfața graf ică,
interacționează cu nivelul de servicii, actualizează modelul și direcționează utilizatorul
către view -ul care conține rezultatul execuției cererilor.
Pe lângă împărțirea aplicației în aceste componente, arhitectura MVC definește și
interacțiunea dintr e aceste modele. Modelul este însărcinat cu gestionarea datelor aplicației, el
recepționând comenzi trimise de către controller. View -ul se ocupă de prezentarea informației
modelului într -un format particular. Controller -ul recepționează input -ul utilizato rului, opțional îl
validează și apoi efectuează operațiile pe obiectele model.
(spring mvc cook book, pagina 68)
16
Scopurile șablonului de proiectare MVC sunt :
Dezvoltarea simultană – fiind realizată decuplarea componentelor aplicației, se poate lucra
simultan la module diferite ale aplicației fără a exista riscul de a bloca pe altcineva în timpul
dezvoltării. Spre exemplu, o echipă se poate împărți în oameni car e se ocupă de partea de
front end și în oameni care se ocupă de partea de back end. Oamenii de pe partea de back
end pot structura aplicația fără a avea nevoie de o interfață cu utilizatorul.
Reutilizarea codului – Prin crearea unor componente care sunt in dependente una față de
cealaltă, programatorii sunt capabili să reutilizeze componente rapid și ușor în alte aplicații.
Avantajele acestui șablon de proiectare sunt : dezvoltarea simultană, coeziunea ridicată
(MVC permite gruparea logică a acțiunilor corel ate într -un singur controller), cuplarea redusă și
ușurința prin care se pot realiza modificări.
Dezavantajele acestuia sunt parcurgerea codului, menținerea consistenței și nevoia de a
cunoaște mai multe tehnologii.
3.2 Fluxul datelor în Spring MVC
Spring MVC implementează un șablon front controller. Punctul de intrare al aplicației este
DispatcherServlet -ul. DispatcherServlet -ul se bazează pe o implemen tare HandlerMapping .
Utilizând diferite strategii și specificații, HandlerMapping -ul atribuie o metodă de tip handler din
Controller care să gestioneze cererea.
Odată ce DispatcherServlet -ul deține o metodă de tip handler a Controller -ului, acesta
trimite cererea către aceasta. Metoda returnează un nume de View (sau direct View -ul) și de
asemenea o biectul modelului populat către DispatcherServlet.
Cu numele View -ului, DispatcherServlet -ul cere unei implementări ViewResolver și să
selecteze un View.
Având cererea, un View și un Model, DispatcherServlet -ul deține tot ceea ce are nevoie
pentru a co nstrui un răspuns pentru client. View -ul este procesat alături de toate elementele și
răspunsul este în cele din urmă returnat către servlet -container.
17
Fluxul datelor în Spring MVC poate fi reprezentat ca în figura următoare :
3.3 DispatcherServlet
Spring MVC, asemenea multor framework -uri web, este construit în jurul unui controller
principal care este reprezentat de un Servlet, DispatcherServlet -ul, care furnizează un algoritm
pentru procesarea cererilor, în timp ce acestea sunt executate de către componentele configurabile.
18
DispatcherServlet -ul, ca și orice alt Servlet, trebuie să fie declarat și conectat corespunzător
specificațiilor unui Servlet utilizând configurare Java sau configurarea în fișierul web.xml. În
schimb, DispatcherServlet -ul uti lizează configurația Spring pentru a localiza componentele
necesare pentru gestionarea cererilor, afișarea view -ului corespunzător, tratarea excepțiilor
ș.a.m.d.
Mai jos este prezentat un exemplu de configurare xml.
<web-app>
<listener>
<listener –
class>org.springframework.web.context.ContextLoaderListener </listener –
class>
</listener>
<context -param>
<param-name>contextConfigLocation </param-name>
<param-value>/WEB-INF/app-context.xml </param-value>
</context -param>
<servlet>
<servlet -name>app</servlet -name>
<servlet –
class>org.springframework.web.servlet.DispatcherServlet </servlet –
class>
<init-param>
<param-name>contextConfigLocation </param-name>
<param-value></param -value>
</init-param>
<load-on-startup> 1</load-on-startup>
</servlet>
<servlet -mapping>
19
<servlet -name>app</servlet -name>
<url-pattern> /app/*</url-pattern>
</servlet -mapping>
</web-app>
3.3.1 Ierarhia Contextelor
DispatcherServlet -ul necesită un WebApplicationContext , care este o extensie a
ApplicationContext -ului, pentru configurația proprie.
În majoritatea aplicațiilor web este suficient să existe un singur WebApplicationContext.
Este posibil de asemenea ca o aplicație să conțină o ierarhie a contextelor cu un
WebApplicationContext ca și rădăcină, care să fie distribuit în restul instanțelor u nui
DispatcherServlet, care la rândul lor să aibă o configurație WebApplicationContext.
WebApplicationContext -ul rădăcină conține infrastructura bean -urilor, precum serviciile
business și gestiunea datelor, care trebuie să fie distribuite prin instanțele multiple ale unui
Servlet.
(poza din pro spring 5, figura 16 -2)
20
3.3.2 Tipuri speciale de bean -uri
DispatcherServlet -ul delegă procesarea cererilor către anumite bean -uri speciale și
interpretează răspunsurile corespunzătoare. Denumirea de “bean special” este utilizată pentru a
descrie instanțelor obiectelor administrate de către Spring, care implementează un contract
WebFlux.
Tipuri de bean -uri speciale :
21
HandlerMapping – Direcționează o cere re și o listă de interceptori de procesare către un
manipulator. Direcționarea se bazează pe un criteriu, detaliile fiind diferite de la o
implementare la alta. Cele 2 implementări de bază sunt RequestMappingHandlerMapping
(care conține adnotarea @ RequestMapping ) și SimpleHandlerMapping .
HandleAdapter – Vine în ajutorul DispatcherServlet -uluim el fiind responsabil spre
exemplu de detaliile invocării unui manipulator, protejând DispatcherServlet -ul de astfel
de detalii.
HandleExceptionResolver – Strategie pentru manipularea și tratarea excepțiilor.
LocaleResolver – Ajută la implementări precum internaționalizarea.
ThemeResolver – Gestionează tematicile pe care le poate folosi o aplicație.
MultipartResolver – Abstractizează procesarea unei cereri formate din mai multe părți.
FlashMapManager – Administrează fluxul input -urilor și output -urilor dintre cereri
Aplicațiile pot declara infrastructura listei de bean -uri speciale care sunt necesare pentru
procesarea cererilor. DispatcherServlet -ul caută în mod WebApplicationContext bean -uri speciale.
Dacă nu găsește , se întoarce la valorile standard înlănțuite în DispatcherServlet.properties.
În cele mai multe cazuri, MVC Config -ul este cel mai bun punct de start. Acesta declară
bean -urile necesare, în format Java sau XML, și furnizează un callback API personalizabil .
3.4 Annotated Controllers
Spring MVC furnizează un model programabil bazat pe adnotări unde componentele
@Controller și @RestController utilizează adnotări pentru a gestiona cererile. Un Controller care
este adnotat are semnături flexibile ale metodelor și nu este nevoie de implementarea unor interfețe
speciale sau de moștenirea de la niște clase de bază.
Mai jos este prezentat un exempl u de Controller definit de adnotări : (spring doc 5.1.3,
annotated controllers)
@Controller
public class HelloController {
22
@GetMapping ("/hello" )
public String handle(Model model) {
model.addAttribute( "message" , "Hello World!" );
return "index";
}
}
În exemplul anterior, metoda primește un Model ca și parametru și returnează numele unui
view care este format dintr -un șir de caractere.
3.4.1 Declararea
Se pot defini mai multe bean -uri de tip controller utilizând o definiție standard în
WebApplicationContext. Adnotarea @Controller permite detectarea automată, împreună cu
suportul general Spring pentru detectarea claselor adnotate cu @Component și le înregistrează
automat definițiile. De asemenea, se com portă ca un stereotip pentru clasa adnotată, indicând rolul
acesteia ca și componentă web.
Pentru a activa detectarea automată a bean -urilor precum @Controller, se poate adaugă
scanarea componentelor în configurația Java, așa cum este arătat în următorul exemplu :
@Configuration
@ComponentScan ("org.example.web" )
public class WebConfig {
// …
}
(Exemplu luat din spring doc 5.1.3/ annotated controller / declaration)
Echivalentul exemplului precedent în format XML este următorul : (sub cel de sus)
23
<?xml version="1.0" encoding="UTF -8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema -instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context ="http://www.springframework.org/schema/context"
xsi:schemaLocation ="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring –
beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring –
context.xsd" >
<context:component -scan base-package="org.example.web" />
<!– … –>
</beans>
3.4.2 Request Mapping
Adnotarea @RequestMapping este utilizată pentru a distribui cererile către metodele aflate
în controller. Deține atribute variate pentru a se potrivi prin URL, metoda HTTP, parametrii cererii
și tipuri media. Se poate folosi la nivel de clasă pentru a exprima distribuirile, sau la nivel de
metodă pentru a restrânge la o ges tionare particulară.
Există de asemenea variante ale adnotării @RequestMapping care sunt specifice pentru un
scop mai exact al metodelor HTTP :
@GetMapping.
@PostMapping.
24
@PutMapping.
@DeleteMapping.
@PatchMapping.
Aceste prescurtări sunt adnotări personalizate care sunt furnizate deoarece majoritatea
metodelor unui controller ar trebui conectate corespunzător unei metode specifice HTTP, în timp
ce adnotarea @RequestMapping, în mod implicit, se potrivește cu toate metodele HTTP. În același
timp, @Re questMapping este necesară la nivel de clasă pentru a exprima conectările.
Următorul exemplu conține distribuiri la nivel de clasă și de metode : (spr 5.1, request m)
@RestController
@RequestMapping ("/persons" )
class PersonController {
@GetMapping ("/{id}")
public Person getPerson( @PathVariable Long id) {
// …
}
@PostMapping
@ResponseStatus (HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// …
}
}
3.5 Handler Methods
Metodele de tip handler adnotate cu @RequestMapping au o semnătură a funcției flexibilă
și pot selecta dintr -o gamă variată de argumente și de valori ce pot fi returnate. Metodele pot
25
conține argumente precum : @RequestParam , @PathVariable , RedirectAttribut es, @ModelAt-
tribute sau BindingResult .
Exemple de valori ce pot fi returnate de către o metodă de tip handler :
String – Numele unui view care urmează să fie gestionat cu implementările din
ViewResolver și folosit împreună cu modelul implicit, determinat prin metode ce conțin
@ModelAttribute.
View – O instanță de tip View care este interpretată împreună cu modelul implicit,
determinat cu ajutorul obiectelor de tip comandă.
@ModelAttribute – Atributul care este adăugat în mod implicit modelului.
ModelAndView – Atributele model și view necesare, și, eventual, o stare a răspunsului.
Void – O metodă care returnează tipul void (sau null) este considerată ca fiind o metodă
care a gestionat total răspunsul.
3.5.1 @PathVariable
@PathVariable este o adnotare care se folosește pentru a accesa valorile variabilelor URI
(Uniform Resource Identifier). Variabilele URI pot fi declarate și la nivel de clasă, și la nivel de
metodă, precum este exemplificat în următoarea secvență de cod : (request mapping, URI patterns)
@Controller
@RequestMapping ("/owners/{ownerId}" )
public class OwnerController {
@GetMapping ("/pets/{petId}" )
public Pet findPet( @PathVariable Long ownerId, @PathVariable
Long petId) {
// …
}
}
26
3.5.2 @RequestParam
@RequestParam este utilizată pentru a lega parametrii unei cereri (parametrii interogării sau
date formale) către argumentele unei metode din controller. Exemplul următor arată cum se poate
folosi . (request mapping, request param)
@Controller
@RequestMappi ng("/pets")
public class EditPetForm {
// …
@GetMapping
public String setupForm( @RequestParam ("petId") int petId ,
Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute( "pet", pet);
return "petForm" ;
}
// …
}
3.5.3 @ModelAttribute
Accesarea unui atribut al unui model sau instanțierea acestuia, dacă nu a avut loc deja, se
pot realiza cu ajutorul adnotării @ModelAttribute. Atributul model este în același timp suprapus
cu valori ale para metrilor ale unei cereri HTTP Servlet, ai căror nume se potrivesc cu numele
câmpurilor. Acest procedeu se referă la Data Binding, și face posibilă evitarea procesării și
27
convertirii fiecărui parametru al interogării. Această adnotare se folosește în modul următor : (urm
ex)
@PostMapping ("/owners/{ownerId}/pets/{petId}/edit" )
public String processSubmit( @ModelAttribute Pet pet) { }
Instanța clasei de tip Pet este creată precum urmează :
– Prin model dacă a fost deja adăugat folosind Model.
– Prin sesiunea HTTP utilizând adnotarea @SessionAttributes .
– Prin variabila URI transmisă printr -un Converter.
– Prin invocarea constructorului implicit.
– Prin invocarea constructorului cu argumente.
3.5.4 Redirect Attributes
Pentru a evita trimiterea de par ametrii nedoriți, o metodă de tip @RequestMapping se poate
declara un atribut de tip RedirectAttributes care să specifice în mod explicit ce atribute să fie
disponibile pentru RedirectView. Dacă metoda efectuează o redirecționare, conținutul deținut de
RedirectAttributes este folosit. În caz contrar, este folosit conținutul modelului.
3.5.5 Binding Result
Se folosește pentru a accesa erorile provenite din validarea și legarea unui obiect comandă
(un argument @ModelAttribute), sau erori rezultate în urma validării argumentelor metodelor
@RequestBody sau @RequestPart . Un argument de tip BindingResult trebuie declarat imediat
după validarea argumentului unei metode.
28
3.6 Tehnologiile View
Utilizarea tehnologiilor de view în Spring MVC este flexibilă, programatorul având
libertatea de a schimba tehnologia pe care o folosește în orice moment al dezvoltării apl icației,
fiind nevoie doar de o modificare a configurației.
3.6.1 JSP și JSTL
Spring Framework conține o integrare incorporată pentru utilizarea Spring MVC cu JSP
(Java Server Page) și JSTL (Java Standard Tag Library).
Pentru dezvoltarea cu JSP -uri, se poate declara un InternalResourceViewResolver sau
un ResourceBundleViewResolver .
ResourceBundleViewResolver se bazează pe fișierul de proprietăți ca să definească numele
view -urilor transmise către o clasă sau un URL. Cu ResourceBundleViewResolver se pot combina
diferite tipuri de view -uri folosind un singur manipulator, precum este exemplificat în exemplul
următor : (spring web mvc, view tech, jsp and jstl)
<!– the ResourceBundleViewResolver –>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver
">
<property name="basename" value="views"/>
</bean>
InternalResourceViewResolver este folosit de obicei pentru fișiere de tip JSP. Ca o practică
bună, se recomandă plasarea fișierelor JSP într -un director care se află în ‘WEB -INF’, ca să fie
restricționat accesul clienților. (după cel de sus)
<bean id="viewResolver"
29
class="org.springframework.web.servlet.view.InternalResourceViewR
esolver" >
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp"/>
</bean>
Atunci când se utilizează Java Standard Tag Library trebuie folosită o clasă specială,
JstlView , JSTL necesitând pregătire precedentă implementărilor caracteristicilor precum
internaționalizarea.
Pentru a facilita dezvoltarea utilizând paginile JSP în combinație cu caracteristicile Data
Binding, Spring furnizează câteva tag -uri care ușurează lucrurile. Librăria spring.tld (TLD – Tag
Library Descriptor) este inclusă în pachetul spring -webmvc.jar.
Cu versiunea 2.0, Spring a creat un set de tag -uri pentru gestionarea elementelor formelor
în timpul utilizării JSP și Spring Web MVC, numit Spring Form Tag Library. Fiecare tag
furnizează suport pentru un set de atribute corespondente tag -urilor din HTML, făcând tag -urile
familiare și intuitive la implementare.
Spre deosebire de alte librării, librăria Spring Form este integrată cu Spring Web MVC,
oferind acces tag -urilor la obiectul comandă și la date.
Pentru a utiliza Spring Tag Form Library, trebuie adă ugată la începutul paginii JSP
următoarea secvență :
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"
%>
După cum se poate vedea în exemplul anterior, “form” reprezintă prefixul numelui tag -ului
care se dorește a fi utilizat. Acest t ag interpretează tag -ul ‘form’ din HTML. Acesta pune obiectul
comandă în PageContext pentru a permite obiectului comandă să fie accesat de tag -urile interioare.
Toate celelalte tag -uri din această librărie sunt tag -uri care extind tag -ul form.
30
Exemplul ur mător se adresează unui obiect numit User. Este un bean Java care este
caracterizat prin proprietăți precum firstName și lastName. Acest bean poate fi utilizat ca fiind un
model, care ar fi gestionat de către un controller, care ar returna un fișier form.j sp. Fișierul
form.jsp ar putea arăta în felul următor:
<form:form>
<table>
<tr>
<td>First Name: </td>
<td><form:input path="firstName" /></td>
</tr>
<tr>
<td>Last Name: </td>
<td><form:input path="lastName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
Valorile câmpurilor firstName și lastName sunt preluate de la obiectul de comandă care
este plasat în PageContext de către Page Controller. Echivalentul exemplului precedent scris
folosind doar HTML ar fi :
<form method="POST">
<table>
<tr>
<td>First Name: </td>
<td><input
name="firstName" type="text" value="Harry"/></td>
31
</tr>
<tr>
<td>Last Name: </td>
<td><input
name="lastName" type="text" value="Potter" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form>
Există tag -uri predefinite pentru acțiuni comune, precum : checkboxes, radiobutton,
password, select, option, ș.a.m.d., acestea fiind precedate de tag -ul form.
32
CAPITOLUL 4
SPRING JPA
4.1 Spring Data
Spring JPA (Java Persistence API) face parte din Spring Data. Spring Data reprezintă unul
din modulele de bază din Spring, sarcina acestuia fiind aceea de a furniza un mod familiar și
consistent de a accesa datele stocate.
Spring Data oferă o modalitate simplă de a utiliza tehnologiile de accesare a datelor dintr –
o bază de date, relațională sau nu, sau a serviciilor cloud. Spring Data conține mai multe tehnologii
folosite pentru diverse tipuri de baze de date, precum : JPA, JDBC și REST.
Caracteristici ale Spring Data :
Abstractizări personalizate ale distribuirii obiectelor.
Crearea de interogări dinamice pe numele metodelor aflate în repository.
Proprietățile de bază furnizate de către implementările claselor de bază .
Suport pentru controlul transparent.
Posibilitatea integrării unui cod al repository -ului personalizat.
Integrarea simplă utilizân d JavaConfig și namespace -urile XML.
Integrare avansată cu Spring MVC.
Câteva module principale care aparțin Spring Data sunt :
Spring Data JDBC – oferă un repository suport pentru JDBC.
Spring JPA – oferă un repository suport pentru integrarea cu JPA.
Spring Data MongoDB – oferă suport și documentație Spring pentru baza de date
MongoDB.
33
Spring Data REST – implementează serviciile REST.
4.2 ORM Data Access
Framework -ul Spring oferă integrare cu Java Persistence API și Hibernate, acestea fiind
utilizate la gestionarea resurselor, la implementările Data Access Object (DAO) și la strategiile
tranzacțiilor. Spre exemplu, Hibernate conține o clasă suport cu caracteristici similare ale IoC. Se
pot configura toate caracteristicile necesare pentru distribuirea instrumentelor OR (object
relational) utilizând Dependency Injection. Ele pot participa în administrarea datelor și
tranzacțiilor gestionate de către Spring, venind în completarea tranzacțiilor generice ale lui Spring
și a ierarhiilor DAO. DAO este stilul de integrare recomandat în aceste tipuri de aplicații.
Spring adaugă îmbunătățiri considerabile ORM -ului în dezvoltarea aplicațiilor care
accesează date. Se poate utiliza suportul ORM asemenea unei librării, deoarece totul este structurat
ca fiind un set de bean -uri Java reutilizabile. ORM în container -ul Spring IoC facilitează
configurarea și dezvoltarea.
Beneficiile aduse de utilizarea Spring Framework în crearea obiectelor de tip DAO sunt :
Testarea mai ușoară – Abordarea Spring IoC ușurează schimbarea implementărilor și a
locațiilor configurațiilor instanțelor de tip SessionFactory , DataSource și a
administratorilor de tranzacții.
Excepții generale la accesarea datelor – Spring poate împacheta excepțiile venite de la
ORM și să le convertească din excepț ii neprevăzute într -o excepție de tip
DataAccessException.
Un mod general de a administra resursele – Contextele unei aplicații Spring pot
manipula locația și configurația instanțelor de tip SessionFactory, EntityManagerFactory
și DataSource. Acest lucru ușurează gestionarea și modificarea acestor valori.
Integrarea administrării tranzacțiilor – Codul O RM poate fi împachetat cu stilul Aspect
Oriented Programming (AOP) utilizând adnotarea @Transactional sau configurând în
mod explicit AOP în fișierul de configurare XML.
34
4.3 Spring Data JPA
Interfața centrală din JPA este Repository, aceasta preluând clasa de bază pentru a o
administra. Interfața Repository vine în ajutorul dezvoltatorului pentru a descoperi interfețe care o
pot extinde pe aceasta. Interfața CrudRepository furnizează funcțion alități CRUD (Create Read
Update Delete) avansate entității care este administrate.
Interfața CrudRepository : (spring jpa doc, 4.1 core concepts, example 3)
public interface CrudRepository <T, ID extends Serializable >
extends Repository <T, ID> {
<S extends T> S save(S entity );
Optional <T> findById (ID primaryKey );
Iterable <T> findAll();
long count();
void delete(T entity );
boolean existsById (ID primaryKey );
// … more functionality omitted.
}
Save – salvează entitatea primită ca și parametru. FindById – returnează entitatea
identificată prin id -ul primit. findAll – returnează toate entitățile. Count – returnează numărul
entităților. Delete – elimină entitatea primită. ExistsById – verifică dacă există o entitate cu
identificatorul respectiv.
35
PagingAndSortingRepository reprezintă o abstractizare care adaugă metode adiționale
celor din CrudRepository pentru a ușura accesul paginat al acestor entități.
Interfața PagingA ndSortingRepository : (sub cel de sus)
public interface PagingAndSortingRepository <T, ID extends
Serializable >
extends CrudRepository <T, ID> {
Iterable <T> findAll(Sort sort);
Page<T> findAll(Pageable pageable );
}
Spre exemplu, accesarea celei de a doua pagini, care conține 20 de elemente, se poate
efectua în mod următor :
PagingAndSortingRepository <User, Long> repository = // … get access
to a bean
Page<User> users = repository .findAll(PageRequest .of(1, 20));
4.4 Query Methods
Funcționalitățile standard CRUD din repository de obicei conțin interogări care stau la baza
administrării unei baze de date. Cu Spring Data JPA, declararea acestor interogări se realizează în
4 pași :
1. Declararea unei interfețe care extinde Repository, sau una din interfețel e derivate din
aceasta, tipul clasei de bază și tipul identificatorului. (query methods)
interface PersonRepository extends Repository <Person, Long> { … }
2. Declararea unor metode care să efectueze interogări în interfața creată.
36
interface PersonRepository extends Repository <Person, Long> {
List<Person> findByLastname (String lastname );
}
3. Crearea instanțelor de tip Repository, fie cu JavaConfig sau XML.
a. Pentru configurarea Java, se poate crea o clasă asemănătoare următoarei :
import
org.springframework .data.jpa.repository .config.EnableJpaRepositories ;
@EnableJpaRepositories
class Config {}
b. Prin configurare XML, care va fi exemplificată mai târziu în acest capitol.
4. Injectarea instanței repository -ului și utilizarea acesteia.
class SomeClient {
private final PersonRepository repository ;
SomeClient (PersonRepository repository ) {
this.repository = repository ;
}
void doSomething () {
List<Person> persons = repository .findByLastname ("Matthews" );
}
}
37
4.4.1 Definirea interfețelor de tip Repository
Prima dată trebuie declarată o interfață care trebuie să extindă Repository și să fie legată
de clasa de bază. Pentru accesarea metodelor CRUD trebuie extinsă interfața CrudRepository.
De obicei, interfața creată va extinde Repository, CrudRe pository sau
PagingAndSortingRepository. Spring Data JPA permite de asemenea dezvoltatorului, în cazul în
care nu dorește să implementeze una din interfețele enumerate anterior, să -și definească propria
interfață repository, adnotând -o cu @RepositoryDefini tion . CrudRepository furnizează un set
complet de metode care pot fi utilizate la manipularea entităților.
Utilizarea unui modul unic din Spring Data simplifică lucrurile, deoarece toate interfețele
repository sunt trimise către modulul respectiv.
4.4.2 Definirea metodelor care efectuează interogările
Proxy -ul are 2 opțiuni pentru definirea unei interogări din numele unei metode :
Prin derivarea interogării direct din numele metodei.
Prin definirea manuală a interogării.
Mecanismul de creare al interogărilor din Spring Data este util la scrierea interogărilor
bazate pe constrângeri. Mecanismul separă prefixele find…By, read…By, query…By, count…By și
get…By de metodă și începe să proceseze ce a rămas. Partea de început poate să conțină a lte
constrângeri, precum Distinct. Mai jos se pot observa câteva exemple de interogări : (query
creation)
interface PersonRepository extends Repository <User, Long> {
List<Person> findByEmailAddressAndLastname (EmailAddress
emailAddress , String lastname );
// Enables the distinct flag for the query
38
List<Person>
findDistinctPeopleByLastnameOrFirstname (String lastname , String
firstname );
List<Person>
findPeopleDistinctByLastnameOrFirstname (String lastname , String
firstname );
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase (String lastname );
// Enabling ignoring case for all suitable properties
List<Person>
findByLastnameAndFirstnameAllIgnoreCase (String lastname , String
firstname );
// Enabling static ORDER BY for a query
List<Person>
findByLastnameOrderByFirstnameAsc (String lastname );
List<Person>
findByLastnameOrderByFirstnameDesc (String lastname );
}
Rezultatul procesării metodei depinde de gestionarul de persistență căruia i -a fost adr esată
interogarea. Totuși, există câteva caracteristici comune :
Expresiile conțin de obicei o traversare a proprietăților combinate cu operatori care
pot fi concatenate. Expresiile pot fi combinate folosind operatori precum AND și
OR. De asemenea, se pot utiliza operatori precum Between, LessThan, GreaterThan
și Like.
Procesatorul metodei permite utilizarea unui IgnoreCase pentru proprietățile
individuale (spre exemplu, findByLastNameIgnoreCase(…)) sau pentru toate
proprietățile ale căror tip suportă IgnoreCase (de obicei instanțele de tip șir de
caractere – exemplu : findByLastNameAndFirstNameAllIgnoreCase(…)).
39
Se poate aplica o ordonare statică prin atașarea secvenței OrderBy care furnizează
o directivă de sortare (ASC sau DESC) a unei proprietăți specificate.
Expresiile care conțin proprietăți pot face referire la o singură proprietate. În momentul
creării interogării, trebuie verificat dacă proprietatea transmisă face parte din proprietățile clasei
de bază. (4.4.3 property expressions)
List<Person> findByAddressZipCode (ZipCode zipCode);
Exemplul precedent exempli fică accesarea proprietății ZipCode, proprietate care face parte
din Address, care la rândul ei este o proprietate a entității Person (echivalent cu
x.address.zipCode). Ca să nu existe ambiguități, spre exemplu entitatea Person ar putea avea și o
proprieta te ZipCode, se poate folosi simbolul _ în interiorul numelui metodei pentru a defini
manual traversarea. (sub cel de sus)
List<Person> findByAddress_ZipCode (ZipCode zipCode);
Pe lângă gestionarea parametrilor unei interogări cum a fost exemplificat anterior,
infrastructura permite și utilizarea unor tipuri speciale, precum Pageable and Sort, pentru a aplica
paginări și sortări interogărilor în mod dinamic. (sub cel de sus)
Page<User> findByLastname (String lastname , Pageable pageable );
Slice<User> findByLastname (String lastname , Pageable pageable );
List<User> findByLastname (String lastname , Sort sort);
List<User> findByLastname (String lastname , Pageable pageable );
40
Prima metodă efectuează trimiterea unei instanțe de tip Pageable către interogare și
realizează paginarea în mod dinamic a rezultatelor interogării. O instanță de tip Page știe de
numărul total de elemente și de numărul disponibil de pagini.
Rezultatele acestor interogări pot fi limitate folosind cuvinte cheie precum first sau top. Pe
lângă aceste cuvinte cheie, se poate adăuga o valoare numerică care să seteze numărul maxim de
elemente care poate fi returnat. (sub cel de sus)
User findFirstByOrderByLastnameAsc ();
User findTopByOrderByAgeDesc ();
Page<User> queryFirst10ByLastname (String lastname , Pageable
pageable );
Slice<User> findTop3ByLastname (String lastname , Pageable
pageable );
List<User> findFirst10ByLastname (String lastname , Sort sort);
List<User> findTop10ByLastname (String lastname , Pageable
pageable );
4.4.3 Crearea instanțelor de tip repository
Configurare XML
Fiecare modul din Spring Data conține un element numit repositories care permite definirea
unui pachet de bază pe care Spring să -l scaneze în căutarea interfețelor de tip Repository.
41
<?xml version ="1.0" encoding ="UTF-8"?>
<beans:beans
xmlns:beans ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema -instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation ="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring -beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring –
jpa.xsd" >
<repositories base-package="com.acme.repositories" />
</beans:beans>
Java Config
Infrastructura unui repository poate de asemenea să fie declanșată utilizând adnotarea
@Enable$ {Store} Repositories pe o clasă Java.
@Configuration
@EnableJpaRepositories ("com.acme.repositories" )
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory () {
// …
}
}
42
CAPITOLUL 5
APLICAȚIE. Martial Arts Manager
Martial Arts Manager este o aplicație web care permite gestionarea cluburilor afiliate unei
federații de arte marțiale. Această aplicație a fost creată cu scopul de a ușura administrarea
structurii interioare a unei federații de arte marțiale .
O federație de arte marțiale este formată din mai multe cluburi, iar aceste cluburi la rândul
lor p ot avea mai mulți sportivi afiliați. Există de asemenea în aplicație și o secțiune dedicată
diverselor tipuri de evenimente , aceste evenimente fiind gestionate de către un administrator , iar
un sportiv afiliat unui club poate participa la unul din aceste evenimente.
Aplicația a fost dezvoltată în mediul de dezvoltare Spring Tool Suite (STS), acesta fiind un
mediu de dezvoltare bazat pe structura mediului Eclipse și oferă un suport personalizat pentru
dezvoltarea aplicațiilor scrise în Spring.
5.1 Structura aplicației
Aplicația este structurată și abstractizată pe mai multe nivele, abstractizarea pe mai multe
nivele oferind un control mai amplu al fluxului datelor în aplicație și posibilitatea de a modifica
un nivel al aplicației fără să fie nevoie ca să fie modificate și restul nivelelor.
Clasele scrise în limbajul Java sunt grupate în pachete, acestea fiind :
43
Controller – Acest pachet conține toate clasele de tip Cont roller care se ocupă de
gestionarea cererilor primite de la server.
Controller.Utils – Este un pachet dedicat claselor ajutătoare celor din pachetul
Controller.
DTO – Reprezintă pachetul care este alcătuit din clasele care sunt destinate
transferului de da te (Data Transfer Object) dintre baza de date și aplicație.
Exception – Acest pachet conține excepțiile care pot fi “aruncate” pe parcursul
execuției programului. (DuplicateRecordException, RecordNotFoundException)
Model – Este un pachet dedicat claselor d e tip Business ale aplicației, acesta
conținând entitățile care urmează să fie salvate în baza de date.
Model.Utils – Este un pachet dedicat claselor ajutătoare celor din pachetul Model,
conținând enumerații precum DegreeType.
Repository – Acest pachet con ține interfețele care se ocupă de operațiile aplicate
bazei de date.
Service – Reprezintă pachetul care conține clasele ce implementează metodele din
interfețele din pachetul Repository și care sunt utilizate în pachetul Controller.
Dacă, la un moment dat , se dorește schimbarea bazei de date utilizată pentru această
aplicație, acest lucru se poate realiza foarte ușor prin simpla modificare a interfețelor din pachetul
Repository, nefiind nevoie de schimbări la celelalte nivele ale aplicației. De asemenea, d acă se
dorește modificarea tehnologiei utilizate pentru crearea interfeței cu utilizatorul, acest lucru se
poate efectua fără a fi nevoie de a modifica logica aplicației.
În pachetul src/main/resources se află fișierele de proprietăți și cele de configurare ale
aplicației, precum jpaContext.xml , care conține detaliile legate de motorul bazei de date , de
administratorul tranzacțiilor și de clasele ce trebuie să fie explorate.
Directorul WEB -INF conține :
Fișierul web.xml , fișier în care este implementată configurarea entităților de tip
Servlet.
Fișierul dispatcher -servlet.xml , fișier în care este configurată modalitatea de
explorarea a fișierelor necesară detectării resurselor precum fișiere CSS sau
JavaScr ipt.
44
Directorul JSP. În acest director se află toate fișierele cu extensia .jsp, acestea
reprezentând paginile care pot fi accesate în această aplicație.
Structura fișierelor poate fi vizualizată în figura următoare :
5.2 Funcționalitățile aplicației
45
Aplicația poate fi folosită de un vizitator, de un sportiv care aparține unui club sau de un
administrator.
Un vizitator , adică o persoană care nu este administrator și nu reprezintă nici un sportiv
afiliat unui club, îi sunt permise următoarele drepturi :
Poate să vadă detalii despre cluburile afiliate federației.
Poate să vadă membrii care aparțin unui anumit club.
Poate să vadă performanțele unui sportiv dorit.
Poate să vadă o listă cu diversele evenimente care urmează să se desfășoare și să
vadă detalii despre acestea.
Poate să vadă tipurile de grade existente în federație.
Poate să caute prin listele de cluburi, sportivi sau evenimente și poate să -și sorteze
rezultatele căutării.
Un sportiv care aparține unui club , pe lângă ceea ce poate face un vizitato r oarecare, poate
de asemenea :
Să se autentifice cu datele personale salvate în baza de date.
Să-și editeze detaliile personale precum nume, vârstă, email, etc.
Să se înscrie la unul din evenimentele disponibile.
Administratorul reprezintă punctul principa l al aplicației , acesta având acces la toate
resursele aplicației și drepturi depline asupra tuturor funcționalităților puse la dispoziție de către
aplicație :
Adăugare, modificare și ștergere a cluburilor afiliate federației.
Gestionarea evenimentelor.
Adm inistrarea sportivilor afiliați unui club.
Gestionarea performanțelor unui sportiv.
Administrarea tipurilor de grade existente în federație . (Un sportiv poate deține spre
exemplu gradul “Centur ă maro 1 Kyu” , dar tipurile gradelor și culorile centurilor
pot varia de la un club la altul)
Administratorul poate realiza toate aceste operații doar în urma autentificării. Acesta
accesează pagina de autentificare, iar dacă datele sunt valide, acesta va fi autentificat cu succes și
va fi redirecționat către pagina p rincipală.
46
Același lucru este valabil și pentru un sportiv. Acțiunile de editare a detaliilor personale și
de participare la un eveniment nu pot fi realizate dacă sportivul nu este autentificat.
5.3 Modulele aplicației
În procesul de dezvoltare al acestei aplicații au fost utilizate următoarele module :
1. Modulul Spring MVC este folosit pentru separarea logicii aplicației de ceea ce
aparține de interfața cu utilizatorul și pentru a oferi caracteristici de integrare
orientate web .
2. Modulul JPA sau Java Persistence API este folosit pentru accesarea și modificarea
informațiilor din baza de date, legate de cluburi, sportivi, etc.
3. Modulul Spring Security este folosit pentru aplicarea constrângerilor de securitate
și pentru implementarea procesului de autenti ficare .
4. Tehnologia JSP este folosită pentru crearea paginilor corespunzătoare fiecărei
secțiuni a aplicației. Personalizarea și stilizarea paginilor a fost realizată utilizând
tehnologia Bootstrap 4.
5. MySQL este baza de date utilizată pentru stocarea tuturo r informațiilor din cadrul
aplicației.
6. Apache Tomcat reprezintă server -ul web utilizat. Tomcat implementează
specificațiile Java Servlet și Java Server Pages(JSP) și furnizează un mediu HTTP
pur Java.
7. Maven este tehnologia utilizată pentru descrierea struc turii aplicației și pentru
gestionarea dependențelor acesteia.
Aplicația este asemenea bazată pe mai multe șabloane de proiectare, două dintre ele fiind
Model View Controller (MVC), care a fost descris într -un capitol anterior, și Data Transfer Object
(DTO).
47
Data Transfer Object (DTO) este un container de date folosit la transferarea informațiilor
între diferitele nivele ale aplicației. Aceste are ca unic scop doar să transfere date și nu conține
logică de tip business.
5.3.1 Proiectarea bazei de date
Baza de date care a fost folosită în dezvoltarea acestei aplicații conține următoarele tabele :
Karateka
Club
Degree
Event
Karateka_Degree
Karateka_Event
Tabela Karateka are rolul de a stoca datele unui sportiv. Aceste date sunt nume le
sportivului , prenume le acestuia , vârst a, anul în care sportivul s -a apucat de practicat, email -ul,
parol a, un câmp în care este specificat id -ul clubului căruia îi aparține sportivul și un câmp
care să specifice dacă sportivul respectiv este, sau nu, antrenor .
Tabela Club este formată din trei coloane : numele clubului, adresa acestuia și data în care
a fost înființat.
Tabela Degree conține trei coloane, și anume culoarea centurii, tipul gradului și rangul
acestuia.
Tabela Event are rolul de a stoca datele unui eveniment. Aceste date sunt numele
evenimentului, tipul acestuia, locația, capacitatea și data în care va fi desfășurat evenimentul
respectiv.
Tabela Karateka_Degree are rolul de a stoca datele legate de gradul obținut de un anumit
sportiv. Datele acestea sunt id -ul sportivului care a obținut gradul, id -ul gradului pe care l -a
obținut, id -ul clubului la care este înregistrat respectivul sportiv, id -ul antrenorului care i -a
acordat acel grad și data obținerii acestuia.
48
Tabela Karateka_Event reține id -ul spor tivului și id -ul evenimentului la care acesta
participă.
Diagrama bazei de date și relațiile dintre tabele este reprezentată în imaginea următoare.
După cum se poate observa în imagine, relațiile dintre tabele sunt următoarele :
Relația dintre Club și Ka rateka este de tipul one to manny și reprezintă faptul că
un club are asociat mai mulți sportivi, iar un sportiv poate aparține doar unui singur
club.
Relația dintre Event și Karateka este de tipul manny to manny și reprezintă faptul
că un eveniment poate conține mai mulți sportivi, dar și faptul că un sportiv poate
49
participa la mai multe evenimente. Pentru o administrare mai ușoară a fost folosită
o tabelă de legătură, numită Karateka_Event.
Relația dintre Degree și Karateka este tot de tipul manny to mann y și reprezintă
faptul că un grad poate să fie deținut de mai mulți sportivi, dar și faptul că un sportiv
poate să dețină mai multe grade. În acest caz a fost utilizată tabela Karateka_Degree
ca și tabelă de legătură.
5.3.2 Procesul de autentificare
Pentru ca cineva să se poate autentifica cu succes în cadrul aplicației, utilizatorul trebuie
să introducă datele de autentificare corespunzătoare acestuia, adică email -ul și parola care îi sunt
asociate. În figura următoare se poate observa pagina de aute ntificare unde utilizatorul își introduce
detaliile de acces.
În cazul în care datele introduse de către utilizator nu sunt corecte, i se afișează un mesaj
de eroare.
Pentru dezvoltarea acestei pagini de autentificare s -au folosit tehnologiile JSP, H TML și
Bootstrap, iar codul aferent paginii de autentificare prezentată anterior este :
<form class="form-horizontal" role="form" method="POST" action="/login" >
50
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<h2>Please Login </h2>
<hr>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group has -danger">
<label class="sr-only" for="email">E-Mail Address </label>
<div class="input-group mb -2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width:
2.6rem">
<i class="fa fa-at"></i>
</div>
<input type="text" name="email" class="form-control"
id="email"
placeholder ="Email" required autofocus >
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="form-group">
<label class="sr-only" for="password" >Password </label>
<div class="input-group mb -2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width:
2.6rem">
<i class="fa fa-key"></i>
</div>
<input type="password" name="password" class="form-
control"
id="password" placeholder ="Password"
required >
</div>
</div>
</div>
51
</div>
<div class="row" style="padding-top: 1rem">
<div class="col-md-3"></div>
<div class="col-md-6">
<button type="submit" class="btn btn -success" >
<i class="fa fa-sign-in"></i> Login
</button>
</div>
</div>
</form>
5.4 Modul de funcționare al aplicației
Un vizitator obișnuit poate vedea detalii despre cluburi, sportivi, grade și evenimente. O
astfel de pagină care afișează astfel de detalii arată în modul următor.
52
Vizitatorul poate vedea detaliile despre cluburi și poate vedea membrii care aparțin doar
unui anumit club, prin apăsarea pe link-ul View aflat pe coloana Members. În urma accesării acelui
link, vizitatorul va fi redirecționat pe o pagină care arată în felul următor.
Pentru o vizualizare mai eficientă, acesta poate efectua o căutare prin rezultate afișate,
putând să -și sele cteze criteriile după care să fie efectuată această căutare. În josul paginii este afișat
numărul de rezultate care au fost găsite, numărul total de pagini și numărul paginii curente. De
asemenea, există două link -uri, Older și Newer , care redirecționează utilizatorul pe pagina
anterioară, respectiv pe cea următoare. În cazul în care nu există o pagină precedentă, sau o pagină
nouă, cele două link -uri sunt dezactivate.
Un exemplu de sortare a rezultatelor poate fi acela în care vizitatorul dorește să vadă t oți
sportivii de la clubul C.S. Dinamo Zen Do Caracal, în ordine descrescătoare vârstei, și afișând doar
câte cinci rezultate pe pagină.
Rezultatul ar arăta în felul următor :
53
Fiind afișate doar câte cinci rezultate pe pagină, fiind șase în total, în ur ma apăsării pe link –
ul de Newer, a doua și ultima pagină va conține un singur rezultat, și anume :
Pentru a se putea realiza această filtrare a rezultatelor pe fiecare pagină a fost utilizată clasa
SearchDTO. Un astfel de obiect conține următoarele detalii : un șir de caractere care reține datele
introduse de către utilizator în căsuța de căutare , numărul de elemente care să fie afișate pe pagină
(valoarea implicită este aceea de zece elemente pe pagină), numărul paginii curente, direcția de
sortare (valoarea implicită a acesteia este “ASC”, adică ascendentă) și atributul pe baza căruia se
va realiza sortarea (valoarea implicită este aceea de “id”).
Metoda getPageRequest , care primește ca și parametru un obiect de tipul SearchDTO,
metodă care aparțin e clasei PaginationUtils , procesează informațiile conținute de către obiectul
de tip SearchDTO și returnează un obiect de tipul PageRequest care permite paginarea
rezultatelor.
Metoda getPageRequest arată în modul următor :
public static PageRequest getPageRequest (SearchDTO searchDTO ) {
PageRequest pageRequest = null;
if (searchDTO != null) {
int pageNumber = searchDTO .getPageNumber ();
int perPage = searchDTO .getPerPage ();
String sortBy = searchDTO .getSortBy ();
54
String sortDirection = searchDTO .getSortDirection ();
Sort sort = null;
if (pageNumber <= 0) {
pageNumber = 0;
} else {
pageNumber = pageNumber – 1;
}
if (perPage < 1) {
perPage = DEFAULT_PER_PAGE ;
}
if (!StringUtils .hasText(sortBy)) {
sortBy = "id";
}
if ("ASC".equalsIgnoreCase (sortDirection )) {
sort = new Sort(Direction .ASC, sortBy);
} else if ("DESC".equalsIgnoreCase (sortDirection )) {
sort = new Sort(Direction .DESC, sortBy);
} else {
sort = new Sort(Direction .DESC, sortBy);
}
pageRequest = PageRequest .of(pageNumber , perPage, sort);
} else {
pageRequest = PageRequest .of(0, DEFAULT_PER_PAGE );
}
return pageRequest ;
}
Căutarea unui anume sportiv în baza de date se realizează cu ajutorul acestei metode ce se
află în interfața KaratekaRepository :
Page<Karateka >
findByFirstNameIgnoreCaseContainingOrLastNameIgnoreCaseContainingOrEmailContaining (
Pageable pageRequest , String query, String query2, String query3);
Interfața KaratekaRepository extinde interfața JpaRepository, cea din urmă oferind suport
pentru operațiile ce se pot efectua pe baza de date , acest lucru fiind explicat mai detaliat în capitolul
dedicat Spring JPA. Pentru operațiile comune, precum cele CRUD, nu este nevoie de o
55
implementare concretă . Spre exemplu, pentru operația de adăugare în baze de date, este suficient
să avem o metodă în clasa KaratekaService care să arate în felul următor :
@Override
public Karateka addKarateka (Karateka karateka ) {
return karatekaRepository .save(karateka );
}
Pentru o operație mai complicată, precum cea descrisă anterior, referitoare la căutarea unui
sportiv în baza de date, JPA oferă o modalitate mult mai ușoară de a satisface această dorință. Nu
este nevoie de o interogare clasică tip Standard Query Language (SQL), JPA realizând o
interpretare a numelui metodei declarate în interfață și transformând -o într -o interogare SQL, astfel
că metoda findByFirstNameIgnoreCaseContainingOrLastName … o să returneze un obiect de tip
Page care o să conțină toate entitățile din baza de date care conțin șirul de caractere introdus de
către utilizator.
Un vizitator poate de asemenea să vadă performanțele unui sportiv, accesând link -ul View
aflat în coloana Historic . Pagina care conține performanțele unui sportiv arată în modul următor :
Această pagina conține detaliile legate de gradul obținut de către sportivul respectiv,
culoarea centurii obținute, data la care a obținut gradul, clubul la care a fost afiliat în timpul
acordării gradului și antrenorul care a acordat gradul.
Un sportiv înregistrat la un club, în urma autentificării cu succes, poate accesa o pagina
pentru a -și edita detaliile personale. Pagina arată astfel :
56
Administrat orul este persoana care are drepturi totale asupra aplicației. Acesta poate
gestiona sport ivii, cluburile și evenimentele din cadrul federației , el având dreptul de a adăuga,
modifica sau șterge entitățile respective.
Spre exemplu, administratorul poate adăuga un eveniment nou. Când acesta apăsă pe
butonul de “Add Event” este redirecționat căt re o pagină unde poate specifica toate detaliile
evenimentului pe care dorește să -l adauge. Administratorul trebuie să specifice numele
evenimentului, tipul acestuia( un eveniment poate să fie de mai multe tipuri ; Seminar – este un
eveniment unde sportivii se adună pentru a acumula cunoștințe și unde aceștia pot obține grade
noi în urma susținerii unor examene și probe , Competition – este un eveniment unde sportivii
concurează unul împotriva altuia în dorința de a se perfecționa și de a obține diverse premi i,
Demonstration – în cadrul unui astfel de eveniment sportivii se organizează și execută în fața unui
public diverse combinații de tehnici), capacitatea evenimentului( numărul de sportivi care poate
participa la evenimentul respectiv), locația unde se va desfășura evenimentul și data desfășurării
acestuia.
Pagina de adăugare a unui eveniment nou este prezentată în figura următoare :
57
Un administrator poate să adauge de asemenea și noi sportivi și să specifice clubul de care
aparține sportivul respectiv. Această operație de adăugare trece totuși prin diverse verificări. Spre
exemplu, nu poate fi adăugat un sportiv cu un email deja existent în baza de date. Codul aferent
acestei verificări este prezentat în cele ce urmează.
try {
Karateka karateka = new Karateka ();
karateka .setLastName (karatekaDTO .getLastName ());
karateka .setFirstName (karatekaDTO .getFirstName ());
karateka .setAge(karatekaDTO .getAge());
karateka .setBeginningYear (karatekaDTO .getBeginningYear ());
if (karateka .checkDifferenceBetweenAgeAndBeginningYear ()) {
result.rejectValue ("age", "input-error", "Years of practice are
bigger than karateka's age" );
logger.error("Add karateka error : " + result.getAllErrors ());
return "add-karateka" ;
}
karateka .setEmail (karatekaDTO .getEmail ());
// Before save encode password.
karateka .setPassword (passwordEncoder .encode(karatekaDTO.getPassword ()));
karateka .setTrainer (karatekaDTO .isTrainer ());
karateka .setClub(new Club(karatekaDTO .getClubId ()));
karatekaService .addKarateka (karateka );
58
catch (DuplicateRecordException e) {
result.rejectValue ("email", "duplicate" , "Email already used" );
logger.error("Add karateka error : " + result.getAllErrors ());
return "add-karateka" ;
}
Parola introdusă și aferentă contului sportivului respectiv este criptată, aceasta nefiind
vizibilă în clar în baza de date. Pentru criptarea parolei s -a utilizat algoritmul de criptare Blowfish
(BCrypt Password Encoder).
De asemenea, o altă verificare care este efectuată în momentul operației de adăugare a unui
sportiv nou este aceea de verificare a vârstei sportivului și a anului în care acesta s -a apucat de
practicat. Administratorului nu i se permite să adauge un sportiv cu vârsta de doisprezece ani, dar
în urma introducerii anului în care s -a apucat de practicat să rezulte că sportivul practică acest
sport de optsprezece ani.
Pentru ca acest lucru să fie evitat, clasa Karateka conține o metodă care să realizeze această
verificare, numită checkDifferenceBetwe enAgeAndBeginningYear , metodă care arată astfel :
//Checks if the years of practice corresponds with karateka's age.
public boolean checkDifferenceBetweenAgeAndBeginningYear () {
if(Calendar .getInstance ().get(Calendar .YEAR) – getBeginningYear () < age – 5) {
return false;
}
return true;
Metoda returnează “false” dacă, în urma introducerii anului în care sportivul s -a apucat de
practicat, rezultă că sportivul s -a apucat de practicat de la o vârstă mai mică de cinci ani, asumând
că aceasta ar fi vârstă minimă pentru a începe practicarea acestui sport. Metoda returne ază “true”
în cazul în care datele sunt validate și sportivul este adăugat cu succes în baza de date.
Gradele ce pot fi obținute de sportivi sunt formate dintr -un rang, un tip al gradului (Kyu
sau Dan) și din culoarea centurii corespunzătoare gradului. În general există un standard al acestor
grade. Spre exemplu, gradul “1 Kyu ” reprezintă în cele mai multe cazuri centura de culoare maro.
Totuși, în anumite locuri aceste standarde pot fi modificate, gradul “1 Kyu ” putând să reprezinte
centura de culoare alba stră spre exemplu.
59
Cu toți acești factori luați în considerare, aplicația a fost dezvoltată în așa fel încât
administratorul să poată adăuga grade asemănătoare, fără a exista restricții sau grade prestabilite,
lucru care ar fi limitat posibilitățile acordă rii gradelor. Atunci când administratorul adaugă un grad
nou, acestuia i se oferă libertatea de a p utea alege ce rang dorește și ce culoare dorește pentru
gradul respectiv.
Să presupunem următorul caz drept exemplu : un sportiv obține la clubul C.S. Dinamo Zen
Do Caracal gradul “1 Kyu Centură Maro ”, dar dup ă este nevoit să plece la clubul Farul, club unde
gradul 1 Kyu are asociată ca și centură pe cea de culoare neagră, nu pe cea de culoare maro. Dacă
sportivul obține la noul club gradul respectiv, administ ratorul poate adăuga noul grad în istoricul
sportivului respectiv, istoric care, în urma modificărilor, ar arăta astfel :
60
CAPITOLUL 6
CONCLUZII
Spring este un framework în continuă dezvoltare, în prezent ajungând la versiunea cu
numărul 5, și care pune la dispoziție dezvoltatorilor o gamă variată de instrumente și module ce
pot fi utilizate în dezvoltarea aplicațiilor Java enterprise.
Cele mai importante module , mecanisme și tehnologii ale acestui framework sunt
următoarele :
Mecanismul Inversio n of Control (IoC) container care gestionează și creează
dependențele obiectelor.
Mecanismul de Validation și Data Binding prin care se poate realiza validarea
datelor.
Mecanismul Spring Expression Language (SpEL) care asigură interogarea și
manipularea gr afului de obiecte în timpul rulării.
Modulul Aspect Oriented Programming (AOP) care furnizează o altă perspectivă
de construire a unui program.
Modulul web care oferă suport pentru dezvoltarea aplicațiilor web care sunt scrise
utilizând framework -ul Spring . Acest modul conține modulul Spring MVC, acesta
fiind bazat pe șablonul de proiectare Model View Controller.
Tehnologiile Java Server Page (JSP) și Java Standard Template Library (JSTL) , cu
ajutorul cărora se pot crea pagini web .
Modulul Spring JPA, care face parte din Spring Data, furnizează un suport
dezvoltat pentru realizarea operațiilor pe o bază de date.
Mecanismele de autentificare și autorizare care sunt oferite de către modulul Spring
Security.
61
Prin intermediul acestei lucrări de licență am scos î n evidență modul de dezvoltare al unei
aplicații web scrisă în limbajul Java și avantajele pe care le aduce utilizarea framework -ului Spring ,
configurând aplicația cu fișiere XML. Rolul acestui framework este acela de a oferi suport
dezvoltatorului și de a ușura pe cât de mult posibil efortul depus de acesta în procesul de dezvoltare
al aplicației. Utilizând acest framework, dezvoltatorul nu este nevoit să -și facă griji legate de
crearea obiectelor sau de instanțierea acestora spre exemplu , lucrurile aceste a fiind făcute de către
container -ul IoC.
Câteva a vantaje ale utilizării acestui framework sunt următoarele :
Flexibilitatea. Dezvoltatorul poate să modifice părți ale aplicației fără ca celelalte
nivele să fie afectate.
Integrare a ușoară cu alte tehnologii .
Suportă configurare atât XML, cât și configurare scrisă în cod Java.
Facilitează bunele practici de programare, precum utilizarea interfețelor în
detrimentul claselor.
Suport consistent pentru framework -ul JDBC, îmbunătățind productivitatea și
reducând d in numărul de erori.
Furnizează un framework puternic și flexibil prin intermediul căruia se pot dezvolta
aplicații web complexe.
Dat fiind faptul că acest framework se află într -o ascensiune continuă, au fost implementate
și îmbunătățite module precum Spr ing JPA, dezvoltatorul nefiind nevoit să scrie o interogare tip
Standard Query Language (SQL) pentru a realiza o operație pe baza de date, acea interogare fiind
procesată automat de către Spring care transform ă numele metodei într -o interogare SQL și
proce sează rezultatul acestei interogări .
Utilizarea acestui framework în cadrul aplicațiilor Java aduce niște avantaje considerabile
și o varietate de mecanisme create pentru a oferi suport dezvoltatorilor.
62
BIBLIOGRAFIE
Bibliografie
Company, S., 2019. springdoc. [Interactiv]
Available at: https://docs.springdoc.io
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Lect. Univ. Dr. Gabroveanu Mihai [628525] (ID: 628525)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
