SlideShare a Scribd company logo
1 of 27
Download to read offline
Università degli Studi di Napoli Parthenope
Facoltà di Scienze e Tecnologie
Corso di laurea in Informatica Applicata
PROGETTO DI SISTEMI OPERATIVI DISTRIBUITI
Studio ed implementazione
dell'interazione tra Spring Framework e Apache Zookeeper
Davide Sito
Matricola 0120000129
Anno Accademico 2014-2015
i
Abstract
Nel presente lavoro viene mostrata l'implementazione di una web application la cui architettura fa
utilizzo sia del framework Spring che di Apache Zookeeper, sia per ottenere un sistema di congu-
razione distribuito sia per creare un sistema di gestione token.
ii
Contents
1 Obiettivi del presente lavoro 1
2 Introduzione al framework Spring 2
2.1 Inversion of Control (IoC) e Dependency Injection (DI) . . . . . . . . . . . . . . . . 2
2.2 Spring IoC container e beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2.1 Container Conguration le . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.2 Beans Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.3 Beans Lifecycle Management . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.4 PropertyPlaceholderCongurer e Proprietà . . . . . . . . . . . . . . . . . . . 9
2.3 Java Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 JDBCTemplate e DAO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.5 Spring MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.5.1 Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.5.2 Validazione forms , Binders e Custom Annotations . . . . . . . . . . . . . . . 13
3 Applicazione 14
3.1 ZooKeeper per congurazione distribuita . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 ZooKeeper per servizio di token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3 Architettura dell'applicazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.4 Struttura del servizio di congurazione distribuito . . . . . . . . . . . . . . . . . . . 16
3.5 Log degli utenti ed heartbeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.6 Servizio di token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4 Congurazione ed esempio d'uso 21
iii
1 Obiettivi del presente lavoro
Nel presente lavoro si è voluto studiare ed approfondire l'architettura del diusissimo framework
Spring, che ore un insieme notevole di strumenti e moduli utilissimi per la creazione non solo
di web-application (nel cui caso la scelta più naturale è Spring MVC) ma per l'organizzazione (e
riorganizzazione) di qualunque tipo di applicazione basata su Java. Si è deciso quindi di costruire,
sulla base di un'architettura strutturata secondo la logica ed i pattern di Spring, un'integrazione tra
la nostra applicazione ed il framework Apache ZooKeeper, che ore un set di primitive utili per la
progettazione di algoritmi di sincronizzazione / controllo distribuiti. In tal senso quindi ZooKeeper
è risultato utile sia per l'implementazione di un servizio di token distribuito (riprendendo la logica
di un plugin già esistente introdotto per le nuove versioni di Spring 4.x ma fornendo una nostra
implementazione da zero) sia per creare un servizio di congurazione distribuita. In tal modo la
web application fa da interfaccia tra quello che è l'utilizzatore (il client, ovvero un browser web) che
usa un protocollo stateless, e il servizio basato su ZooKeeper (tipicamente connection oriented).
1
2 Introduzione al framework Spring
Viene qui di seguito fornita una panoramica di quella che è l'architettura del framework, analizzan-
done le componenti principali, e soermandosi principalmente su quelle che sono le caratteristiche
da noi utilizzate. Volendo citare la documentazione uciale
The Spring Framework is a Java platform that provides comprehensive infrastructure support for
developing Java applications. Spring handles the infrastructure so you can focus on your applica-
tion.
Grazie a Spring è possibile semplicare notevolmente la struttura di un'applicazione in quanto
tramite l'imposizione di determinate best-practices, questo permette la separazione tra le classi
coinvolte, con una notevole riduzione (se non eliminazione) delle dependencies tra gli oggetti, e la
semplicazione di questi in POJOs (plain old java objects, ovvero oggetti dotati esclusivamente di
getter e setters) tramite lo spostamento all'esterno della logica applicativa.
2.1 Inversion of Control (IoC) e Dependency Injection (DI)
Con questi due termini ci si riferisce al principio più importante che sta alla base del funzionamento
di Spring, e di tutti i moduli e strumenti che questo fornisce ai livelli più alti.
Chiunque abbia mai lavorato con Java è consapevole di come tutto sia orientato alle classi.
Persino l'entry point dell'applicativo (il classico main) è plasmato come una classe.
Appare quindi naturale che, anche nel caso dell'applicazione più semplice, si vengano a creare
notevoli relazioni di utilizzo e/o composizione tra oggetti di classi diversi. All'aumentare quindi
della dimensione e della complessità di un'applicazione, aumenta in maniera direttamente pro-
porzionale la complessità di tali relazioni.
Per evitare quindi di ritrovarsi in una situazione in cui un'architettura diventi dicilmente gestibile,
generalmente si adottano determinati paradigmi di progettazione atti a diminuirne la complessità
ridistribuendo le relazioni secondo pattern conosciuti.
Alcuni dei più famosi sono le Factory, le Abstract Factory, i Prototype etc...
Ma si tratta appunto di best practices, che sta al programmatore seguire ed implementare (con un
grado di successo spesso altamente variabile).
E' in questo scenario che Spring risulta estremamente utile in quanto permette di eliminare total-
mente (in ciascuna delle classi) la necessità di istanziare (e quindi controllare, conoscere) qualsivoglia
altro oggetto. In tal modo non esiste una classe che ne controlla un'altra, ma piuttosto esiste un
2
componente completamente esterno che si occupa di iniettare tae conoscenza a runtime, tramite i
setter di quelli che diventano dei semplici POJOS nel momento in cui vengono privati della logica
applicativa. In tal modo il numero delle relazioni che non vengono delegate al componente esterno,
e che quindi rimangono a far parte delle nostre classi java, si riduce drasticamente.
Come anticipato in precedenza, questo principio si applica a tutto l'(enorme) set di funzionalità
oerto dal framework, che si articola in oltre 20 moduli suddivisi nei gruppi principali mostrali nel
seguito.
Il Core Container fornisce le funzionalità fondamentali del framework, quelle grazie alle quali è
appunto possibile implementare l'Inversion of Control e la Dependency Injection. Il modulo conte-
nente l'AOP (Aspect Oriented Programming) fornisce una serie di strumenti utili per inserire una
logica di business secondo il paradigma omonimo, che permette di centralizzare in un sol punto
quello che è il codice implementativo di operazioni ripetute più volte in tutta l'applicazione, evi-
tando quindi le duplicazione (ad esempio per il logging).
Il Data Access/ Integration module permette l'utilizzo di interfacce e connettori per l'accesso a basi
di dati tramite metodologie classiche (quali ad esempio i connettori JDBC) o secondo principi più
moderni quali ad esempio l'ORM (Object-Relational Mapping) che permette di accedere ad una
base dati utilizzando la logica degli oggetti persistenti.
Il modulo Web fornisce strumenti per la progettazione di Web Application secondo gli standard di
3
Java EE (con Spring MVC ad esempio si usano gli Spring Beans come se fossero degli Enterprise
Java Beans) mentre il modulo Test fornisce funzionalità per il testing.
2.2 Spring IoC container e beans
Come descritto in precedenza l'IoC (sinonimo di Dependency Injection) permette agli oggetti che
rappresentano la nostra applicazione di denire le loro dipendenze (le altre classi con cui lavorano)
soltanto come parametri dei costruttori e/o di metodi setter. Il Container è il componente es-
terno (è il cuore di Spring) che si occupa di fare l'injection di tali dipendenze. Questo procedimento
è detto Inversion of Control poichè nell'approccio classico è l'oggetto che controlla che si occupa
di istanziare (al suo interno) l'oggetto controllato. Tale container è rappresentato dall'interfaccia
BeanFactory che permette di recuperare gli oggetti dopo che sono state risolte le dipendenze ed
eettuate le injections. Nel caso pratico Spring ore un'interfaccia denominata ApplicationCon-
text che copre tutte le funzionalità del BeanFactory, oltre ad introdurre funzionalità aggiuntive.
Nel gergo di Spring, gli oggetti congurati tramite il BeanFactory, prendono il nome di Beans.
Il modo in cui il programmatore istruisce il container relativamente a come questo debba combinare
gli oggetti e produrre le dipendenze, è tramite un le xml di congurazione (no a Spring 2.x era
l'unica opzione) o tramite Java annotations (dalla 3.x in poi). Si noti che l'ApplicationContext
è generalmente fornito come punto di partenza di qualunque progetto, quindi il programmatore
deve preoccuparsi esclusivamente di fornire i metadati per la congurazione delle dipendenze, e di
richiamare l'application context dove necessario.
4
2.2.1 Container Conguration le
Si tratta generalmente di un le xml (anche se come descritto in precedenza esistono metodi alter-
nativi per fornire la congurazione al container) nel quale il programmatore fornisce la dichiarazione
della gerarchia delle relazioni tra i bean. .
Nella forma più elementare, ciascuna entry rappresenta un bean, con un proprio id (necessario per
ottenere, nel codice Java, l'istanza di questo) e la classe a cui l'istanza di tale oggetto appartiene.
Nello screen possiamo osservare un esempio di congurazione di due bean, entrambi di classe Utente,
ciascuno rappresentante una diversa istanza. .
La classe utente non è altro che un POJO dotato di setter e getter per le proprietà nome e cognome.
Nel le di congurazione quindi, utilizzando il tag property è possibile indicare che valore (primitivo
in questo caso, non trattandosi ancora di dependency injection) il container dovrà passare ai setter
di tali oggetti..
Nel codice java quindi, sarà possibile ottenere le due istanze create dal container (i due beans)
semplicemente invocando il metodo apposito dal bean factory (in questo caso il cong le viene
cercato nel classpath dell'applicazione)
5
Nell'esempio precedente è stata mostrata l'injection di un valore primitivo. Ma come già detto la
vera potenza di Spring sta nella dependency injection tra oggetti.
Immaginiamo ad esempio di voler creare un oggetto di classe Rubrica, che si occupi di mantenere
una serie di Utenti e di stamparne le caratteristiche. Il codice del pojo Rubrica apparirebbe come
segue:
.
6
mentre il le di congurazione utilizzerebbe la proprietà ref per fare injection con bean preceden-
temente dichiarati (nello stesso le di congurazione o in altri).
Nell'esempio mostrato non viene fatto uso di una Collection per conservare i vari bean injected
nell'oggetto di classe Rubrica.
Ovviamente tale funzionalità è fornita da Spring, che prevede l'utilizzo di strutture quali liste, hash
e sets.
Un'altra funzionalità di Spring Core estremamente utile è l'autowiring , che permette di inferire
quale bean utilizzare per l'injection sulla base del nome della proprietà (che deve corrispondere
all'id di un bean precedentemente dichiarato) o in base al tipo di quest'ultima.
2.2.2 Beans Scope
Quando si parla di scope dei Beans in Spring ci si riferisce al modo in cui viene gestita dal container,
la creazione dell'istanza di questo.
Tra gli scope più utilizzati abbiamo il Singleton ed il Prototype.
Nel Singleton il container crea un'istanza del bean all'avvio dell'applicazione, e tutte le richieste
eettuate all'application context ritorneranno la stessa istanza.
Tale scope è quello di default per le dichiarazioni dei beans.
7
A dierenza dei bean Singleton, per i bean di tipo Prototype (che vanno dichiarati come tali nel
momento in cui vengon deniti nel le di congurazione) non viene creata nessun'istanza all'avvio
del container, e tale allocazione viene rimandata no al momento in cui viene richiesto l'oggetto.
Inoltre ciascuna richiesta all'application context fornirà un'istanza diversa del bean.
2.2.3 Beans Lifecycle Management
Spring permette al programmatore di scrivere codice che va ad inserirsi nei vari stadi del ciclo di
vita di un bean.
In prima istanza è possibile dichiarare un metodo (nel POJO) che venga richiamato nel momento
in cui termina l'injection dell'ultimo setter di quest'ultimo (tramite l'utilizzo della proprietà xml
initMethod) e lo stesso per la fase relativa alla distruzione del bean (destroyMethod) . Alter-
nativamente, volendo lavorare direttamente col codice java è possibile ottenere lo stesso risultato
facendo implementare all'oggetto pojo le interfacce InitializingBean e DisposableBean.
In questi modi è possibile creare metodi specici per una determinata classe di bean. Ma come fare
8
qualora si volesse creare un comportamento comune, e indipendente dal tipo di oggetto istanziato
?
In questo caso ci viene in aiuto l'interfaccia BeanPostProcessor che ci permette di creare un
bean i cui 3 metodi (ereditati dall'interfaccia) partono rispettivamente prima e dopo l'injection di
qualunque altro bean, e prima della distruzione di questo.
Esiste inoltre la possibilità di dichiarare bean di tipo BeanFactoryPostProcessor ovvero bean i
cui metodi partono subito prima e subito dopo la congurazione del container.
I bean che implementano tale interfaccia per esempio sono quelli che permettono di utilizzare le EL
expression all'interno dello stesso le di congurazione, in maniera tale da centralizzare una serie
di proprietà (coppie chiave-valore) in le di tipo properties (si legga in seguito).
2.2.4 PropertyPlaceholderCongurer e Proprietà
Spring mette a disposizione la possibilità di utilizzare le di tipo *.properties per delocalizzare
determinati valori (un po' come accade per le stringhe in Java Android) e di riferirsi a tali valori
utilizzando le EL expressions, ovvero istruzioni del tipo ${nomeproprieta}..
Per sfruttare questa possibilità occorre innanzitutto creare un le *.properties e metterlo nel class-
path del progetto.
A questo punto basta creare un bean di tipo PropertyPlaceholderCongurer (o creando una propria
classe che implementi tale interfaccia) -si noti che spesso in Spring si parla di creazione di bean di
un tipo appartenente ad un'interfaccia, ma si tratta appunto del tipo formale, poichè ovviamente
un'interfaccia non può essere istanziata- settando il path del le di tipo properties come valore della
proprietà location di quest'ultimo. .
Essendo un oggetto di tipo PropertyPlaceholderCongurer innanzitutto un BeanFactoryPostProces-
sor, questo si occupa automaticamente, nel momento in cui termina la congurazione del container
tramite il le xml, di risolvere tutte le EL expressions presenti in quest'ultimo, nel loro valore reale
(prelevato dal le *.properties). .
La potenza ed il vantaggio dell'utilizzo di tali properties sta nel fatto che è possibile creare una
struttura gerarchica (ad esempio ${utente.nome} e ${utente.cognome} )..
E non è tutto, poichè utilizzando al posto del PropertyPlaceholderCongurer un PropertySource-
sPlaceholderCongurer, è possibile ottenere lo stesso risultato di cui sopra, ma con l'aggiunta
del fatto di avere a disposizione tali proprietà anche a livello del codice java e dell'application con-
text.
Proprio in questo consiste uno degli aspetti del nostro applicativo, ovvero nel fatto di utilizzare la
9
struttura dei nodi di un server Zookeeper (e i loro valori) come un insieme gerarchico di proprietà,
che rappresentano quindi un sistema di congurazione distribuita (invece che localizzata, come nel
caso di un le *.properties).
2.3 Java Annotation
Dalla versione 3.0 di Spring è stata introdotta la possibilità di passare da una congurazione ef-
fettuata esclusivamente nel le di congurazione *.xml, all'utilizzo di annotazioni Java, da inserire
quindi direttamente nel codice sorgente delle classi. Si noti che inizialmente la transizione è stata
soltanto parziale, e che quindi per poter utilizzare parecchie delle annotazioni, era comunque nec-
essario dichiarare un BeanPostProcessor associato (nel le xml di congurazione) che si occupasse
di trasformare tali annotazioni in un equivalente listato xml (invisibile al programmatore).
Ad esempio qualora si volesse utilizzare il tag @Autowired nel codice java, per indicare che una
proprietà di un pojo è soggetta a dependency injection da parte del container, sarebbe stato neces-
sario dichiarare (nel le .xml) un bean di tipo AutowiredBeanPostProcessor.
Tuttavia oggi, a conclusione di una transizione totale verso le annotazioni, basta utilizzare il tag
context:annotation-cong nel le *.xml per evitare di dover dichiarare tali bean a supporto di
ciascuna specica annotazione. In tal modo è possibile utilizzare anche soltanto esclusivamente le
annotazioni, senza dover dichiarare nulla nel le di congurazione xml.
Alcune delle annotazioni più diuse sono:
• @Autowired: permette di specicare la dependency innjection tramite autowiring
• @Resource(name=...) : specica la dependency injection classica
• @Value(${proprieta}): injection di un valore estratto dalle properties caricate da un Prop-
ertyResourcesPlaceholderCongurer nel contesto dell'applicazione
• @PostConstruct e @PreDestroy: il metodo taggato parte dopo il costruttore di qualunque
bean creato dall'application context
• @Component: la classe dichiarata con questo tag equivale alla denizione di un bean
nell'xml. Il nome della classe per default è l'id associato.
• @Conguration: la classe annotata con questo tag indica al container che si tratta di una
factory per bean (ovvero di una classe i cui metodi generano beans). Tali bean sono specicati
annotando i metodi della classe con @Bean
10
Questi sono soltanto alcuni dei tantissimi tag che Spring mette a disposizione per le più disparate
funzionalità, ed un elenco omnicomprensivo di questi va al di la dello scopo della presente relazione.
2.4 JDBCTemplate e DAO
Spring prevede l'accesso alle basi di dati utilizzando diversi approcci dierenti, ma che condividono
tutti la possibilità di seguire quello che è il pattern del Data Access Object (DAO).
Un DAO è un'interfaccia (interfaccia nel senso di java interface) denita dal programmatore che
presenta quelli che sono i metodi di interazione tra il programmatore e la base dati, relativamente ad
una specica entità (che solitamente è un bean). Ad esempio un DAO relativo alla classe Studente
potrebbe presentare metodi quali i getAllStudenti per ottenere la lista degli studenti presenti nel
DB, i getStudente(Nome) etc...
Sta al programmatore implementare un DAO in una classe che preveda l'accesso ad uno specico
DB (MySql, Oracle, Postgres...) utilizzando un particolare tipo di tecnologia (JDBC, Hibernate
etc...). Volendo ad esempio implementare il DAO Studente, per un DB Oracle usando JDBC,
creeremmo una classe che implementa il DAO e gestisce direttamente JDBC.
Spring ci semplica la vita fornendo determinati Templates, a seconda di quella che è la tecnologia
di accesso scelta.
Nel caso ad esempio di JDBC, Spring ci ore un JDBCTemplate, ovvero una classe capace di
gestire per noi tutte le operazioni di routine relative all'apertura e alla chiusura di una ses-
sione/transazione verso il DB. In tal modo il programmatore deve soltanto implementare i metodi
ereditati dall'interfaccia DAO, e occuparsi di congurare il JDBCTemplate anchè possa essere
usato in tali metodi.
L'uso di JDBCTemplate si basa sulla classe DataSource che (istanziata solitamente come bean
singleton) permette di congurare tutte le opzioni di accesso della base dati target del connettore.
2.5 Spring MVC
2.5.1 Architettura
Tra gli innumerevoli moduli e strumenti che il framework mette a disposizione, quello più usato
forse è proprio Spring MVC.
Si tratta di un framework per la creazione di web application secondo il paradigma Model-View-
Controller, che è seguito seguendo la struttura del Primary Controller.
11
In una web application la View è la pagina che viene mostrata all'utente, il Controller è l'elemento
(di back-end, sul server) che si occupa di gestire, secondo una logica programmata, quelle che sono
le richieste http sui vari url, ritornando a seconda del caso la view adatta, mentre i Model sono gli
oggetti che rappresentano le informazioni, che vengono scambiati sia tra il client ed il server, sia tra
le diverse componenti di back-end.
Seguendo questo paradigma si usa spesso progettare l'applicazione utilizzando un singolo controller
principale, che intercetta tutte le richieste relativamente a tutti gli indirizzi, e a seconda del mapping
degli url, invoca il controller apposito. Tale controller ritorna un model contenente informazioni ed
un nome logico di una view, che viene intercettato (assieme al model) dal controller principale e
tradotto da un View Resolver, che ritorna la view sica (una pagina html, jsp etc...) al controller
principale, che in ultima istanza la manda (assieme al model) all'utente (il browser).
Questa è proprio la struttura di un'applicazione Spring MVC, che si basa a sua volta sulla tec-
nologia Java Servlet (il primary controller infatti è proprio una servlet congurata, nel classico
deployment descriptor delle servlet ovvero il web.xml, per intercettare tutte le richieste ricevute
dall'applicazione).
Dal punto di vista del programmatore, la Dispatcher Servlet è automaticamente congurata (tramite
12
la generazione del deployment descriptor, ovvero il web.xml) in maniera tale da riconoscere quelli
che sono i controller da noi deniti.
Per denire un controller abbiamo due possibilità (come del resto per qualunque altra funzione a
partire da Spring 3.x) ovvero possiamo farlo nel le di congurazione xml (solitamente nel caso di
web application si utilizzano due application context, uno generico, ed uno associato direttamente
al contesto web della dispatcher servlet, quindi questo porta ad avere due le di congurazione xml)
ed in tal caso l'id usato per il bean diventa l'url sul quale è mappato il controller, oppure tramite
annotazioni utilizzando il tag @Controller che utilizzato a livello di classe, indica al WebAppli-
cationContext che i metodi di quella classe verranno mappati su determinati url. Tale mapping
(detto HandlerMapping) viene eettuato utilizzando @RequestMapping prima dei metodi del
controller che vogliamo che rispondano ad un determinato url. Nel tag RequestMapping è possibile
specicare anche il tipo di richiesta HTTP (POST/GET) al quale rispondere.
Tali metodi dovranno occuparsi di ritornare o una stringa rappresentante il nome logico della view,
o un Model (al cui interno è specicato il nome della view) contenente i dati desiderati. Sem-
pre in questi metodi è possibile ottenere (come parametro di input) l'HttpServletRequest e
l'HttpServletResponse che sono due delle più importanti componenti utilizzati nelle web appli-
cation basate su tecnologia Servlet, oltre che direttamente i parametri della request http.
2.5.2 Validazione forms , Binders e Custom Annotations
Una delle caratteristiche più interessanti dei controller di Spring MVC è la possibilità di ottenere
beans a runtime, nel momento in cui un client invia una richiesta http tramite un form di compi-
lazione dati, tramite un mapping tra i campi del form e le proprietà di un pojo.
Tramite il tag @ModelAttribute è infatti possibile ricevere come input (nel metodo del controller)
un bean già riempito con i dati inseriti nel form che l'ha invocato..
Immaginiamo ad esempio di avere un form html che invochi, nel momento del submit, una pagina
chiamata invioForm. E' possibile allora creare un controller Spring in ascolto su tale url, che riceva i
parametri inseriti nel form direttamente nella forma di un bean mappante (ovviamente deve esserci
corrispondenza univoca tra i nomi dei campi del form e le proprietà del pojo usato per il mapping).
13
ritornando quindi la view col nome logico dati, sarà possibile accedere in questa ai dati inseriti nel
form proprio come dei parametri di request, oltre ovviamente a poter essere elaborati dal controller
che sta in mezzo.
Ed è proprio in quest'ultimo scenario che vanno ad inserirsi due strumenti importantissimi di Spring
MVC, ovvero il Binder ed il Validator.
Il Binder è l'elemento che si occupa di trasformare le stringhe fornite in un form, nelle variabili di
classe corrispondenti nel mapping bean.
Tramite il tag @InitBinder è possibile individuare un metodo che si occupi di inizializzare e/o
modicare il binder di sistema, denendo in tal modo un proprio PropertyEditor ad hoc.
Ancora più importante è l'utilizzo di un Validator.
Si tratta di un elemento che si occupa di validare i valori inseriti nei campi del form, nel momento
in cui vengono trasformate (dal binder) nelle variabili del mapping bean.
I campi che sono sottoposti a validazione sono quelli che corrispondono a variabili che nel bean sono
annotate usando una delle annotazioni di validazione fornite da Spring (@Size, @Max, @Min
etc...) .
Aggiungendo il tag @Valid al tag @ModelAttribute del bean in input nel metodo del controller, ed
inserendo come ulteriore parametro di input un oggetto di tipo BindingResult sarà possibile nel
controller valutare il risultato della validazione.
3 Applicazione
Lo scopo dell'applicazione sviluppata è quello di mostrare un esempio di integrazione tra una web
application in Spring, ed un ensemble ZooKeeper..
Dal punto di vista delle funzionalità, ZooKeeper è utilizzato sia per creare un servizio di congu-
razione distribuita, sia per fornire un servizio di sincronizzazione distribuito tramite token..
14
3.1 ZooKeeper per congurazione distribuita
L'obiettivo è utilizzare la struttura a lesystem di ZooKeeper per individuare una serie di propri-
età da importare ed utilizzare nell'ecosistema di Spring come se si usasse un le di tipo properties..
Come visto in precedenza in Spring è possibile utilizzare le EL Expression (espressioni del tipo
${proprietà}) per fare injection di proprietà memorizzate in le locali di tipo properties..
La struttura di un le properties è gerarchica, e tale gerarchia viene mantenuta dalle proprietà una
volta che siano state caricate nel contesto dell'applicazione da un bean di tipo PropertySources-
PlaceholderCongurer (nel modo mostrato precedentemente). In tal modo quindi, mappiamo la
gerarchia dei nodi di un server ZK, utilizzando il valore dei dati contenuti in ciascun nodo, in una
gerarchia di proprietà..
In tal modo è possibile delocalizzare le proprietà e mantenerle in ZK invece che in un le di tipo prop-
erties locale all'applicazione. Cambiando quindi il valore di un nodo, e restartando l'applicazione,
cambia il valore delle proprietà injected.
3.2 ZooKeeper per servizio di token
Dal punto di vista del servizio di sincronizzazione distribuita, vengono usate le primitive fornite da
ZooKeeper per implementare un sistema in cui per ciascun client connesso alla web application su
Spring, viene creato un corrispondente client su ZooKeeper. Le richieste di token, eettuate dagli
utenti connessi, vengono mappate da nodi in ZooKeeper, e tramite un master viene implementata
la logica del servizio di sincronizzazione..
In tal modo Spring fa da middleware tra il client (il browser) che comunica usando richieste http
(tipicamente stateless) e le primitive di ZooKeeper che necessitano di una sessione (quindi di una
comunicazione continuativa).
3.3 Architettura dell'applicazione
Viene mostrata di seguito l'architettura generale dell'applicazione.
15
Da un punto di vista globale, la web application in Spring utilizza due controller principali..
Un primo controller mappa quelli che sono gli url utilizzati per fare il log in dell'utente, tramite
nome e password..
Tali informazioni sono ottenute utilizzando un pattern DAO da una base di dati..
Le informazioni relative alla base dati sono fornite tramite il servizio di congurazione distribuita su
ZK (per fornire tale servizio, allo start del contesto dell'applicazione si utilizza un bean apposito).
.
Una volta che un client risulta loggato, si collega al secondo controller, che si occupa di gestire
(tramite multithreading) le operazioni per fornire il servizio di tokening. .
Tecnicamente è possibile utilizzare due server ZK dierenti, uno per la congurazione ed uno per il
servizio di token..
Il modo in cui comunicano il client e l'applicazione Spring, è tramite http request, in cui tra i vari
parametri è inserita anche una stringa JSON che indica lo stato del client..
Per ottenere un comportamento stile sessione, viene usato javascript lato client, usando le variabili
di stato che vengono inviate al client via JSON.
Tutte le strutture dati utilizzate nella web application per memorizzare le informazioni sui client,
sono sincronizzate in accesso mutualmente esclusivo, essendo più di un thread coinvolto.
3.4 Struttura del servizio di congurazione distribuito
Come descritto in precedenza per ottenere il servizio di congurazione distribuita tramite ZooKeeper,
si vuole far sì che la struttura del server ZK venga mappata come una gerarchia di proprietà im-
16
portate nell'application context di Spring. Per fare questo quindi utilizziamo due componenti
principali: un bean con scope singleton, anchè questo venga istanziato all'avvio dell'applicazione,
ed un connettore che faccia il wrapping delle primitive oerte da ZooKeeper per accedere in lettura
alla struttura dei nodi e dei rispettivi gli (con i valori).
Viene usato un unico le di tipo properties, per alcune proprietà non delocalizzate, tra cui l'url del
server zookeeper usato per la congurazione distribuita, ed il nodo da usare come root (ovvero da
quale nodo in poi iniziare a mappare tutti i gli come valori chiave/valore di tipo properties).
Il connettore quindi, tramite un metodo lanciato dal bean iniettore quando l'application context è
partito, visita ricorsivamente la struttura del server zookeeper, e restituisce una mappa chiavi/valori,
dove le chiavi sono i path gerarchici di tutti i nodi zk trovati dal root in poi, e i valori sono quelli
prelevati da ciascun nodo.
A questo punto viene quindi creato in Spring un nuovo PropertySourcesPlaceholderCongurer che
viene poi aggiunto al contesto (che viene refreshato).
In tal modo, dopo qualche secondo, nel contesto dell'applicazione saranno disponibili le proprietà
fornite, tramite zookeeper, in maniera distribuita e decentralizzata.
Nel nostro esempio questo servizio viene utilizzato per memorizzare i dati relativi al DB (url DB,
tipo di connettore usato, nome DB, password) da utilizzare per accedere alle password/username
degli utenti, quando questi cercano di eettuare il login.
Il vantaggio è che qualora si volesse cambiare il DB, basterebbe cambiare il valore dei nodi sul server
ZooKeeper, riavviando l'applicazione.
Le informazioni relative al DB vengono iniettate in un bean di tipo DataSource, che viene passato
al JDBCTemplate utilizzato da un nostro bean che segue il pattern Data Access Object, per imple-
mentare le operazioni di accesso al db necessarie per ottenere le coppie nome utente/password.
Tali informazioni sono utilizzate da un validatore (di supporto ad una nostra annotazione custom).
3.5 Log degli utenti ed heartbeat
All'avvio l'utente si collega al root url dell'applicazione (in Spring MVC questo è dato dal path del
server e dall'artifact ID di Maven) sul quale risponde il primo dei due controller, inviando come
view un form di login.
Quando l'utente inserisce i dati di login, il submit del form avviene ad un url sul quale è map-
pato un metodo del controller principale, che tramite la tecnica del @ModelAttribute (descritta in
precedenza) e l'uso di una nostra annotazione custom denominata @CheckDatiSuDb fa si che il
binder di Spring faccia l'injection dei dati inseriti nel form in un bean di tipo Utente, e invochi un
17
validatore fornito da noi che si colleghi al DB (tramite il bean DAO sui cui viene fatto l'injection
delle proprietà di congurazione distribuita estratte da ZooKeeper) e controlli che esista quella
combinazione di utente/password. In caso aermativo l'utente viene indirizzato ad un url su cui
è mappato il secondo controller, dopo aver inserito nella richiesta http il nome dell'utente come
parametro (in questo modo indichiamo al secondo controller che l'utente è loggato).
Quando un utente arriva al secondo controller è sicuramente loggato, ma a questo punto occorre
che gli si crei un nodo corrispettivo su ZooKeeper.
Diremo che l'utente è confermato su ZooKeeper quando oltre ad essere loggato, risulta avere un
nodo in /clients/ su ZooKeeper.
Quando un utente arriva per la prima volta sul secondo controller, il suo stato non è confermato (il
controller se ne accorge in quanto l'utente ha il parametro nome settato, ma non vi è alcuna traccia
dell'utente nelle strutture dati globali che tengono traccia dei clients connessi). A quel punto il
controller aggiunge entry del client in una sola delle strutture (quella relativa allo stato) e avvia un
thread che, tramite un connettore ad hoc, si occupa di richiedere su ZooKeeper la creazione di un
nodo in /clients/. Prima che tale thread concluda il suo compito, ed indipendentemente dall'esito,
il controller rimanda response http al client in modo tale che per le successive richieste (inviate dal
client PRIMA che il thread di cui sopra abbia concluso) il controller si accorgerà che, esistendo
un'unica entry relativa allo stato per quel client, si tratta di un utente che sta attendendo che il
thread di cui sopra abbia terminato la creazione su ZK del nodo. Fin quando rimane in questo
stato, l'utente si dice in wait per la conferma su ZK.
18
Quando il thread termina correttamente, generando un nodo su ZooKeeper che rappresenti tale
utente, vengono aggiunte tutte le informazioni relativamente al client, nelle strutture dati globali del
controller Spring (nome utente nella lista utenti, stato aggiornato, connettore usato per connettersi
a Zk nella lista dei connettori, e stringa JSON utilizzata per la comunicazione dello stato client-
side), e viene avviato un thread di servizio denito checker che si occupa di controllare ogni tot
secondi che l'utente abbia inviato un heartbeat. Un heartbeat del client è una semplice richiesta
http (che il client invia ciclicamente grazie ad uno script javascript, assieme alla stringa json che
rappresenta il suo stato). Se il checker trova che l'utente ha inviato heartbeat, risetta a false il
suo stato, in maniera tale che il client, anchè venga considerato vivo, debba reinviare un nuovo
heartbeat (che faccia impostare lo stato a true) prima del successivo controllo del checker. Nel
momento in cui un checker si accorge che un client è disconnesso, esegue la chiusura del connettore
e la pulizia delle strutture dati.
3.6 Servizio di token
Quando un client risulta autenticato e confermato (connesso) utilizza lo stato delle sue variabili
locali (lato client, modicabili tramite l'interazione del client nel browser e inviate come stringa
json) per comunicare al controller le proprie scelte.
Per implementare tale servizio, vengono usati tre nodi nel server ZooKeeper.
Un nodo /master, utilizzato dal master thread per registrare un watcher sulla ricezione del token
(un client riceve token quando viene settato un nuovo valore per il dato conservato nel nodo ad esso
19
associato).
Un nodo cede il token al master ogni volta che ha terminato di utilizzarlo.
Un nodo /clients, sotto il quale vengono creati (come gli) i nodi associati ai client connessi.
Quando un client richiede un token, dopo aver registrato in ZK la propria richiesta, setta un
watcher sul cambiamento del valore contenuto nel nodo ad esso associato, in modo tale da sapere
quando riceverà lo riceverà.
Un nodo /token_requests sotto il quale sono creati i nodi relativi alle richieste di token (ciascuno
con un nome indicativo del client).
Quando il master riceve il token, controlla la lista delle richieste in /token_requests: se ne trova
almeno una, cede il token al client associato e ritorna in attesa di ricevere di nuovo token, se la lista
è vuota, setta direttamente un watcher su token_requests.
Dal punto di vista del client quindi, nel caso in cui il client esprima la volontà di ottenere il token, e
non lo abbia già (e non lo stia aspettando) viene messo in wait per la ricezione di questo. Il controller
crea quindi un thread che si occupa, tramite il connettore ZK associato al client (precedentemente
già utilizzato per eettuare la creazione del nodo in /clients/ ) di creare un nuovo nodo (con un
nome indicativo del client) come glio di /token_requests, e di registrare un watcher sul valore del
nodo in /clients. Il watcher, alla ricezione del token, setta lo stato delle variabili di stato del client
in maniera tale da indicare che questo ha ricevuto il token (oltre a cancellare il nodo rappresentante
la richiesta da /token_requests).
Nel caso in cui il client non voglia il token, ed il suo stato indichi che lo possiede, si tratta allora di
una cessione, quindi settiamo di conseguenza gli stati relativi al possesso, e rimettiamo il token nel
sistema (ricedendolo al master).
Inne nel caso in cui un client non voglia il token ma il suo stato indichi che è in attesa di riceverlo,
vuol dire che è stata annullata una richiesta non ancora soddisfatta. Viene allora eliminato il nodo
della richiesta pendente e viene aggiornato lo stato del client.
Oltre ai thread associati ciascuno ad un utente diverso quindi, è necessario che il controller lanci
un thread che rappresenti il coordinatore del sistema (il master). Si tratta di un thread creato
come bean singleton, il cui metodo principale (il classico run) parte dopo che sono stati invocati i
setter. Questo thread utilizza un connettore ad hoc per il master che permette di creare (all'avvio
dell'applicazione) il nodo /master, e di registrare inizialmente il watcher sul cambio del valore di
quest'ultimo.
20
4 Congurazione ed esempio d'uso
Il testing dell'applicazione è stato eettuato usando Eclipse Spring Tool Suite.
Il server usato è un'istanza di Apache Tomcat.
Per gestire le dipendenze (sia di ZooKeeper che di Spring) è stato usato Maven .
Come primo step occorre congurare ZooKeeper in maniera tale che presenti la struttura che
l'applicazione Spring si aspetta.
Occorre quindi creare sul sistema ZooKeeper scelto per il servizio di congurazione distribuita
almeno il nodo /database, con gli i /nodi url_db, /driverClassName, /username, /pass-
word .
I valori di tali nodi rappresentano le informazioni (congurazione distribuita) di connessione al
database scelto per contenere una tabella chiamata utenti, contenente i campi username e
password, che utilizzeremo per fare il check degli utenti che vogliono loggare.
Il server ZooKeeper usato poi per il servizio di token, deve avere almeno i nodi /clients e /to-
ken_requests.
Come ultimo step di congurazione, vengono indicati nel le congurazioneLocale.properties i path
dei server zookeeper e il presso del nodo da cui creare il mapping delle proprietà per la congu-
razione distribuita.
Come prima cosa si avvia il server ZooKeeper con il comando zkServer.cmd.
A questo punto si lancia il server Tomcat in Eclipse.
L'url a cui è raggiungibile la pagina principale dell'applicazione (quella che nel gergo della tecnologia
Servlet è detto context path) è dato dall'indirizzo del server seguito dall'artifact id di Maven (in
questo caso /sito).
Eettuando un test in locale quindi ci colleghiamo all'indirizzo http://localhost:8081/sito , e ci
verrà presentata la pagina di login
21
I dati da inserire sono quelli di un utente presente nella base dati scelta (altrimenti l'applicazione
ci informa che i dati sono errati o l'utente è già connesso).
Una volta identicato correttamente, il client è in attesa della conferma della registrazione su
ZooKeeper.
22
cliccando sulla penultima icona è possibile avviare la richiesta del token, per il quale il client risulterà
in attesa.
23

More Related Content

What's hot

Java Spring Basics - Donato Andrisani - Gabriele Manfredi
Java Spring Basics - Donato Andrisani - Gabriele ManfrediJava Spring Basics - Donato Andrisani - Gabriele Manfredi
Java Spring Basics - Donato Andrisani - Gabriele ManfrediGabriele Manfredi
 
Mvc e di spring e angular js
Mvc e di   spring e angular jsMvc e di   spring e angular js
Mvc e di spring e angular jsRiccardo Cardin
 
Struttin' on, novità in casa Struts
Struttin' on, novità in casa StrutsStruttin' on, novità in casa Struts
Struttin' on, novità in casa StrutsMarcello Teodori
 
AreaMVC: un'architettura software basata sulla semplicità
AreaMVC: un'architettura software basata sulla semplicitàAreaMVC: un'architettura software basata sulla semplicità
AreaMVC: un'architettura software basata sulla semplicitàGiulio Destri
 
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015Codemotion
 
Introduzione ad angular 7/8
Introduzione ad angular 7/8Introduzione ad angular 7/8
Introduzione ad angular 7/8Valerio Radice
 
Introduzione agli Hooks – Primo Episodio
Introduzione agli Hooks – Primo EpisodioIntroduzione agli Hooks – Primo Episodio
Introduzione agli Hooks – Primo EpisodioAntonio Musarra
 
Liferay: Esporre Web Services Custom
Liferay: Esporre Web Services CustomLiferay: Esporre Web Services Custom
Liferay: Esporre Web Services CustomAntonio Musarra
 
Struts - Overview, Installazione e Setup
Struts - Overview, Installazione e SetupStruts - Overview, Installazione e Setup
Struts - Overview, Installazione e SetupFederico Paparoni
 
Sistemi Context-aware: Esercitazione 3
Sistemi Context-aware: Esercitazione 3Sistemi Context-aware: Esercitazione 3
Sistemi Context-aware: Esercitazione 3Marco Loregian
 
Come portare il profiler di symfony2 in drupal8
Come portare il profiler di symfony2 in drupal8Come portare il profiler di symfony2 in drupal8
Come portare il profiler di symfony2 in drupal8Luca Lusso
 

What's hot (20)

Java Spring Basics - Donato Andrisani - Gabriele Manfredi
Java Spring Basics - Donato Andrisani - Gabriele ManfrediJava Spring Basics - Donato Andrisani - Gabriele Manfredi
Java Spring Basics - Donato Andrisani - Gabriele Manfredi
 
Spring - Ecosistema
Spring - EcosistemaSpring - Ecosistema
Spring - Ecosistema
 
Java lezione 18
Java lezione 18Java lezione 18
Java lezione 18
 
ORM Java - Hibernate
ORM Java - HibernateORM Java - Hibernate
ORM Java - Hibernate
 
Java lezione 17
Java lezione 17Java lezione 17
Java lezione 17
 
Mvc e di spring e angular js
Mvc e di   spring e angular jsMvc e di   spring e angular js
Mvc e di spring e angular js
 
Struttin' on, novità in casa Struts
Struttin' on, novità in casa StrutsStruttin' on, novità in casa Struts
Struttin' on, novità in casa Struts
 
AreaMVC: un'architettura software basata sulla semplicità
AreaMVC: un'architettura software basata sulla semplicitàAreaMVC: un'architettura software basata sulla semplicità
AreaMVC: un'architettura software basata sulla semplicità
 
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015
Java EE facile con Spring Boot - Luigi Bennardis - Codemotion Roma 2015
 
Introduzione ad angular 7/8
Introduzione ad angular 7/8Introduzione ad angular 7/8
Introduzione ad angular 7/8
 
Introduzione agli Hooks – Primo Episodio
Introduzione agli Hooks – Primo EpisodioIntroduzione agli Hooks – Primo Episodio
Introduzione agli Hooks – Primo Episodio
 
Liferay: Esporre Web Services Custom
Liferay: Esporre Web Services CustomLiferay: Esporre Web Services Custom
Liferay: Esporre Web Services Custom
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
Xamarin.android
Xamarin.androidXamarin.android
Xamarin.android
 
Microservices
MicroservicesMicroservices
Microservices
 
Struts - Overview, Installazione e Setup
Struts - Overview, Installazione e SetupStruts - Overview, Installazione e Setup
Struts - Overview, Installazione e Setup
 
Sistemi Context-aware: Esercitazione 3
Sistemi Context-aware: Esercitazione 3Sistemi Context-aware: Esercitazione 3
Sistemi Context-aware: Esercitazione 3
 
Maven - Aprile 2010
Maven - Aprile 2010Maven - Aprile 2010
Maven - Aprile 2010
 
Come portare il profiler di symfony2 in drupal8
Come portare il profiler di symfony2 in drupal8Come portare il profiler di symfony2 in drupal8
Come portare il profiler di symfony2 in drupal8
 
Laravel Framework PHP
Laravel Framework PHPLaravel Framework PHP
Laravel Framework PHP
 

Similar to Progetto SOD Davide Sito

Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGiacomoZorzin
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...artemedea
 
Tesi Discussione
Tesi DiscussioneTesi Discussione
Tesi DiscussioneYeser Rema
 
Dependency injection questa sconosciuta
Dependency injection questa sconosciutaDependency injection questa sconosciuta
Dependency injection questa sconosciutaAndrea Dottor
 
Yii Framework - yes it is rapid web application development (Parte 1)
Yii Framework - yes it is rapid web application development (Parte 1)Yii Framework - yes it is rapid web application development (Parte 1)
Yii Framework - yes it is rapid web application development (Parte 1)brossi676
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...daniel_zotti
 
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...Progetto e implementazione di una pipeline di sviluppo software con tecnologi...
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...Mattia Milleri
 
Progettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerProgettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerAlessandro Mascherin
 
Acadevmy - Angular Overview
Acadevmy - Angular OverviewAcadevmy - Angular Overview
Acadevmy - Angular OverviewFrancesco Sciuti
 
AngularJS – Reinventare le applicazioni web
AngularJS – Reinventare le applicazioni webAngularJS – Reinventare le applicazioni web
AngularJS – Reinventare le applicazioni webLuca Milan
 
Hadoop [software architecture recovery]
Hadoop [software architecture recovery]Hadoop [software architecture recovery]
Hadoop [software architecture recovery]gioacchinolonardo
 
Progettazione e realizzazione di un sistema software per il time logging
Progettazione e realizzazione di un sistema software per il time loggingProgettazione e realizzazione di un sistema software per il time logging
Progettazione e realizzazione di un sistema software per il time loggingVittoriano Muttillo
 

Similar to Progetto SOD Davide Sito (20)

TesiEtta
TesiEttaTesiEtta
TesiEtta
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptx
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
 
Tesi Discussione
Tesi DiscussioneTesi Discussione
Tesi Discussione
 
Dependency injection questa sconosciuta
Dependency injection questa sconosciutaDependency injection questa sconosciuta
Dependency injection questa sconosciuta
 
Yii Framework - yes it is rapid web application development (Parte 1)
Yii Framework - yes it is rapid web application development (Parte 1)Yii Framework - yes it is rapid web application development (Parte 1)
Yii Framework - yes it is rapid web application development (Parte 1)
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
 
Tesi_Adamou
Tesi_AdamouTesi_Adamou
Tesi_Adamou
 
Tesi_Adamou
Tesi_AdamouTesi_Adamou
Tesi_Adamou
 
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...Progetto e implementazione di una pipeline di sviluppo software con tecnologi...
Progetto e implementazione di una pipeline di sviluppo software con tecnologi...
 
Database Data Aggregator
Database Data AggregatorDatabase Data Aggregator
Database Data Aggregator
 
Tesi Todone
Tesi TodoneTesi Todone
Tesi Todone
 
Progettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerProgettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computer
 
Acadevmy - Angular Overview
Acadevmy - Angular OverviewAcadevmy - Angular Overview
Acadevmy - Angular Overview
 
MVC and Struts 1
MVC and Struts 1MVC and Struts 1
MVC and Struts 1
 
Many Designs Elements
Many Designs ElementsMany Designs Elements
Many Designs Elements
 
AngularJS – Reinventare le applicazioni web
AngularJS – Reinventare le applicazioni webAngularJS – Reinventare le applicazioni web
AngularJS – Reinventare le applicazioni web
 
Hadoop SAR
Hadoop SARHadoop SAR
Hadoop SAR
 
Hadoop [software architecture recovery]
Hadoop [software architecture recovery]Hadoop [software architecture recovery]
Hadoop [software architecture recovery]
 
Progettazione e realizzazione di un sistema software per il time logging
Progettazione e realizzazione di un sistema software per il time loggingProgettazione e realizzazione di un sistema software per il time logging
Progettazione e realizzazione di un sistema software per il time logging
 

Progetto SOD Davide Sito

  • 1. Università degli Studi di Napoli Parthenope Facoltà di Scienze e Tecnologie Corso di laurea in Informatica Applicata PROGETTO DI SISTEMI OPERATIVI DISTRIBUITI Studio ed implementazione dell'interazione tra Spring Framework e Apache Zookeeper Davide Sito Matricola 0120000129 Anno Accademico 2014-2015
  • 2. i
  • 3. Abstract Nel presente lavoro viene mostrata l'implementazione di una web application la cui architettura fa utilizzo sia del framework Spring che di Apache Zookeeper, sia per ottenere un sistema di congu- razione distribuito sia per creare un sistema di gestione token. ii
  • 4. Contents 1 Obiettivi del presente lavoro 1 2 Introduzione al framework Spring 2 2.1 Inversion of Control (IoC) e Dependency Injection (DI) . . . . . . . . . . . . . . . . 2 2.2 Spring IoC container e beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.2.1 Container Conguration le . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2.2 Beans Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.3 Beans Lifecycle Management . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2.4 PropertyPlaceholderCongurer e Proprietà . . . . . . . . . . . . . . . . . . . 9 2.3 Java Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4 JDBCTemplate e DAO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5 Spring MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5.1 Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5.2 Validazione forms , Binders e Custom Annotations . . . . . . . . . . . . . . . 13 3 Applicazione 14 3.1 ZooKeeper per congurazione distribuita . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.2 ZooKeeper per servizio di token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.3 Architettura dell'applicazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.4 Struttura del servizio di congurazione distribuito . . . . . . . . . . . . . . . . . . . 16 3.5 Log degli utenti ed heartbeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.6 Servizio di token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4 Congurazione ed esempio d'uso 21 iii
  • 5. 1 Obiettivi del presente lavoro Nel presente lavoro si è voluto studiare ed approfondire l'architettura del diusissimo framework Spring, che ore un insieme notevole di strumenti e moduli utilissimi per la creazione non solo di web-application (nel cui caso la scelta più naturale è Spring MVC) ma per l'organizzazione (e riorganizzazione) di qualunque tipo di applicazione basata su Java. Si è deciso quindi di costruire, sulla base di un'architettura strutturata secondo la logica ed i pattern di Spring, un'integrazione tra la nostra applicazione ed il framework Apache ZooKeeper, che ore un set di primitive utili per la progettazione di algoritmi di sincronizzazione / controllo distribuiti. In tal senso quindi ZooKeeper è risultato utile sia per l'implementazione di un servizio di token distribuito (riprendendo la logica di un plugin già esistente introdotto per le nuove versioni di Spring 4.x ma fornendo una nostra implementazione da zero) sia per creare un servizio di congurazione distribuita. In tal modo la web application fa da interfaccia tra quello che è l'utilizzatore (il client, ovvero un browser web) che usa un protocollo stateless, e il servizio basato su ZooKeeper (tipicamente connection oriented). 1
  • 6. 2 Introduzione al framework Spring Viene qui di seguito fornita una panoramica di quella che è l'architettura del framework, analizzan- done le componenti principali, e soermandosi principalmente su quelle che sono le caratteristiche da noi utilizzate. Volendo citare la documentazione uciale The Spring Framework is a Java platform that provides comprehensive infrastructure support for developing Java applications. Spring handles the infrastructure so you can focus on your applica- tion. Grazie a Spring è possibile semplicare notevolmente la struttura di un'applicazione in quanto tramite l'imposizione di determinate best-practices, questo permette la separazione tra le classi coinvolte, con una notevole riduzione (se non eliminazione) delle dependencies tra gli oggetti, e la semplicazione di questi in POJOs (plain old java objects, ovvero oggetti dotati esclusivamente di getter e setters) tramite lo spostamento all'esterno della logica applicativa. 2.1 Inversion of Control (IoC) e Dependency Injection (DI) Con questi due termini ci si riferisce al principio più importante che sta alla base del funzionamento di Spring, e di tutti i moduli e strumenti che questo fornisce ai livelli più alti. Chiunque abbia mai lavorato con Java è consapevole di come tutto sia orientato alle classi. Persino l'entry point dell'applicativo (il classico main) è plasmato come una classe. Appare quindi naturale che, anche nel caso dell'applicazione più semplice, si vengano a creare notevoli relazioni di utilizzo e/o composizione tra oggetti di classi diversi. All'aumentare quindi della dimensione e della complessità di un'applicazione, aumenta in maniera direttamente pro- porzionale la complessità di tali relazioni. Per evitare quindi di ritrovarsi in una situazione in cui un'architettura diventi dicilmente gestibile, generalmente si adottano determinati paradigmi di progettazione atti a diminuirne la complessità ridistribuendo le relazioni secondo pattern conosciuti. Alcuni dei più famosi sono le Factory, le Abstract Factory, i Prototype etc... Ma si tratta appunto di best practices, che sta al programmatore seguire ed implementare (con un grado di successo spesso altamente variabile). E' in questo scenario che Spring risulta estremamente utile in quanto permette di eliminare total- mente (in ciascuna delle classi) la necessità di istanziare (e quindi controllare, conoscere) qualsivoglia altro oggetto. In tal modo non esiste una classe che ne controlla un'altra, ma piuttosto esiste un 2
  • 7. componente completamente esterno che si occupa di iniettare tae conoscenza a runtime, tramite i setter di quelli che diventano dei semplici POJOS nel momento in cui vengono privati della logica applicativa. In tal modo il numero delle relazioni che non vengono delegate al componente esterno, e che quindi rimangono a far parte delle nostre classi java, si riduce drasticamente. Come anticipato in precedenza, questo principio si applica a tutto l'(enorme) set di funzionalità oerto dal framework, che si articola in oltre 20 moduli suddivisi nei gruppi principali mostrali nel seguito. Il Core Container fornisce le funzionalità fondamentali del framework, quelle grazie alle quali è appunto possibile implementare l'Inversion of Control e la Dependency Injection. Il modulo conte- nente l'AOP (Aspect Oriented Programming) fornisce una serie di strumenti utili per inserire una logica di business secondo il paradigma omonimo, che permette di centralizzare in un sol punto quello che è il codice implementativo di operazioni ripetute più volte in tutta l'applicazione, evi- tando quindi le duplicazione (ad esempio per il logging). Il Data Access/ Integration module permette l'utilizzo di interfacce e connettori per l'accesso a basi di dati tramite metodologie classiche (quali ad esempio i connettori JDBC) o secondo principi più moderni quali ad esempio l'ORM (Object-Relational Mapping) che permette di accedere ad una base dati utilizzando la logica degli oggetti persistenti. Il modulo Web fornisce strumenti per la progettazione di Web Application secondo gli standard di 3
  • 8. Java EE (con Spring MVC ad esempio si usano gli Spring Beans come se fossero degli Enterprise Java Beans) mentre il modulo Test fornisce funzionalità per il testing. 2.2 Spring IoC container e beans Come descritto in precedenza l'IoC (sinonimo di Dependency Injection) permette agli oggetti che rappresentano la nostra applicazione di denire le loro dipendenze (le altre classi con cui lavorano) soltanto come parametri dei costruttori e/o di metodi setter. Il Container è il componente es- terno (è il cuore di Spring) che si occupa di fare l'injection di tali dipendenze. Questo procedimento è detto Inversion of Control poichè nell'approccio classico è l'oggetto che controlla che si occupa di istanziare (al suo interno) l'oggetto controllato. Tale container è rappresentato dall'interfaccia BeanFactory che permette di recuperare gli oggetti dopo che sono state risolte le dipendenze ed eettuate le injections. Nel caso pratico Spring ore un'interfaccia denominata ApplicationCon- text che copre tutte le funzionalità del BeanFactory, oltre ad introdurre funzionalità aggiuntive. Nel gergo di Spring, gli oggetti congurati tramite il BeanFactory, prendono il nome di Beans. Il modo in cui il programmatore istruisce il container relativamente a come questo debba combinare gli oggetti e produrre le dipendenze, è tramite un le xml di congurazione (no a Spring 2.x era l'unica opzione) o tramite Java annotations (dalla 3.x in poi). Si noti che l'ApplicationContext è generalmente fornito come punto di partenza di qualunque progetto, quindi il programmatore deve preoccuparsi esclusivamente di fornire i metadati per la congurazione delle dipendenze, e di richiamare l'application context dove necessario. 4
  • 9. 2.2.1 Container Conguration le Si tratta generalmente di un le xml (anche se come descritto in precedenza esistono metodi alter- nativi per fornire la congurazione al container) nel quale il programmatore fornisce la dichiarazione della gerarchia delle relazioni tra i bean. . Nella forma più elementare, ciascuna entry rappresenta un bean, con un proprio id (necessario per ottenere, nel codice Java, l'istanza di questo) e la classe a cui l'istanza di tale oggetto appartiene. Nello screen possiamo osservare un esempio di congurazione di due bean, entrambi di classe Utente, ciascuno rappresentante una diversa istanza. . La classe utente non è altro che un POJO dotato di setter e getter per le proprietà nome e cognome. Nel le di congurazione quindi, utilizzando il tag property è possibile indicare che valore (primitivo in questo caso, non trattandosi ancora di dependency injection) il container dovrà passare ai setter di tali oggetti.. Nel codice java quindi, sarà possibile ottenere le due istanze create dal container (i due beans) semplicemente invocando il metodo apposito dal bean factory (in questo caso il cong le viene cercato nel classpath dell'applicazione) 5
  • 10. Nell'esempio precedente è stata mostrata l'injection di un valore primitivo. Ma come già detto la vera potenza di Spring sta nella dependency injection tra oggetti. Immaginiamo ad esempio di voler creare un oggetto di classe Rubrica, che si occupi di mantenere una serie di Utenti e di stamparne le caratteristiche. Il codice del pojo Rubrica apparirebbe come segue: . 6
  • 11. mentre il le di congurazione utilizzerebbe la proprietà ref per fare injection con bean preceden- temente dichiarati (nello stesso le di congurazione o in altri). Nell'esempio mostrato non viene fatto uso di una Collection per conservare i vari bean injected nell'oggetto di classe Rubrica. Ovviamente tale funzionalità è fornita da Spring, che prevede l'utilizzo di strutture quali liste, hash e sets. Un'altra funzionalità di Spring Core estremamente utile è l'autowiring , che permette di inferire quale bean utilizzare per l'injection sulla base del nome della proprietà (che deve corrispondere all'id di un bean precedentemente dichiarato) o in base al tipo di quest'ultima. 2.2.2 Beans Scope Quando si parla di scope dei Beans in Spring ci si riferisce al modo in cui viene gestita dal container, la creazione dell'istanza di questo. Tra gli scope più utilizzati abbiamo il Singleton ed il Prototype. Nel Singleton il container crea un'istanza del bean all'avvio dell'applicazione, e tutte le richieste eettuate all'application context ritorneranno la stessa istanza. Tale scope è quello di default per le dichiarazioni dei beans. 7
  • 12. A dierenza dei bean Singleton, per i bean di tipo Prototype (che vanno dichiarati come tali nel momento in cui vengon deniti nel le di congurazione) non viene creata nessun'istanza all'avvio del container, e tale allocazione viene rimandata no al momento in cui viene richiesto l'oggetto. Inoltre ciascuna richiesta all'application context fornirà un'istanza diversa del bean. 2.2.3 Beans Lifecycle Management Spring permette al programmatore di scrivere codice che va ad inserirsi nei vari stadi del ciclo di vita di un bean. In prima istanza è possibile dichiarare un metodo (nel POJO) che venga richiamato nel momento in cui termina l'injection dell'ultimo setter di quest'ultimo (tramite l'utilizzo della proprietà xml initMethod) e lo stesso per la fase relativa alla distruzione del bean (destroyMethod) . Alter- nativamente, volendo lavorare direttamente col codice java è possibile ottenere lo stesso risultato facendo implementare all'oggetto pojo le interfacce InitializingBean e DisposableBean. In questi modi è possibile creare metodi specici per una determinata classe di bean. Ma come fare 8
  • 13. qualora si volesse creare un comportamento comune, e indipendente dal tipo di oggetto istanziato ? In questo caso ci viene in aiuto l'interfaccia BeanPostProcessor che ci permette di creare un bean i cui 3 metodi (ereditati dall'interfaccia) partono rispettivamente prima e dopo l'injection di qualunque altro bean, e prima della distruzione di questo. Esiste inoltre la possibilità di dichiarare bean di tipo BeanFactoryPostProcessor ovvero bean i cui metodi partono subito prima e subito dopo la congurazione del container. I bean che implementano tale interfaccia per esempio sono quelli che permettono di utilizzare le EL expression all'interno dello stesso le di congurazione, in maniera tale da centralizzare una serie di proprietà (coppie chiave-valore) in le di tipo properties (si legga in seguito). 2.2.4 PropertyPlaceholderCongurer e Proprietà Spring mette a disposizione la possibilità di utilizzare le di tipo *.properties per delocalizzare determinati valori (un po' come accade per le stringhe in Java Android) e di riferirsi a tali valori utilizzando le EL expressions, ovvero istruzioni del tipo ${nomeproprieta}.. Per sfruttare questa possibilità occorre innanzitutto creare un le *.properties e metterlo nel class- path del progetto. A questo punto basta creare un bean di tipo PropertyPlaceholderCongurer (o creando una propria classe che implementi tale interfaccia) -si noti che spesso in Spring si parla di creazione di bean di un tipo appartenente ad un'interfaccia, ma si tratta appunto del tipo formale, poichè ovviamente un'interfaccia non può essere istanziata- settando il path del le di tipo properties come valore della proprietà location di quest'ultimo. . Essendo un oggetto di tipo PropertyPlaceholderCongurer innanzitutto un BeanFactoryPostProces- sor, questo si occupa automaticamente, nel momento in cui termina la congurazione del container tramite il le xml, di risolvere tutte le EL expressions presenti in quest'ultimo, nel loro valore reale (prelevato dal le *.properties). . La potenza ed il vantaggio dell'utilizzo di tali properties sta nel fatto che è possibile creare una struttura gerarchica (ad esempio ${utente.nome} e ${utente.cognome} ).. E non è tutto, poichè utilizzando al posto del PropertyPlaceholderCongurer un PropertySource- sPlaceholderCongurer, è possibile ottenere lo stesso risultato di cui sopra, ma con l'aggiunta del fatto di avere a disposizione tali proprietà anche a livello del codice java e dell'application con- text. Proprio in questo consiste uno degli aspetti del nostro applicativo, ovvero nel fatto di utilizzare la 9
  • 14. struttura dei nodi di un server Zookeeper (e i loro valori) come un insieme gerarchico di proprietà, che rappresentano quindi un sistema di congurazione distribuita (invece che localizzata, come nel caso di un le *.properties). 2.3 Java Annotation Dalla versione 3.0 di Spring è stata introdotta la possibilità di passare da una congurazione ef- fettuata esclusivamente nel le di congurazione *.xml, all'utilizzo di annotazioni Java, da inserire quindi direttamente nel codice sorgente delle classi. Si noti che inizialmente la transizione è stata soltanto parziale, e che quindi per poter utilizzare parecchie delle annotazioni, era comunque nec- essario dichiarare un BeanPostProcessor associato (nel le xml di congurazione) che si occupasse di trasformare tali annotazioni in un equivalente listato xml (invisibile al programmatore). Ad esempio qualora si volesse utilizzare il tag @Autowired nel codice java, per indicare che una proprietà di un pojo è soggetta a dependency injection da parte del container, sarebbe stato neces- sario dichiarare (nel le .xml) un bean di tipo AutowiredBeanPostProcessor. Tuttavia oggi, a conclusione di una transizione totale verso le annotazioni, basta utilizzare il tag context:annotation-cong nel le *.xml per evitare di dover dichiarare tali bean a supporto di ciascuna specica annotazione. In tal modo è possibile utilizzare anche soltanto esclusivamente le annotazioni, senza dover dichiarare nulla nel le di congurazione xml. Alcune delle annotazioni più diuse sono: • @Autowired: permette di specicare la dependency innjection tramite autowiring • @Resource(name=...) : specica la dependency injection classica • @Value(${proprieta}): injection di un valore estratto dalle properties caricate da un Prop- ertyResourcesPlaceholderCongurer nel contesto dell'applicazione • @PostConstruct e @PreDestroy: il metodo taggato parte dopo il costruttore di qualunque bean creato dall'application context • @Component: la classe dichiarata con questo tag equivale alla denizione di un bean nell'xml. Il nome della classe per default è l'id associato. • @Conguration: la classe annotata con questo tag indica al container che si tratta di una factory per bean (ovvero di una classe i cui metodi generano beans). Tali bean sono specicati annotando i metodi della classe con @Bean 10
  • 15. Questi sono soltanto alcuni dei tantissimi tag che Spring mette a disposizione per le più disparate funzionalità, ed un elenco omnicomprensivo di questi va al di la dello scopo della presente relazione. 2.4 JDBCTemplate e DAO Spring prevede l'accesso alle basi di dati utilizzando diversi approcci dierenti, ma che condividono tutti la possibilità di seguire quello che è il pattern del Data Access Object (DAO). Un DAO è un'interfaccia (interfaccia nel senso di java interface) denita dal programmatore che presenta quelli che sono i metodi di interazione tra il programmatore e la base dati, relativamente ad una specica entità (che solitamente è un bean). Ad esempio un DAO relativo alla classe Studente potrebbe presentare metodi quali i getAllStudenti per ottenere la lista degli studenti presenti nel DB, i getStudente(Nome) etc... Sta al programmatore implementare un DAO in una classe che preveda l'accesso ad uno specico DB (MySql, Oracle, Postgres...) utilizzando un particolare tipo di tecnologia (JDBC, Hibernate etc...). Volendo ad esempio implementare il DAO Studente, per un DB Oracle usando JDBC, creeremmo una classe che implementa il DAO e gestisce direttamente JDBC. Spring ci semplica la vita fornendo determinati Templates, a seconda di quella che è la tecnologia di accesso scelta. Nel caso ad esempio di JDBC, Spring ci ore un JDBCTemplate, ovvero una classe capace di gestire per noi tutte le operazioni di routine relative all'apertura e alla chiusura di una ses- sione/transazione verso il DB. In tal modo il programmatore deve soltanto implementare i metodi ereditati dall'interfaccia DAO, e occuparsi di congurare il JDBCTemplate anchè possa essere usato in tali metodi. L'uso di JDBCTemplate si basa sulla classe DataSource che (istanziata solitamente come bean singleton) permette di congurare tutte le opzioni di accesso della base dati target del connettore. 2.5 Spring MVC 2.5.1 Architettura Tra gli innumerevoli moduli e strumenti che il framework mette a disposizione, quello più usato forse è proprio Spring MVC. Si tratta di un framework per la creazione di web application secondo il paradigma Model-View- Controller, che è seguito seguendo la struttura del Primary Controller. 11
  • 16. In una web application la View è la pagina che viene mostrata all'utente, il Controller è l'elemento (di back-end, sul server) che si occupa di gestire, secondo una logica programmata, quelle che sono le richieste http sui vari url, ritornando a seconda del caso la view adatta, mentre i Model sono gli oggetti che rappresentano le informazioni, che vengono scambiati sia tra il client ed il server, sia tra le diverse componenti di back-end. Seguendo questo paradigma si usa spesso progettare l'applicazione utilizzando un singolo controller principale, che intercetta tutte le richieste relativamente a tutti gli indirizzi, e a seconda del mapping degli url, invoca il controller apposito. Tale controller ritorna un model contenente informazioni ed un nome logico di una view, che viene intercettato (assieme al model) dal controller principale e tradotto da un View Resolver, che ritorna la view sica (una pagina html, jsp etc...) al controller principale, che in ultima istanza la manda (assieme al model) all'utente (il browser). Questa è proprio la struttura di un'applicazione Spring MVC, che si basa a sua volta sulla tec- nologia Java Servlet (il primary controller infatti è proprio una servlet congurata, nel classico deployment descriptor delle servlet ovvero il web.xml, per intercettare tutte le richieste ricevute dall'applicazione). Dal punto di vista del programmatore, la Dispatcher Servlet è automaticamente congurata (tramite 12
  • 17. la generazione del deployment descriptor, ovvero il web.xml) in maniera tale da riconoscere quelli che sono i controller da noi deniti. Per denire un controller abbiamo due possibilità (come del resto per qualunque altra funzione a partire da Spring 3.x) ovvero possiamo farlo nel le di congurazione xml (solitamente nel caso di web application si utilizzano due application context, uno generico, ed uno associato direttamente al contesto web della dispatcher servlet, quindi questo porta ad avere due le di congurazione xml) ed in tal caso l'id usato per il bean diventa l'url sul quale è mappato il controller, oppure tramite annotazioni utilizzando il tag @Controller che utilizzato a livello di classe, indica al WebAppli- cationContext che i metodi di quella classe verranno mappati su determinati url. Tale mapping (detto HandlerMapping) viene eettuato utilizzando @RequestMapping prima dei metodi del controller che vogliamo che rispondano ad un determinato url. Nel tag RequestMapping è possibile specicare anche il tipo di richiesta HTTP (POST/GET) al quale rispondere. Tali metodi dovranno occuparsi di ritornare o una stringa rappresentante il nome logico della view, o un Model (al cui interno è specicato il nome della view) contenente i dati desiderati. Sem- pre in questi metodi è possibile ottenere (come parametro di input) l'HttpServletRequest e l'HttpServletResponse che sono due delle più importanti componenti utilizzati nelle web appli- cation basate su tecnologia Servlet, oltre che direttamente i parametri della request http. 2.5.2 Validazione forms , Binders e Custom Annotations Una delle caratteristiche più interessanti dei controller di Spring MVC è la possibilità di ottenere beans a runtime, nel momento in cui un client invia una richiesta http tramite un form di compi- lazione dati, tramite un mapping tra i campi del form e le proprietà di un pojo. Tramite il tag @ModelAttribute è infatti possibile ricevere come input (nel metodo del controller) un bean già riempito con i dati inseriti nel form che l'ha invocato.. Immaginiamo ad esempio di avere un form html che invochi, nel momento del submit, una pagina chiamata invioForm. E' possibile allora creare un controller Spring in ascolto su tale url, che riceva i parametri inseriti nel form direttamente nella forma di un bean mappante (ovviamente deve esserci corrispondenza univoca tra i nomi dei campi del form e le proprietà del pojo usato per il mapping). 13
  • 18. ritornando quindi la view col nome logico dati, sarà possibile accedere in questa ai dati inseriti nel form proprio come dei parametri di request, oltre ovviamente a poter essere elaborati dal controller che sta in mezzo. Ed è proprio in quest'ultimo scenario che vanno ad inserirsi due strumenti importantissimi di Spring MVC, ovvero il Binder ed il Validator. Il Binder è l'elemento che si occupa di trasformare le stringhe fornite in un form, nelle variabili di classe corrispondenti nel mapping bean. Tramite il tag @InitBinder è possibile individuare un metodo che si occupi di inizializzare e/o modicare il binder di sistema, denendo in tal modo un proprio PropertyEditor ad hoc. Ancora più importante è l'utilizzo di un Validator. Si tratta di un elemento che si occupa di validare i valori inseriti nei campi del form, nel momento in cui vengono trasformate (dal binder) nelle variabili del mapping bean. I campi che sono sottoposti a validazione sono quelli che corrispondono a variabili che nel bean sono annotate usando una delle annotazioni di validazione fornite da Spring (@Size, @Max, @Min etc...) . Aggiungendo il tag @Valid al tag @ModelAttribute del bean in input nel metodo del controller, ed inserendo come ulteriore parametro di input un oggetto di tipo BindingResult sarà possibile nel controller valutare il risultato della validazione. 3 Applicazione Lo scopo dell'applicazione sviluppata è quello di mostrare un esempio di integrazione tra una web application in Spring, ed un ensemble ZooKeeper.. Dal punto di vista delle funzionalità, ZooKeeper è utilizzato sia per creare un servizio di congu- razione distribuita, sia per fornire un servizio di sincronizzazione distribuito tramite token.. 14
  • 19. 3.1 ZooKeeper per congurazione distribuita L'obiettivo è utilizzare la struttura a lesystem di ZooKeeper per individuare una serie di propri- età da importare ed utilizzare nell'ecosistema di Spring come se si usasse un le di tipo properties.. Come visto in precedenza in Spring è possibile utilizzare le EL Expression (espressioni del tipo ${proprietà}) per fare injection di proprietà memorizzate in le locali di tipo properties.. La struttura di un le properties è gerarchica, e tale gerarchia viene mantenuta dalle proprietà una volta che siano state caricate nel contesto dell'applicazione da un bean di tipo PropertySources- PlaceholderCongurer (nel modo mostrato precedentemente). In tal modo quindi, mappiamo la gerarchia dei nodi di un server ZK, utilizzando il valore dei dati contenuti in ciascun nodo, in una gerarchia di proprietà.. In tal modo è possibile delocalizzare le proprietà e mantenerle in ZK invece che in un le di tipo prop- erties locale all'applicazione. Cambiando quindi il valore di un nodo, e restartando l'applicazione, cambia il valore delle proprietà injected. 3.2 ZooKeeper per servizio di token Dal punto di vista del servizio di sincronizzazione distribuita, vengono usate le primitive fornite da ZooKeeper per implementare un sistema in cui per ciascun client connesso alla web application su Spring, viene creato un corrispondente client su ZooKeeper. Le richieste di token, eettuate dagli utenti connessi, vengono mappate da nodi in ZooKeeper, e tramite un master viene implementata la logica del servizio di sincronizzazione.. In tal modo Spring fa da middleware tra il client (il browser) che comunica usando richieste http (tipicamente stateless) e le primitive di ZooKeeper che necessitano di una sessione (quindi di una comunicazione continuativa). 3.3 Architettura dell'applicazione Viene mostrata di seguito l'architettura generale dell'applicazione. 15
  • 20. Da un punto di vista globale, la web application in Spring utilizza due controller principali.. Un primo controller mappa quelli che sono gli url utilizzati per fare il log in dell'utente, tramite nome e password.. Tali informazioni sono ottenute utilizzando un pattern DAO da una base di dati.. Le informazioni relative alla base dati sono fornite tramite il servizio di congurazione distribuita su ZK (per fornire tale servizio, allo start del contesto dell'applicazione si utilizza un bean apposito). . Una volta che un client risulta loggato, si collega al secondo controller, che si occupa di gestire (tramite multithreading) le operazioni per fornire il servizio di tokening. . Tecnicamente è possibile utilizzare due server ZK dierenti, uno per la congurazione ed uno per il servizio di token.. Il modo in cui comunicano il client e l'applicazione Spring, è tramite http request, in cui tra i vari parametri è inserita anche una stringa JSON che indica lo stato del client.. Per ottenere un comportamento stile sessione, viene usato javascript lato client, usando le variabili di stato che vengono inviate al client via JSON. Tutte le strutture dati utilizzate nella web application per memorizzare le informazioni sui client, sono sincronizzate in accesso mutualmente esclusivo, essendo più di un thread coinvolto. 3.4 Struttura del servizio di congurazione distribuito Come descritto in precedenza per ottenere il servizio di congurazione distribuita tramite ZooKeeper, si vuole far sì che la struttura del server ZK venga mappata come una gerarchia di proprietà im- 16
  • 21. portate nell'application context di Spring. Per fare questo quindi utilizziamo due componenti principali: un bean con scope singleton, anchè questo venga istanziato all'avvio dell'applicazione, ed un connettore che faccia il wrapping delle primitive oerte da ZooKeeper per accedere in lettura alla struttura dei nodi e dei rispettivi gli (con i valori). Viene usato un unico le di tipo properties, per alcune proprietà non delocalizzate, tra cui l'url del server zookeeper usato per la congurazione distribuita, ed il nodo da usare come root (ovvero da quale nodo in poi iniziare a mappare tutti i gli come valori chiave/valore di tipo properties). Il connettore quindi, tramite un metodo lanciato dal bean iniettore quando l'application context è partito, visita ricorsivamente la struttura del server zookeeper, e restituisce una mappa chiavi/valori, dove le chiavi sono i path gerarchici di tutti i nodi zk trovati dal root in poi, e i valori sono quelli prelevati da ciascun nodo. A questo punto viene quindi creato in Spring un nuovo PropertySourcesPlaceholderCongurer che viene poi aggiunto al contesto (che viene refreshato). In tal modo, dopo qualche secondo, nel contesto dell'applicazione saranno disponibili le proprietà fornite, tramite zookeeper, in maniera distribuita e decentralizzata. Nel nostro esempio questo servizio viene utilizzato per memorizzare i dati relativi al DB (url DB, tipo di connettore usato, nome DB, password) da utilizzare per accedere alle password/username degli utenti, quando questi cercano di eettuare il login. Il vantaggio è che qualora si volesse cambiare il DB, basterebbe cambiare il valore dei nodi sul server ZooKeeper, riavviando l'applicazione. Le informazioni relative al DB vengono iniettate in un bean di tipo DataSource, che viene passato al JDBCTemplate utilizzato da un nostro bean che segue il pattern Data Access Object, per imple- mentare le operazioni di accesso al db necessarie per ottenere le coppie nome utente/password. Tali informazioni sono utilizzate da un validatore (di supporto ad una nostra annotazione custom). 3.5 Log degli utenti ed heartbeat All'avvio l'utente si collega al root url dell'applicazione (in Spring MVC questo è dato dal path del server e dall'artifact ID di Maven) sul quale risponde il primo dei due controller, inviando come view un form di login. Quando l'utente inserisce i dati di login, il submit del form avviene ad un url sul quale è map- pato un metodo del controller principale, che tramite la tecnica del @ModelAttribute (descritta in precedenza) e l'uso di una nostra annotazione custom denominata @CheckDatiSuDb fa si che il binder di Spring faccia l'injection dei dati inseriti nel form in un bean di tipo Utente, e invochi un 17
  • 22. validatore fornito da noi che si colleghi al DB (tramite il bean DAO sui cui viene fatto l'injection delle proprietà di congurazione distribuita estratte da ZooKeeper) e controlli che esista quella combinazione di utente/password. In caso aermativo l'utente viene indirizzato ad un url su cui è mappato il secondo controller, dopo aver inserito nella richiesta http il nome dell'utente come parametro (in questo modo indichiamo al secondo controller che l'utente è loggato). Quando un utente arriva al secondo controller è sicuramente loggato, ma a questo punto occorre che gli si crei un nodo corrispettivo su ZooKeeper. Diremo che l'utente è confermato su ZooKeeper quando oltre ad essere loggato, risulta avere un nodo in /clients/ su ZooKeeper. Quando un utente arriva per la prima volta sul secondo controller, il suo stato non è confermato (il controller se ne accorge in quanto l'utente ha il parametro nome settato, ma non vi è alcuna traccia dell'utente nelle strutture dati globali che tengono traccia dei clients connessi). A quel punto il controller aggiunge entry del client in una sola delle strutture (quella relativa allo stato) e avvia un thread che, tramite un connettore ad hoc, si occupa di richiedere su ZooKeeper la creazione di un nodo in /clients/. Prima che tale thread concluda il suo compito, ed indipendentemente dall'esito, il controller rimanda response http al client in modo tale che per le successive richieste (inviate dal client PRIMA che il thread di cui sopra abbia concluso) il controller si accorgerà che, esistendo un'unica entry relativa allo stato per quel client, si tratta di un utente che sta attendendo che il thread di cui sopra abbia terminato la creazione su ZK del nodo. Fin quando rimane in questo stato, l'utente si dice in wait per la conferma su ZK. 18
  • 23. Quando il thread termina correttamente, generando un nodo su ZooKeeper che rappresenti tale utente, vengono aggiunte tutte le informazioni relativamente al client, nelle strutture dati globali del controller Spring (nome utente nella lista utenti, stato aggiornato, connettore usato per connettersi a Zk nella lista dei connettori, e stringa JSON utilizzata per la comunicazione dello stato client- side), e viene avviato un thread di servizio denito checker che si occupa di controllare ogni tot secondi che l'utente abbia inviato un heartbeat. Un heartbeat del client è una semplice richiesta http (che il client invia ciclicamente grazie ad uno script javascript, assieme alla stringa json che rappresenta il suo stato). Se il checker trova che l'utente ha inviato heartbeat, risetta a false il suo stato, in maniera tale che il client, anchè venga considerato vivo, debba reinviare un nuovo heartbeat (che faccia impostare lo stato a true) prima del successivo controllo del checker. Nel momento in cui un checker si accorge che un client è disconnesso, esegue la chiusura del connettore e la pulizia delle strutture dati. 3.6 Servizio di token Quando un client risulta autenticato e confermato (connesso) utilizza lo stato delle sue variabili locali (lato client, modicabili tramite l'interazione del client nel browser e inviate come stringa json) per comunicare al controller le proprie scelte. Per implementare tale servizio, vengono usati tre nodi nel server ZooKeeper. Un nodo /master, utilizzato dal master thread per registrare un watcher sulla ricezione del token (un client riceve token quando viene settato un nuovo valore per il dato conservato nel nodo ad esso 19
  • 24. associato). Un nodo cede il token al master ogni volta che ha terminato di utilizzarlo. Un nodo /clients, sotto il quale vengono creati (come gli) i nodi associati ai client connessi. Quando un client richiede un token, dopo aver registrato in ZK la propria richiesta, setta un watcher sul cambiamento del valore contenuto nel nodo ad esso associato, in modo tale da sapere quando riceverà lo riceverà. Un nodo /token_requests sotto il quale sono creati i nodi relativi alle richieste di token (ciascuno con un nome indicativo del client). Quando il master riceve il token, controlla la lista delle richieste in /token_requests: se ne trova almeno una, cede il token al client associato e ritorna in attesa di ricevere di nuovo token, se la lista è vuota, setta direttamente un watcher su token_requests. Dal punto di vista del client quindi, nel caso in cui il client esprima la volontà di ottenere il token, e non lo abbia già (e non lo stia aspettando) viene messo in wait per la ricezione di questo. Il controller crea quindi un thread che si occupa, tramite il connettore ZK associato al client (precedentemente già utilizzato per eettuare la creazione del nodo in /clients/ ) di creare un nuovo nodo (con un nome indicativo del client) come glio di /token_requests, e di registrare un watcher sul valore del nodo in /clients. Il watcher, alla ricezione del token, setta lo stato delle variabili di stato del client in maniera tale da indicare che questo ha ricevuto il token (oltre a cancellare il nodo rappresentante la richiesta da /token_requests). Nel caso in cui il client non voglia il token, ed il suo stato indichi che lo possiede, si tratta allora di una cessione, quindi settiamo di conseguenza gli stati relativi al possesso, e rimettiamo il token nel sistema (ricedendolo al master). Inne nel caso in cui un client non voglia il token ma il suo stato indichi che è in attesa di riceverlo, vuol dire che è stata annullata una richiesta non ancora soddisfatta. Viene allora eliminato il nodo della richiesta pendente e viene aggiornato lo stato del client. Oltre ai thread associati ciascuno ad un utente diverso quindi, è necessario che il controller lanci un thread che rappresenti il coordinatore del sistema (il master). Si tratta di un thread creato come bean singleton, il cui metodo principale (il classico run) parte dopo che sono stati invocati i setter. Questo thread utilizza un connettore ad hoc per il master che permette di creare (all'avvio dell'applicazione) il nodo /master, e di registrare inizialmente il watcher sul cambio del valore di quest'ultimo. 20
  • 25. 4 Congurazione ed esempio d'uso Il testing dell'applicazione è stato eettuato usando Eclipse Spring Tool Suite. Il server usato è un'istanza di Apache Tomcat. Per gestire le dipendenze (sia di ZooKeeper che di Spring) è stato usato Maven . Come primo step occorre congurare ZooKeeper in maniera tale che presenti la struttura che l'applicazione Spring si aspetta. Occorre quindi creare sul sistema ZooKeeper scelto per il servizio di congurazione distribuita almeno il nodo /database, con gli i /nodi url_db, /driverClassName, /username, /pass- word . I valori di tali nodi rappresentano le informazioni (congurazione distribuita) di connessione al database scelto per contenere una tabella chiamata utenti, contenente i campi username e password, che utilizzeremo per fare il check degli utenti che vogliono loggare. Il server ZooKeeper usato poi per il servizio di token, deve avere almeno i nodi /clients e /to- ken_requests. Come ultimo step di congurazione, vengono indicati nel le congurazioneLocale.properties i path dei server zookeeper e il presso del nodo da cui creare il mapping delle proprietà per la congu- razione distribuita. Come prima cosa si avvia il server ZooKeeper con il comando zkServer.cmd. A questo punto si lancia il server Tomcat in Eclipse. L'url a cui è raggiungibile la pagina principale dell'applicazione (quella che nel gergo della tecnologia Servlet è detto context path) è dato dall'indirizzo del server seguito dall'artifact id di Maven (in questo caso /sito). Eettuando un test in locale quindi ci colleghiamo all'indirizzo http://localhost:8081/sito , e ci verrà presentata la pagina di login 21
  • 26. I dati da inserire sono quelli di un utente presente nella base dati scelta (altrimenti l'applicazione ci informa che i dati sono errati o l'utente è già connesso). Una volta identicato correttamente, il client è in attesa della conferma della registrazione su ZooKeeper. 22
  • 27. cliccando sulla penultima icona è possibile avviare la richiesta del token, per il quale il client risulterà in attesa. 23