Professional Documents
Culture Documents
Traversi Simone
29 gennaio 2010
Indice
Introduzione
0.1 0.2 0.3 Panoramica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le innovazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . I moduli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
iv
iv iv v
I Core Technologies
1 Il contenitore IoC
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 Creazione di un bean . . . . . . . . . . . . . . . . . Congurazione di un bean . . . . . . . . . . . . . . Costruire una rete di oggetti . . . . . . . . . . . . . Le collezioni . . . . . . . . . . . . . . . . . . . . . . 1.4.1 I generics . . . . . . . . . . . . . . . . . . . La stringa vuota e il null . . . . . . . . . . . . . . . Inizializzazione Lazy . . . . . . . . . . . . . . . . . Lo scope . . . . . . . . . . . . . . . . . . . . . . . . Ciclo di vita . . . . . . . . . . . . . . . . . . . . . . Estensione del container . . . . . . . . . . . . . . . 1.9.1 BeanPostProcessor . . . . . . . . . . . . . . 1.9.2 BeanFactoryPostProcessor . . . . . . . . . . 1.9.3 BeanFactory . . . . . . . . . . . . . . . . . Ereditariet . . . . . . . . . . . . . . . . . . . . . . Usare i le .properties . . . . . . . . . . . . . . . . Uso delle annotation . . . . . . . . . . . . . . . . . 1.12.1 @Required . . . . . . . . . . . . . . . . . . . 1.12.2 @Resource . . . . . . . . . . . . . . . . . . . 1.12.3 @PostConstruct e @PreDestroy . . . . . . . Rilevazione automatica dei componenti . . . . . . . 1.13.1 Uso dei ltri per uno scanning customizzato Internazionalizzazione con MessageSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
4 5 6 8 8 9 9 10 10 11 11 12 13 13 14 15 15 15 16 16 17 18
2 Validazione
Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . L'interfaccia Validator . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Gestione di controlli pi complessi . . . . . . . . . . . 2.2.2 Gestione dei messaggi d'errore tramite messageSource Validazione di una rete di bean . . . . . . . . . . . . . . . . .
20
20 20 23 23 24
INDICE
ii
Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Panoramica sulle caratteristiche . . . . . . . . . . . . . . . . . . . . . . Valutazione delle espressioni usando l'interfaccia Expression di Spring 3.3.1 L'interfaccia EvaluationContext . . . . . . . . . . . . . . . . . . Supporto delle espressioni alla denizione dei bean . . . . . . . . . . . Reference del linguaggio . . . . . . . . . . . . . . . . . . . . . . . . . . Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Concetti dell'AOP . . . . . . . . . . . . . . . . . . . . . . 4.1.2 Capacit e obiettivi di Spring AOP . . . . . . . . . . . . . 4.1.3 AOP Proxies . . . . . . . . . . . . . . . . . . . . . . . . . AOP con @AspectJ . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Abilitare il supporto a @AspectJ . . . . . . . . . . . . . . 4.2.2 Dichiarare un aspetto . . . . . . . . . . . . . . . . . . . . 4.2.3 Dichiarare un pointcut . . . . . . . . . . . . . . . . . . . . 4.2.3.1 Indicatori supportati per i Pointcut . . . . . . . 4.2.3.2 Combinare le espressioni dei pointcut . . . . . . 4.2.3.3 Condividere le denizioni dei pointcut in comune 4.2.3.4 Alcuni esempi . . . . . . . . . . . . . . . . . . . 4.2.4 Dichiarare un advice . . . . . . . . . . . . . . . . . . . . . 4.2.4.1 Before . . . . . . . . . . . . . . . . . . . . . . . . 4.2.4.2 After returning . . . . . . . . . . . . . . . . . . . 4.2.4.3 After throwing . . . . . . . . . . . . . . . . . . . 4.2.4.4 After (nally) . . . . . . . . . . . . . . . . . . . 4.2.4.5 Around . . . . . . . . . . . . . . . . . . . . . . . 4.2.4.6 Gestione dei parametri . . . . . . . . . . . . . . 4.2.4.7 Priorit . . . . . . . . . . . . . . . . . . . . . . . 4.2.5 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.6 Modello di instanziazione di un aspetto . . . . . . . . . . 4.2.7 Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . . . AOP con XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Dichiarare un aspetto . . . . . . . . . . . . . . . . . . . . 4.3.2 Dichiarare un pointcut . . . . . . . . . . . . . . . . . . . . 4.3.3 Dichiarare un advice . . . . . . . . . . . . . . . . . . . . . 4.3.3.1 Before . . . . . . . . . . . . . . . . . . . . . . . . 4.3.3.2 After returning . . . . . . . . . . . . . . . . . . . 4.3.3.3 After throwing . . . . . . . . . . . . . . . . . . . 4.3.3.4 After (nally) . . . . . . . . . . . . . . . . . . . 4.3.3.5 Around . . . . . . . . . . . . . . . . . . . . . . . 4.3.3.6 Gestione dei parametri . . . . . . . . . . . . . . 4.3.4 Introduzioni . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.5 Modello di instanziazione di un aspetto . . . . . . . . . . 4.3.6 Advisors . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.7 Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . . . Scegliere lo strumento per gestire l'AOP . . . . . . . . . . . . . . 4.4.1 Spring AOP o AspectJ? . . . . . . . . . . . . . . . . . . . 4.4.2 @AspectJ o XML? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
28 28 29 31 31 32 33 33 34 35 35 35 36 36 37 38 39 40 42 42 43 43 44 44 45 46 46 47 47 49 50 50 51 51 51 52 52 52 52 53 53 53 54 55 55 56
4 Spring AOP
33
4.2
4.3
4.4
INDICE 4.5 4.6 Il meccanismo dei proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Capire i proxy AOP . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creazione programmatica dei proxy @AspectJ . . . . . . . . . . . . . . . . . .
iii 57 57 60
II
Data Access
5.1 Vantaggi del supporto di Spring alla gestione delle transazioni 5.1.1 Transazioni globali . . . . . . . . . . . . . . . . . . . . 5.1.2 Transazioni locali . . . . . . . . . . . . . . . . . . . . . 5.1.3 Modello di programmazione di Spring . . . . . . . . . Comprendere l'astrazione delle transazioni di Spring . . . . . Gestione dichiarativa delle transazioni . . . . . . . . . . . . . 5.3.1 Comprendere l'approccio dichiarativo . . . . . . . . . . 5.3.2 Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.3 Rollback . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.4 Congurazione dierenti per bean dierenti . . . . . . 5.3.5 <tx:advice/> . . . . . . . . . . . . . . . . . . . . . . . 5.3.6 @Transactional . . . . . . . . . . . . . . . . . . . . . . 5.3.6.1 Congurazione di @Transactional . . . . . . 5.3.7 Propagazione delle transazioni . . . . . . . . . . . . . . 5.3.7.1 Required . . . . . . . . . . . . . . . . . . . . 5.3.7.2 RequiresNew . . . . . . . . . . . . . . . . . . 5.3.7.3 Nested . . . . . . . . . . . . . . . . . . . . . 5.3.8 Advise per le transazioni . . . . . . . . . . . . . . . . . Gestione programmatica delle transazioni . . . . . . . . . . . 5.4.1 Usare il PlatformTransactionManager . . . . . . . . . Gestione dichiarativa Vs gestione programmatica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
62
62 62 62 63 63 65 66 66 69 69 71 71 73 73 73 74 74 74 77 77 78
5.2 5.3
5.4 5.5
Introduzione
Questo documento ha lo scopo di fornire una guida semplice e immediata, ma non esaustiva, all'utilizzo del framework Spring. Il motivo che mi ha spinto alla composizione di questo documento risiede nella scarsissima presenza di materiale in italiano sull'argomento. Ci tengo a precisare che questa guida non presenta tutti gli argomenti presenti sulle reference uciali, ma solo una selezione, eettuata sulla base dell'eettiva utilit che io ho rilevato su certe funzionalit. Tanto per citare uno dei grandi esclusi, la funzionalit dell'autowiring non sar trattata, poich la ritengo una funzionalit che se da una parte semplica un pochino il le di congurazione di Spring, dall'altra rischia di introdurre imprecisione nella struttura dell'applicazione che si realizza. Tale imprecisione, se non ben controllata, pu portare a un cattivo funzionamento. Tuttavia, saltare questo argomento non toglie nulla allo studio del framework, poich lo stesso tipo di funzionalit (ma senza imprecisioni) pu essere ottenuta con altri strumenti che invece saranno trattati. In ogni caso, per qualsiasi approfondimento si consiglia di utilizzare la documentazione uciale in lingua inglese, disponibile all'indirizzo: http://www.springsource.org/documentation La versione a cui questo documento si riferisce la 3.
0.1 Panoramica
Spring un framework opensource per lo sviluppo di applicazioni su piattaforma Java [Wikipedia]. Il suo scopo quello di rendere lo sviluppo di applicazioni enterprise pi semplice e pi veloce, abbattendo quindi i costi di produzione. La prima versione venne scritta da Rod Johnson, nel Giugno 2003 e sotto Licenza Apache. Il primo rilascio, la versione 1.0, risale al Marzo 2004. Spring nasce per fornire agli sviluppatori un'alternativa pi semplice agli EJB 2.0, lasciando al tempo stesso una maggiore libert. In questo stato ampiamente riconosciuto come un ottimo risultato.
0.2 Le innovazioni
Uno dei meriti di questo framework stato quello di rendere popolari alcune tecniche prima di esso poco note, come l'Inversion Of Control e il paradigma di programmazione orientata agli aspetti. Tipicamente un'applicazione Java composta da un certo numero di componenti, gli oggetti, che collaborano l'uno con l'altro per ottenere il comportamento desiderato. Tali oggetti sono pertanto legati da dipendenze. Il linguaggio Java ha sempre lasciato gli sviluppatori molto liberi di progettare l'architettura delle applicazioni con assoluta (forse troppa) libert. Per questo motivo nel corso del iv
INTRODUZIONE
tempo sono nati una vasta gamma di patterns (Factory, Builder, Abstract Factory, Decorator, Service Locator ) comunemente riconosciuti ed accettati dall'industria del software. Questo stato sicuramente un bene, ma i pattern sono solo questo: dei best pratices. Il modulo dell'IoC (Inversion of Control), come si vedr in seguito, formalizza questi concetti, fornendo una infrastruttura che guida lo sviluppatore nella costruzione dell'applicazione, fornendogli tutti gli strumenti necessari per poter applicare i suddetti patterns. La programmazione orientata agli aspetti un paradigma di programmazione basato sulla creazione di entit software, gli aspetti, che sovrintendono alle interazioni fra oggetti nalizzate ad eseguire un compito comune. Il vantaggio rispetto all'approccio tradizionale ad oggetti consiste nel non dover implementare separatamente in ciascun oggetto il codice necessario ad eseguire questo compito comune [Wikipedia]. Tipici esempi di responsabilit 'spalmate' su molteplici oggetti sono il log delle operazioni o la gestione della prolazione. Entrambi questi concetti saranno approfonditi pi avanti.
0.3 I moduli
In realt chiamarlo framework forse riduttivo, poich Spring composto da una vasta gamma di moduli, ognuno dei quali un framework a se stante. L'aspetto interessante che ognuno di questi moduli lavora in maniera indipendente dagli altri. Questo permette di integrare nella propria applicazione tutti e soli i moduli di interesse. Non siamo quindi costretti ad un approccio del tipo o tutto o niente. Tali moduli si possono raggruppare in base alle loro caratteristiche principali: Core Container, Data Access/Integration, Web, AOP, Instrumentation, Test. L'organizzazione di tali gruppi visibile chiaramente nella gura 1.
INTRODUZIONE Nei capitoli che seguiranno verrano descritti questi moduli uno per uno.
vi
Parte I
Core Technologies
Capitolo 1
Il contenitore IoC
E' probabilmente uno degli aspetti in cui Spring si mostrato veramente innovativo. Per usufruire di tale servizio necessario importare nel proprio progetto i due les jar org.springframework.beans e org.springframework.context. Molto spesso nelle nostre applicazioni, capita che un oggetto A debba usare il metodo di un oggetto B. Quando questo succede, tipicamente l'oggetto A deve prima di tutto creare un'istanza di B. Questa operazione possibile tramite l'uso dell'operatore new :
// classe B public class B { public void esegui() { System.out.println("Ciao Mondo!"); } } // classe A public class A { public static void main(String[] args) { B b = new B(); b.esegui(); } }
Con l'operatore new noi stiamo chiedendo l'istanziazione della classe B. Ovvero la classe A chiede alla JVM un'istanza della classe B, come rappresentato in gura 1.1.
new A JVM
Figura 1.1: Creazione di un bean L'IoC parte dall'idea di inframezzare tra la classe A e la JVM un terzo componente, che chiameremo IOC, che si propone come intermediario. Il passaggio, com' visibile in gura 1.2, sar quindi: A chiede a IOC di chiedere a JVM un'istanza della classe B. 2
Figura 1.2: Inversion of Control Da qui, il concetto di 'inversione del controllo': non siamo pi 'noi' a chiedere un'istanza, ma qualcun altro che la chiede (certo, sulla base comunque delle nostre istruzioni) e ce la restituisce. Ma perch introdurre un'indirezione? L'idea di introdurre un intermediario, in realt, non poi cosi strana. In eetti, nelle attuali applicazioni, molto spesso la creazione dei beans adata a classi (di solito statiche) preposte a questo compito, nel rispetto del pattern factory. Nell'esempio in gura 1.3 le classi del business che hanno bisogno di utilizzare classi dao, ne chiedono le istanze al modulo ServiceFactory. Il vantaggio quello di centralizzare il concetto di creazione (e soprattutto congurazione) dei beans all'interno di un'unica classe, anch il problema possa essere gestito pi agilmente. I vantaggi di questo approccio saranno meglio evidenziati pi avanti.
Business DAO
ServiceFactory
Figura 1.3: Esempio di implementazione del pattern factory Supponendo di adottare una soluzione di questo tipo, il nostro codice diventerebbe:
// Factory public abstract class BeansFactory { public static B getB() { return new B(); } } // classe A public class A {
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- Definizione dei beans --> <bean id="pippo" class="esempio.B" /> </beans>
Il tag usato per la denizione dei beans <bean>. Ogni beans andr denito dentro un proprio tag di questo tipo. Tra i vari attributi che possibile settare, due sono obbligatori: id, con il quale deniamo il nome che decidiamo di assegnare al bean in questione (in questo caso pippo ); class, con il quale specichiamo al container qual , all'interno del nostro progetto, la classe che dovr essere utilizzata per l'implementazione di pippo, completa di package in cui tale classe si trova. Vediamo a questo punto come cambia il codice all'interno della nostra classe A.
// classe A public class A { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"spring-config.xml"}); B b = (B) context.getBean("pippo"); b.esegui(); } }
ApplicationContext l'interfaccia che ci permette di comunicare con il container IoC e utilizzare i servizi che mette a disposizione. Per poter usufruire di tali servizi creiamo l'oggetto context (che quindi di tipo ApplicationContext ), passandogli il le di congurazione a cui deve fare riferimento, e in cui nello specico abbiamo denito il bean pippo. L'argomento della funzione ClassPathXmlApplicationContext() un array di stringhe poich possono essere passati pi le .xml.
A questo punto possiamo chiedere al container di restituirci un'istanza del bean che abbiamo chiamato 'pippo', tramite il metodo getBean(String nome). Tale metodo sovraccarico. In questa specica denizione, accetta come parametro una stringa (il nome del bean che vogliamo). Restituisce un object, e questo ci costringe ad un cast. Se vogliamo evitare il cast possiamo anche scrivere:
B b = context.getBean("pippo", B.class);
Avendo passato la classe B tra i parametri, il cast non necessario.
// classe B public class B { private String stringa; public void esegui() { System.out.println(this.stringa); } } // metodi set() e get()...
In questo modo, il metodo esegui() scrive sulla console la stringa con cui in quel momento stato settato l'oggetto. Il le di congurazione di Spring a questo punto potrebbe diventare questo:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- Definizione dei beans --> <bean id="pippo" class="esempio.B"> <property name="stringa" value="Ciao Mondo!" /> </bean>
</beans>
E' stato aggiunto un nuovo nodo, interno a <bean> che <property>. Con questo tag possibile elencare tutti gli attributi che si vogliono congurare contestualmente alla creazione del bean. Ogni attributo viene identicato con l'attributo name, valorizzato con il nome con cui stato chiamato all'interno della classe B. Il suo valore viene settato con l'attributo value. Questa denizione anche dotata di un meccanismo automatico di conversione dei valori. Se per esempio scrivessimo:
<!-- bean che rappresenta un punto nel piano --> <bean id="origine" class="esempio.Punto"> <property name="x" value="0.0" /> <property name="y" value="0.0" /> </bean>
il sistema, conoscendo il tipo di x e y, sa in automatico che i valori '0.0' sono numeri e come tali li convertir. Se x fosse di tipo String, tratterebbe '0.0' come una stringa. La classe A non subisce alcuna modica. Una chiamata del tipo:
B b = context.getBean("pippo", B.class);
restituirebbe un'istanza di B gi congurata cosi come specicato nel le spring-cong.xml. E' facile capire come questo meccanismo renderebbe pi semplice, per fare un esempio, l'uso di un DAO. Il le spring-cong.xml potrebbe contenere un frammento del tipo.
<!-- dao --> <bean id="dao" class="esempio.ClasseDAO"> <property name="url" value="jdbc:mysql://localhost:3306/mydb" /> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="username" value="admin" /> <property name="password" value="admin" /> </bean>
In questo modo, chiedere al container un'istanza di 'dao' restituirebbe una dao gi congurato e pronto all'uso. Ovviamente possibile descrivere pi congurazioni diverse per lo stesso bean. Potrei per esempio creare pi di una congurazione diversa per ClasseDAO, chiamandone una o l'altra in funzione dello specico DB da utilizzare. Non possibile, per, avere due bean con lo stesso id. Anch questo meccanismo possa funzionare, necessario che siano stati creati i metodi set() di tutti gli attributi che Spring dovr settare. Il fatto che sia il container a settare direttamente gli attributi, prende il nome di Direct Injection (iniezione diretta, DI).
Uno degli aspetti pi utili del modulo IoC di Spring che il meccanismo di DI funziona anche nel caso in cui l'attributo da settare un riferimento ad un altro oggetto! Vediamo un esempio... Supponiamo di avere la seguente situazone:
// classe B public class B { private String stringa; public void esegui() { System.out.println(this.stringa); }
// classe Main public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( new String[] {"spring-config.xml"}); A a = context.getBean("pippo", A.class); a.esegui(); } }
In questo esempio, l'oggetto A, per eseguire il metodo esegui() ha bisogno di utilizzare l'oggetto B. Il le di congurazione di Spring diventa:
<!-- bean A --> <bean id="pippo" class="esempio.A"> <property name="b" ref="pluto" /> </bean> <!-- bean B --> <bean id="pluto" class="esempio.B" > <property name="stringa" value="Ciao Mondo!" /> </bean>
Qui vediamo un altro uso del tag <property>. Poich la variabile b un riferimento ad un altro oggetto, al posto dell'attributo value si usa l'attributo ref. Tale attributo permette di
dire al container che quella variabile va fatta riferire ad un altro bean. Il valore dell'attributo ref l'id che viene denito per un bean. Nel nostro caso specico, quando verr fatta la chiamata
A a = context.getBean("pippo", A.class);
verr creata un'istanza di 'pluto' e poi 'iniettata' nella variabile b del bean 'pippo'. E' facile immaginare come con questo meccanismo sia possibile tirare su con una semplice istruzione una rete di oggetti complicata quanto si vuole. Il programmatore deve solo opportunamente congurarla all'interno del le spring-cong.xml.
1.4 Le collezioni
Pu capitare, naturalmente, che un attributo di un oggetto sia una collezione (list, map, set o properties). In tal caso la sintassi nel spring-cong.xml pu essere la seguente:
<bean id="oggettoComplicato" class="progetto.ComplexObject"> <!-- Per le liste --> <property name="lista"> <list> <!-- Inserisco l'elemento per valore --> <value>elemento qualunque</value> <!-- Oppure lo inserisco per riferimento --> <ref bean="mioDataSource" /> </list> </property> <!-- Mappa --> <property name="mappa"> <map> <!-- Elemento inserito per valore --> <entry key="elemento" value="una stringa qualunque"/> <!-- Elemento inserito per riferimento --> <entry key ="riferimento" value-ref="mioDataSource"/> </map> </property> <!-- Set --> <property name="set"> <set> <value>una stringa qualunque</value> <ref bean="mioDataSource" /> </set> </property> <bean>
1.4.1 I generics
Dalla Java 5 in poi, possibile utilizzare le collezioni tipizzate. Utilizzando il meccanismo di DI per settare una collezione all'interno di un bean, possiamo trarre vantaggio dal meccanismo di conversione di tipi di Spring. Ad esempio:
public class Pippo { private Map<String, Float> conti; public void setConti(Map<String, Float> conti) { this.conti = conti; } } <beans> <bean id="pippo" class="x.y.Pippo"> <property name="conti"> <map> <entry key="uno" value="9.99"/> <entry key="due" value="2.75"/> <entry key="sei" value="3.99"/> </map> </property> </bean> </beans>
Le stringhe '9.99', '2.75' e '3.99' verranno automaticamente convertite in Float.
setEmail("");
Se si vuole invece considerare il valore null, si deve scrivere:
10
1.7 Lo scope
Lo scope di un bean rappresenta l'arco di tempo in cui il bean deve essere utilizzabile e al di fuori del quale non lo deve essere. Spring supporta cinque diversi scope, tre dei quali sono disponibili solo in contesti Web. La tabella 1.1 riassume gli scope disponibili:
scope
singleton prototype request session globalSession
descrizione
il bean l'unico del suo tipo all'interno del container esiste un bean per ogni istanza creata il bean esiste solo nell'ambito di una singola richiesta HTTP il bean esiste nell'ambito di un'intera HTTP Session il bean esiste nell'ambito di una HTTP Session globale Tabella 1.1: scope dei bean
Per la denizione dello scope di un bean, si utilizza l'attributo scope del tag <bean>:
public class Pippo { // ... public void init() { // Inizializzazione dell'oggetto... } } <bean id="pippo" class="esempi.Pippo" init-method="init" />
Quando al container viene chiesta un'istanza di pippo, subito dopo la sua creazione verr invocato il metodo init(). Lo stesso discorso vale anche per la fase di distruzione di un bean. In questo caso l'attributo da usare destroy-method. Ad esempio:
11
public class Pippo { // ... public void cleanup() { // Distruzione dell'oggetto... } } <bean id="pippo" class="esempi.Pippo" destroy-method="cleanup" />
In questo modo, prima della distruzione del bean pippo verranno eseguite tutte le operazioni all'interno del metodo cleanup() (ad esempio la chiusura delle connessioni di un dao).
1.9.1 BeanPostProcessor
L'IoC permette di poter estendere alcune funzionalit sul controllo del ciclo di vita dei bean, facendo uso di apposite interfacce. Una di queste la BeanPostProcessor. Tale interfaccia mostrata di seguito:
public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String name) throws BeansException; Objcet postProcessAfterInitialization(Object bean, String name) throws BeansException; }
E' suciente scrivere una classe che implementi questa interfaccia e dichiararla come bean all'interno del le di congurazione di spring. A questo punto, prima della fase di inizializzazione di ogni bean verr richiamato il metodo postProcessBeforeInitialization(...) e dopo l'inizializzazione il metodo postProcessAfterInitialization(...). L'esempio molto semplice che segue mostra un BeanPostProcessor che chiama il metodo toString() ogni volta che viene creato un nuovo bean.
public class MioBeanPostProcessor implements BeanPostProcessor { // ritorno il bean appena istanziato cos com' public Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException { return bean; } public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException { System.out.println( "Bean '" + beanName + "' creato : " + bean.toString() ); return bean;
12
}
A questo punto suciente dichiararlo all'interno del spring-cong.xml:
<bean class="esempi.MioBeanPostProcessor"/>
Da notare che questo bean non ha l'attributo id. Immaginando una situazione del tipo
// classe Punto public class Punto { private int x; private int y; @Override public String toString() { return "x="+this.getX()+" y="+this.getY(); } ...
// configurazione per Spring <bean id="punto" class="esempi.Punto"> <property name="x" value="1" /> <property name="y" value="3" /> </bean>
il metodo main seguente
public static void main(final String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext( new String[] {"spring-config.xml"}); Punto p = context.getBean("punto"); }
produrrebbe un output del tipo
1.9.2 BeanFactoryPostProcessor
Esiste anche il concetto di BeanFactoryPostProcessor che attraverso il metodo postProcessBeanFactory(...) permette di aggiungere del comportamento dopo che il container ha caricato le denizioni dei beans, ma prima che gli stessi siano istanziati. Non va quindi a inserirsi nel ciclo di vita dei beans, ma in quello dello stesso container. L'interfaccia la seguente:
public interface BeanFactoryPostProcessor { public void postProcessBeanFactory( ConfigurableListableBeanFactory beanFactory) throws BeansException; }
13
Anche in questo caso suciente creare un bean che implementi questa interfaccia e denirne la congurazione all'interno dello spring-cong.xml.
1.9.3 BeanFactory
E' possibile implementare l'interfaccia org.springframework.beans.factory.FactoryBean per tutti quegli oggetti che sono essi stessi delle factory. Tale interfaccia da considerare come un'occasione per aggiungere funzionalit logiche di istanziazione al container IoC. Qualora si abbia del codice molto complesso per l'inizializzazione che rimane meglio esprimere direttamente in java invece che con moltissimo codice XML, possibile creare una propria FactoryBean, scrivere l'inizializzazione all'interno di questa classe e inne integrare tale classe all'interno del container. Questa interfaccia provvede ai seguenti tre metodi: Object getObject(): ritorno un'istanza dell'oggetto che questa factory crea; boolean isSingleton(): ritorna true se questa FactoryBean ritorna un singleton, false altrimenti; Class getObjectType(): ritorna il tipo ritornato da getObject() o null se il tipo non noto a priori;
1.10 Ereditariet
La denizione di un bean pu comprendere la congurazione di molte informazioni. La denizione di un bean glio eredita tutte queste informazioni. Inoltre pu anche sovrascrivere alcuni valori o aggiungerne di nuovi, in base alle necessit. Questo tipo di congurazione possibile tramite l'attributo parent del tag <bean>, come mostrato nell'esempio seguente:
<bean id="inheritedTestBean" abstract="true" class="esempi.TestBean"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithDifferentClass" class="esempi.DerivedTestBean" parent="inheritedTestBean" init-method="initialize"> <property name="name" value="override"/> <!-- Il valore 1 sulla propriet age viene ereditato dal padre --> <!-- l'attributo name invece viene sovrascritto --> </bean>
La denizione del bean glio usa la classe dalla denizione del padre, se non ne stata specicata nessuna, ma pu anche sovrascriverla. In quest'ultimo caso, la classe del glio dev'essere compatibile con quella del padre e deve accettare i valori delle propriet del genitore. La denizione di un bean glio eredita i valori degli argomenti del costruttore, i valori delle propriet e i metodi dal padre, con in pi la possibilit di aggiungere nuovi valori. Ogni metodo di inizializzazione, di distruzione e/o setting dei metodi factory statici che si specica eettuer un override dei corrispondenti deniti nel genitore. Ci che rimane viene sempre
14
preso dalla denizione del glio: depends on, autowire mode, dependency check, singleton, scope e lazy-init. L'esempio precedente marca esplicitamente la denizione del bean genitore come astratto, usando l'attributo abstract. Se la denizione del padre non specica nessuna classe, tale denizione dev'essere marcata necessariamente come astratta, come segue:
<bean id="inheritedTestBeanWithoutClass" abstract="true"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithClass" class="esempi.DerivedTestBean" parent="inheritedTestBeanWithoutClass" init-method="initialize"> <property name="name" value="override"/> <!-- Il valore 1 sulla propriet age viene ereditato dal padre --> <!-- l'attributo name invece viene sovrascritto --> </bean>
Il bean genitore non pu essere istanziato da solo, perch incompleto, essendo stato esplicitamente marcato come astratto. Quando una denizione astratta come questa, viene utilizzata esclusivamente come template che serve come genitore per la denizione di altri bean. Se si prova a richiedere al container un'istanza di tale bean, con il metodo getBean(), viene ritornato un errore. Allo stesso modo, il metodo preInstantiateSingletons() del container ignora le denizioni dei bean settati come astratti.
chiave = valore
Quello che segue ne un esempio:
15
<!-- Definizione file properties --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:/proprieta.properties"/> </bean> <!-- oppure con la seguente espressione --> <context:property-placeholder location="classpath:/proprieta.properties"/> <!-- Definizione dei beans --> <bean id="dao" class="esempiospring.MioDao"> <property name="url" value="${url}" /> <property name="driver" value="${driver}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean> <bean id="punto" class="esempiospring.Punto"> <property name="x" value="${x}" /> <property name="y" value="${y}" /> </bean>
1.12.1 @Required
Si applica ai metodi setter, come nel seguente esempio:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ...
Questa annotation semplicemente indica che l'attributo associato al metodo set() deve essere necessariamente popolato a tempo di congurazione, con un valore denito esplicitamente. In caso contrario, il container solleva un'eccezione.
1.12.2 @Resource
@Resource prende il nome di un attributo e di dafault Spring interpreta questo valore come il nome di un bean da iniettare.
16
private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; }
Se non viene specicato nessun nome, questo viene derivato dal nome dell'attributo o del metodo set() associato in base a dov' stato applicato. Nell'esempio che segue verr ricercato il bean chiamato 'movieFinder'.
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; }
public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... }
In questo modo non c' bisogno di usare gli attributi init-method e destroy-method visti nel paragrafo 1.8.
17
Tale funzionalit pu risultare utile in tutti quei casi in cui si vuole che il container distingua i bean in funzione del ruolo che svolgono all'interno del container, per esempio per una gestione diversicata dei log tra i vari strati dell'applicazione, oppure per gestire meglio il problema dell'autowiring. Per fare in modo che Spring esegua una scansione del classpath allo scopo di catalogare i bean che abbiamo marchiato, l'istruzione da inserire nel le di congurazione la seguente:
<context:component-scan base-package="org.example"/>
dove l'attributo base-package specica il package da cui iniziare la ricerca. Questa scansione infatti non deve coinvolgere necessariamente tutta l'applicazione, ma anche solo una parte.
Descrizione
Un annotazione dev'essere presente al livello di tipo nel componente target Un classe (o interfaccia) che il componente target pu estendere/implementare Un espressione di tipo AspectJ che deve matchare con il componente target Un'espressione regolare che deve matchare con il nome della classe del componente target
aspectj regex
org.example..*Service+ org/.example/.Default.*
custom
org.example.MyCustomTypeFilter Un'implementazione customizzata dell'interfaccia org.springframework.core.type.TypeFilter Tabella 1.2: Opzioni dei ltri
L'esempio che segue mostra la congurazione XML ignorando tutti i componenti @Repository tranne quelli che rispettano l'espressione regolare .*Stub.*Repository, ovvero il cui nome termina con Repository e il nome del package in cui sono contenuti termina con Stub.
18
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <!-- Qui posso definire pi file di properties. Per ora --> <!-- inserisco l'unico che ho che si chiama 'messaggi'. --> <value>messaggi</value> </list> </property> </bean> </beans>
Main.java
19
public class Main { public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("spring-config.xml"); String message = resources.getMessage("benvenuto", null, "Default", null); System.out.println(message); } }
Eseguendo la classe Main, l'output sar:
Ciao Mondo!!!
Se la voce benvenuto non fosse stata trovata all'interno del messaggi.properties, l'output sarebbe stato
Default
Questo meccanismo permette di eliminare qualunque stringa esplicitamente dichiarata all'interno del codice, sistemandole tutte in les appositi. Tale utilit risulta evidente proprio in fase di localizzazione. Supponendo per esempio di voler tradurre la nostra applicazione in inglese (en-GB), dovremo creare un le chiamato messaggi_en_GB.properties. Tipicamente la localizzazione gestita dall'ambiente su cui gira l'applicazione. Supponendo per esempio di avere i seguenti les: messaggi.properties
public class Main { public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("spring-config.xml"); // carico il file predefinito String message = resources.getMessage("benvenuto", null, "Default", null); System.out.println(message); // carico il file per la lingua inglese message = resources.getMessage("benvenuto", null, "Default", Locale.UK); System.out.println(message); } }
L'output in questo caso sar:
Capitolo 2
Validazione
2.1 Introduzione
La validazione, ovvero il controllo della validit dei valori con cui viene settato un bean o in generale vengono settati dei parametri, un aspetto molto importante in ogni applicazione. Questa problematica diventa pi critica quando i valori immessi provengono direttamente dall'utente. E' necessario quindi avere un meccanismo che consenta di controllare questo tipo di operazioni. Ci sono pro e contro nel considerare la validazione come parte della logica di business e Spring ore una soluzione per la validazione che non esclude nessuna delle due cose. In particolare la validazione non dovrebbe essere legata al layer web; dovrebbe essere semplice gestirne la localizzazione; dovrebbe essere possibile aggiungere qualunque validatore disponibile; Per assolvere a queste funzionalit, Spring propone l'interfaccia Validator.
CAPITOLO 2. VALIDAZIONE
21
class Indirizzo { // attributi private String private String private String private String private String private String private String
prefissoVia; // "via", "viale", "piazza", ecc. nomeVia; numeroCivico; cap; citta; provincia; nazione;
Per validare questo bean, abbiamo bisogno di un validatore ad hoc che, come descritto poco fa, deve implementare l'interfaccia Validator :
public class IndirizzoValidator implements Validator { // Con questo metodo cosi definito, indichiamo che // questa classe valida SOLO gli oggetti Indirizzo public boolean supports(Class clazz) { return Indirizzo.class.equals(clazz); } // Metodo che realizza la validazione public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "prefissoVia", "required", "prefissoVia obbligatorio"); ValidationUtils.rejectIfEmpty(e, "nazione", "required", "nazione obbligatorio"); ValidationUtils.rejectIfEmpty(e, "cap", "required", "cap obbligatorio");
All'interno del metodo validate(...) vengono utilizzati i metodi statici della classe ValidationUtils. Il metodo rejectIfEmpty(...), come dice il nome stesso, riuta (o, in altre parole, invalida la verica) il valore assegnato all'attributo pressoVia di obj nel caso in cui tale attributo sia lasciato vuoto (ovvero con valore null). Il primo parametro un riferimento all'oggetto di tipo Errors che raccoglier tutte le informazioni circa gli errori rilevati; il secondo parametro si riferisce all'attributo di obj su cui viene eettuata la verica; il terzo parametro un codice che rappresenta il tipo di errore. Tale codice arbitrariamente scelto dallo sviluppatore (in questo caso stato denito per semplicit all'interno della funzione, ma
CAPITOLO 2. VALIDAZIONE
22
sarebbe pi corretto denirlo in un apposito le di costanti). L'ultimo parametro, che non obbligatorio, il messaggio d'errore di default. Secondo la logica utilizzata nell'esempio, un indirizzo non considerato valido se il presso della via, la nazione e il cap non sono stati deniti. Per testare il nostro validatore, suciente eseguire il seguente metodo main:
public static void main(String[] args) { // creo un indirizzo Indirizzo i = new Indirizzo(); i.setNomeVia("F. Ozanam"); i.setNumeroCivico("44"); i.setCap("123456789"); // creo il validatore IndirizzoValidator iv = new IndirizzoValidator(); Errors errInd = new BindException(i, i.getClass().getName()); // effettuo e gestisco la validazione iv.validate(i, errInd); // controllo se ci sono stati errori if(errInd.hasFieldErrors()) { // mi faccio restituire una lista di errori rilevati sui campi List<FieldError> listaCampi = errInd.getFieldErrors(); for (FieldError fe : listaCampi) { System.out.println(fe.getDefaultMessage()); } }
Vediamo di commentare il codice appena presentato. Tanto per cominciare viene creato un Indirizzo d'esempio, lasciando a null due dei tre campi richiesti come obbligatori. Viene poi creato il validatore. Abbiamo poi bisogno di un oggetto di tipo Errors. Naturalmente, trattandosi di un'interfaccia, niente ci impedisce di crearne uno nostro, ma Spring ne mette a disposizione alcuni gi pronti. Nel nostro caso abbiamo usato un BindException. Non c' un motivo particolare per aver scelto questo, se non che implementa Errors per un uso abbastanza standard. Da notare che questa classe estende Exception. Il costruttore di questo oggetto prende come parametri l'oggetto su cui si fa la validazione e il nome di tale oggetto. A questo punto possiamo chiedere al validatore di eettuare le veriche. Terminate queste operazioni, siamo ovviamente interessati a conoscere l'esito della validazione. Interroghiamo quindi l'oggetto Errors. Con il metodo hasFieldErrors() verichiamo (ci restituisce un boolean) se sono stati rilevati errori sui controlli dei campi. In caso aermativo, con il metodo getFieldErrors() ci facciamo restituire una lista di FieldError. Ognuno di questi oggetti contiene tutti i dettagli sugli errori rilevati sui campi. Nel nostro caso siamo interessati al messaggio di default per farcelo stampare sulla console. L'output di questo metodo produrr il seguente risultato:
CAPITOLO 2. VALIDAZIONE
23
public void validate(Object obj, Errors e) { ... Indirizzo i = (Indirizzo) obj; if (!Pattern.matches("[0-9][0-9][0-9][0-9][0-9]", i.getCap())) { e.reject("cap", "notValid", "Cap non valido"); } }
Il commento a questo codice molto semplice. Viene castato obj a Indirizzo, perch ci serve accedere direttamente ad uno dei suoi attributi. Se il valore del cap non matcha con l'espressione regolare espressa dal primo argomento della funzione matches(...), la validazione sul campo cap viene ritenuta fallita e il valore viene riutato. e registra l'errore, gli assegna il codice notValid e il messaggio di default Cap non valido.
required = L'attributo {0} obbligatorio. notValid = L'attributo {0} ha un valore non valido.
Il valore 0 all'interno delle parentesi grae serve nel caso in cui si voglia passare al gestore del messageSource un parametro (stringa) che sar visualizzato al posto di {0}. Se la stringa nel le di properties contiene pi parametri, ogni parametro va indicato con un numero progressivo: {0}, {1}, {2}, ecc. Nel metodo main, quando andiamo a scorrere tutti gli errori, il ciclo for pu essere riscritto nel modo seguente:
for (FieldError fe : listaCampi) { String[] param = {fe.getField()}; System.out.println(resources.getMessage(fe.getCode(), param, fe.getDefaultMessage(), null)); }
CAPITOLO 2. VALIDAZIONE
24
La gestione dei parametri del le di properties adata al secondo parametro della funzione getMessage(...). Questo infatti, se non null, deve necessariamente essere un array di String. Nel nostro caso viene passato l'array {pressoVia} nella prima iterazione, {nazione} nella seconda e {cap} nella terza. Il risultato sar il seguente:
L'attributo prefissoVia obbligatorio. L'attributo nazione obbligatorio. L'attributo cap ha un valore non valido.
Ancora una volta, siamo riusciti a centralizzare la gestione dei messaggi all'interno di le di properties appositamente dedicati.
public class Persona { private String nome; private String cognome; private Integer eta; private Indirizzo indirizzo; } // metodi set e get...
In questa particolare situazione ci troviamo di fronte ad un bean che ha tra i suoi attributi un riferimento ad un altro bean, di cui per altro abbiamo gi un validatore funzionante. E' ovviamente di nostro particolare interesse riciclare IndirizzoValidator e questo possibile farlo all'interno del validatore del bean Persona. Vediamo il codice di PersonaValidator :
public class PersonaValidator implements Validator { public boolean supports(Class clazz) { return Persona.class.equals(clazz); } public void validate(Object obj, Errors e) { // controllo che i campi nome e cognome siano compilati ValidationUtils.rejectIfEmpty(e, "nome", "required"); ValidationUtils.rejectIfEmpty(e, "cognome", "required"); Persona p = (Persona) obj; // effettuo un controllo sull'et, nel caso sia diversa da null if (p.getEta() != null) {
CAPITOLO 2. VALIDAZIONE
25
if (p.getEta() < 0) { e.rejectValue("eta", "valoreNegativo"); } else if (p.getEta() > 110) { e.rejectValue("eta", "troppoVecchio"); }
Questo validatore ci permette di fare due osservazioni. Intanto possibile vedere come viene gestito il caso in cui un valore debba essere riutato per situazioni particolari all'infuori del solito null, come invece avviene con gli attributi nome e cognome. Anzi in questo caso il valore null ammesso, ma non sono ammessi valori all'infuori del range [0..110]. Le ultime tre istruzioni sono un esempio di come sia possibile validare un bean interno ad un altro bean. Immaginiamo la gerarchia tra gli oggetti con uno schema come in gura 2.1:
Persona
+nome: String +cognome: String +eta: Integer +indirizzo: Indirizzo
Indirizzo
attributi omessi...
Figura 2.1: Persona -> Indirizzo La funzione pushNestedPath(String nomeAttributo) dell'oggetto Errors consente di avanzare di uno step nella gerarchia, in base al nome dell'attributo che gli viene passato. Dal momento dell'invocazione di questa funzione, l'oggetto e comincia ad occuparsi di Indirizzo e non pi di Persona. A questo punto, con l'istruzione invokeValidator(...) di ValidationUtils posso attivare la validazione sull'indirizzo. Vengono passati come parametri un'istanza di IndirizzoValidator, l'istanza da validare e l'oggetto Errors che dovr gestire le eventuali situazioni d'errore. Con la funzione popNestedPath(), ritorniamo indietro nella gerarchia, tornando sull'oggetto Persona. Per far comprendere ancora meglio il meccanismo, supponiamo di avere la gerarchia di oggetti in gura 2.2. Se volessimo validare A completamente, AValidator potrebbe avere il codice seguente:
CAPITOLO 2. VALIDAZIONE
A
+b: B +c: C
26
B
+d: D
D
+pippo: String
public void validate(Object obj, Errors e) { // validazione dei campi semplici di A ... A a = (A) obj; // validazione di b e.pushNestedPath("b"); ValidationUtils.invokeValidator(new BValidator(), a.getB(), e); e.popNestedPath(); // validazione di c e.pushNestedPath("c"); ValidationUtils.invokeValidator(new CValidator(), a.getC(), e); e.popNestedPath();
Questo codice parte dal presupposto che chi ha scritto BValidator si sia occupato di rilanciare al suo interno il validatore su D, che in eetti lo scenario pi auspicabile. Qualora cos non fosse, il codice appena visto andrebbe riscritto nel seguente modo:
public void validate(Object obj, Errors e) { // validazione dei campi semplici di A ... A a = (A) obj; // validazione di b e.pushNestedPath("b"); ValidationUtils.invokeValidator(new BValidator(), a.getB(), e); // validazione di d e.pushNestedPath("d"); ValidationUtils.invokeValidator(new DValidator(), a.getB().getD(), e); e.popNestedPath(); // "ritorno" su b e.popNestedPath();
CAPITOLO 2. VALIDAZIONE
27
Capitolo 3
29
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Ciao Mondo'"); String message = (String) exp.getValue(); System.out.println("Visualizzazione stringa: "+message);
L'output prodotto :
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Ciao Mondo'.concat('!')"); String message = (String) exp.getValue(); System.out.println("Esempio concatenazione: "+message);
L'output prodotto :
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Ciao Mondo'.bytes"); byte[] bytes = (byte[]) exp.getValue(); System.out.print("Accesso al campo bytes: "); for (int i = 0; i<bytes.length; i++) System.out.print(bytes[i]+" "); System.out.println();
L'output prodotto :
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Ciao Mondo'.bytes.length"); int length = (Integer) exp.getValue(); System.out.println("Lunghezza del campo bytes: "+length);
30
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('Ciao Mondo').toUpperCase()"); String message = exp.getValue(String.class); System.out.println("Creata la stringa: "+message);
L'output prodotto :
Persona p = new Persona(); p.setNome("Simone"); p.setCognome("Traversi"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("nome"); EvaluationContext context = new StandardEvaluationContext(); context.setRootObject(p); String name = (String) exp.getValue(context); System.out.println("Il nome del bean p : "+name);
L'output :
Persona p = new Persona(); p.setNome("Simone"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("nome == 'Simone'");
31
EvaluationContext context = new StandardEvaluationContext(); context.setRootObject(p); boolean risultato = exp.getValue(context, Boolean.class); System.out.println("Valutazione dell'espressione booleana: "+risultato);
L'output :
Congurazione basata su XML Il valore di un attributo o dell'argomento di un contruttore pu essere denito come di seguito:
<bean id="numero" class="esempi.Numero"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- altre propriet --> </bean>
La variabile systemProperties predenita, quindi possibile usarla nelle espressioni come mostrato in seguito. Nota che non si deve far precedere la variabile predenita dal simbolo # in questo costesto.
<bean id="taxCalculator" class="esempi.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <!-- altre propriet --> </bean>
E' possibile riferirsi ad un attributo di un altro bean, per esempio:
<bean id="numero" class="esempi.Numero"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- altre propriet --> </bean> <bean id="shapeGuess" class="esempi.ShapeGuess"> <property name="initialShapeSeed" value="#{ numero.randomNumber }"/> <!-- altre propriet --> </bean>
32
Congurazione basata sulle annotazioni L'annotazione @Value pu essere posizionata sui campi, sui metodi e sui parametri dei metodi/costruttori, per specicare un valore di default. Ad esempio:
public static class FieldValueTestBean { @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; }
Quello che segue un uso equivalente, in cui l'annotazione viene applicata al metodo setter dello stesso attributo.
public static class PropertyValueTestBean { private String defaultLocale; @Value("#{ systemProperties['user.region'] }") public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; }
Capitolo 4
Spring AOP
4.1 Introduzione
La programmazione orientata agli aspetti (AOP) si pu pensare complementare alla programmazione orientata agli oggetti (OOP), fornendo un'altro modo di pensare la struttura di un'applicazione. Se l'elemento su cui si basa la OOP la classe, l'elemento su cui si basa la AOP l'aspetto. Gli aspetti consentono di gestire responsabilit che tagliano trasversalmente l'applicazione, coinvolgendo pi oggetti tra loro. Tra i vari esempi possiamo citare il logging, la gestione dei privilegi di un utente, la gestione delle transazioni. Uno dei componenti chiave di Spring il framework AOP. Dal momento che il container IoC non dipende da quello AOP, non necessario usare quest'ultimo se non si vuole farlo. AOP usato nel framework Spring per: provvedere alla dichiarazione di servizi enterprise, specialmente in sostituzione degli EJB. Il pi importante quello che riguarda la gestione delle transazioni. consentire agli sviluppatori di implementare aspetti propri.
34
(per esempio l'esecuzione di un metodo con un determinato nome). Il concetto di join points come matchati da un'espressione di un pointcut un concetto centrale nell'AOP, e Spring usa di default le espressioni dei pointcut del linguaggio AspectJ. Introduction : possibilit di dichiarare metodi o attributi aggiuntivi all'interno di un oggetto, alterandone la struttura. Spring AOP permette di introdurre nuove interfacce (e la loro corrispondente implementazione) ad ogni oggetto soggetto ad advice. Nella comunit di AspectJ, questa funzionalit chiamata inter-type declaration. Target Object : Oggetto che soggetto ad advice da uno o pi aspetti. E' chiamato anche adviced object. Dal momento che Spring AOP stato implementato usando proxies runtime, questi oggetti saranno sempre proxied object. AOP Proxy : un oggetto creato dal framework AOP per implementare i contratti di un aspetto (metodi, ecc.). Nel framework Spring, un proxy AOP sar un proxy dinamico JDK o un proxy CGLIB. Weaving : legare gli aspetti con gli altri tipi od oggetti dell'applicazione per creare advised object. Questo pu essere fatto a tempo di compilazione (usando il compilatore AspectJ, per esempio), a tempo di caricamento o a runtime. Spring AOP, come anche il framework Java AOP, eettuano questo legame a runtime. Tipi di advice: before : advice che viene eseguito prima di un join point, ma che non pu alterare il usso di esecuzione che precede il join point (a meno che non sia sollevata un'eccezione). after returning : advice che viene eseguito dopo che un join point termini normalmente: per esempio, se un metodo ritorna senza sollevare eccezioni. After throwing : advice che viene eseguito se un metodo termina sollevando un'eccezione. After (nally) : advice che viene eseguito indipendentemente da come un join point termina. Around : advice che viene eseguito quando viene intercettata la chiamata ad un metodo dell'oggetto target. Questo il pi potente tipo di advice. Si raccomanda sempre l'utilizzo del tipo di advice meno potente che implementa il comportamento richiesto. Per esempio, se si ha la necessit solo di eseguire un update della cache con il valore di ritorno di un metodo, meglio implementare un after returning advice che un around advice, anche se un around advice pu fare la stessa cosa. Usando gli advice pi specici si rende il modello di programmazione pi semplice e meno soggetto ad errori.
35
L'approccio di Spring AOP alla programmazione orientata agli aspetti diversa rispetto agli altri framework AOP. L'obiettivo non fornire un'implementazione completa dell'AOP; piuttosto si tratta di provvedere ad una profonda integrazione tra l'implementazione di AOP e Spring IoC per fornire l'aiuto necessario a risolvere i problemi tipici delle applicazioni enterprise. Infatti, per esempio, le funzionalit di Spring AOP sono tipicamente usate insieme con quelle del container IoC. Gli aspetti sono congurati utilizzando la normale sintassi per la denizione dei bean: questa una dierenza fondamentale con le altre implementazioni AOP. Ci sono alcune cose che non si possono fare facilmente o ecientemente con Spring AOP, come oggetti advise molto dettagliati: AspectJ la migliore scelta in questi casi. Comunque, Spring AOP consente di utilizzare soluzioni eccellenti per molti problemi nelle applicazioni Java EE. Spring AOP non potr mai competere con AspectJ per una completa implementazione dell'AOP. Sia i framerwok basati su proxy, come Spring AOP, sia quelli basati su compilazione, come AspectJ, sono ugualmente utili e vanno considerati complementari, piuttosto che in competizione.
@AspectJ si riferisce ad uno stile per la dichiarazione degli aspetti utilizzando le annotazioni sulle normali classi Java. Lo stile @AspectJ stato introdotto dal progetto AspectJ come
<aop:aspectj-autoproxy/>
Questo parte dal presupposto che si stiano utilizzando i supporti per la congurazione basata su XML, descritti in maniera pi approfondita nell'appendice ??. Alternativamente possibile utilizzare la seguente denizione:
36
// interfaccia public interface IDao { public void salva(); public void recupera(); } // classe dao public class Dao implements IDao {
37
public void salva() { System.out.println("Salvo un dato sul db"); } public void recupera() { System.out.println("Ho recuperato un dato dal db"); }
Supponiamo di essere interessati all'intercettazione dell'esecuzione di un qualunque metodo di questa classe, perch vogliamo loggarli, prima e dopo la loro esecuzione. All'interno dell'aspetto denito poco sopra, deniamo quindi un pointcut sulla base dell'esigenza appena espressa. In questo esempio, sar inoltre chiarita la dierenza tra l'espressione del pointcut e la sua rma.
@Aspect public class LoggerWithAnnotation { // definizione dei pointcut @Pointcut("execution(* esempiospring.Dao.*(..))") // espressione public void inDao() {} // firma }
Abbiamo denito un pointcut chiamato inDao(). Notare che la rma identica a quella di un qualunque metodo vuoto e che il tipo di ritorno void. L'annotazione @Pointcut contiene tra parentesi una stringa espressione che descrive i join points di interesse. L'espressione qui denita si aggancia a tutte le esecuzioni di tutti i metodi della classe esempiospring.Dao. Se avessimo voluto agganciarlo solo al metodo salva() avremmo scritto l'espressione in questo modo:
"execution(* esempiospring.Dao.salva())"
Il pointcut descritto nell'esempio stato denito usando le espressioni regolari di AspectJ 5. Per un'approfondimento sul linguaggio di denizione dei pointcut in AspectJ fare riferimento al testo AspectJ Programming Guide [Tea03] (e per l'estensione a Java 5, AspectJ 5 Developers Notebook) o ad uno dei libri su AspectJ come Eclipse AspectJ di Colyer et. al. oppure AspectJ in Action di Ramnivas Laddad.
38
args : indica l'insieme dei join point nei quali gli argomenti sono istanze di un certo tipo specicato. @target : indica l'insieme dei join point nei quali la classe che sta eseguendo operazioni ha un'annotazione di un certo tipo specicato. @args : indica l'insieme dei join point nei quali il tipo a runtime passato come argomento attuale ha un'annotazione di un certo tipo specicato. @within : indica l'insieme dei join point nei quali i tipi hanno un'annotazione specicata. @annotation : indica l'insieme dei join point nei quali il soggetto del join point ha un'annotazione specicata. La dierenza tra this e target, che apparentemente sembrano simili, si comprende solo ricordando che Spring AOP basato su proxy. Si dierenzia infatti tra l'oggetto proxy stesso (identicato da this ) e l'oggetto target che si trova dietro il proxy (identicato da target ). A causa di questa natura, inoltre, i metodi rmati come protected non vengono intercettati, n per i proxy JDK n per quelli CGLIB. La conseguenza che possibile intercettare solo metodi pubblici. Il set completo di indicatori di AspectJ ne comprende alcuni che non sono supportati da Spring AOP: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cow, cowbelow, if, @this e @withincode. Utilizzare questi causa il sollevamento di una IllegalArgumentException eccezione. Probabilmente il set correntemente supportato da Spring AOP sar ampliato nelle versioni successive. Spring AOP inoltre supporta un altro PCD chiamato bean. Questo permette il matching con i join point che riguardano un particolare bean, passandogli il nome che esso ha all'interno del container. Questo indicatore ha la seguente forma:
bean(idOrNameofBean)
Questo indicatore specico di Spring AOP e quindi non esiste in AspectJ.
// intercetta l'esecuzione di ogni metodo pubblico @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} // intercetta l'esecuzione di tutti i metodi nel package trading @Pointcut("within(com.xyz.someapp.trading..*)") private void inTrading() {} // intercetta l'esecuzione di tutti i metodi pubblici nel package trading @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {}
39
E' una buona abitudine costruire espressioni complesse in questo modo. Quando ci si riferisce ai pointcut tramite il loro nome, vengono applicate le regole di visibilit di Java (puoi vedere pointcut private nello stesso tipo, protected nello stesso package e public ovunque). La visibilit non incide sull'applicazione dei pointcut.
@Aspect public class SystemArchitecture { /** * Intercettazione di qualunque metodo nello strato web */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} /** * Intercettazione di qualunque metodo nello strato dei servizi */ @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} /** * Intercettazione di qualunque metodo nello strato dao */ @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} /** * Un servizio business l'esecuzione di ogni metodo definito nell'interfaccia * service. Questa definizione parte dal presupposto che le interfacce sono * presenti all'interno del package "service" e le implementazioni sono nei * sotto-package. * * Se si raggruppano le interfacce dei servizi per aree funzionali (per esempio, * nel package com.xyz.someapp.abc.service e com.xyz.def.service) allora potrebbe * essere usata l'espressione "execution(* com.xyz.someapp..service.*.*(..))". * * Alternativamente, si pu scrivere l'espressione usando il PCD 'bean', come * "bean(*Service)" (questo parte dal presupposto che sia stato usato un criterio * coerente nell'assegnazione dei nomi dei bean). */ @Pointcut("execution(* com.xyz.someapp.service.*.*(..))") public void businessService() {} /**
40
* Un'operazione di data access corrisponde all'esecuzione di ogni metodo definito * nell'interfaccia dao. Questo parte dal presupposto che le interfacce sono state * definite all'interno del package "dao" e le loro implementazioni in sotto-package. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {}
"execution(* esempiospring.Pippo.ab*(..))" // metodi della classe Pippo public void aba(); public void abb(); public void aca();
I metodi aba() e abb() verranno intercettati. Il metodo aca() non verr intercettato. Il param-pattern un po' pi complicato: () matcha un metodo che non prende parametri, mentre (..) matcha con qualunque numero di parametri (zero o pi). Il pattern (*) matcha con i metodi che prendono un solo parametro (di qualunque tipo), (*, String) matcha con i metodi che prendono due parametri, di cui il secondo necessariamente di tipo String. Per maggiori informazioni consultare la sezione Language Semantics di AspectJ Programming Guide [Tea03]. Quelli che seguono sono alcuni esempi: esecuzione di qualunque metodo pubblico
execution(public * *(..))
esecuzione di ogni metodo con il nome che inizia per 'set'
execution(* set*(..))
esecuzione di ogni metodo denito nell'interfaccia AccountService
41
execution(* com.xyz.service.AccountService.*(..))
esecuzione di ogni metodo denito nel package service
execution(* com.xyz.service.*.*(..))
esecuzione di ogni metodo denito nel package service o in un qualunque sotto package
execution(* com.xyz.service..*.*(..))
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) nel package service
within(com.xyz.service.*)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) nel package service o in un qualunque sotto-package
within(com.xyz.service..*)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) dove il proxy implementa l'interfaccia AccountService. this usato anche nella forma congiunta che si vedr in 4.2.4.6.
this(com.xyz.service.AccountService)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) dove l'oggetto target implementa l'interfaccia AccountService. target usato anche nella forma congiunta che si vedr in 4.2.4.6.
target(com.xyz.service.AccountService)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) che prende un singolo parametro e dove l'argomento passato a runtime Serializable. args usato anche nella forma congiunta che si vedr in 4.2.4.6. Da notare che la seguente scrittura diversa da execution(* *(java.io.Serializable)) : la versione con args matcha se il parametro passato Serializable a runtime, mentre execution prevede un parametro di tipo Serializable gi nella rma del metodo.
args(java.io.Serializable)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) dove l'oggetto target ha l'annotazione @Transactional. @target usato anche nella forma congiunta che si vedr in 4.2.4.6.
@target(org.springframework.transaction.annotation.Transactional)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) dove il tipo dichiarato dell'oggetto target ha l'annotazione @Transactional. @within usato anche nella forma congiunta che si vedr in 4.2.4.6.
@within(org.springframework.transaction.annotation.Transactional)
42
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) dove il metodo in esecuzione ha l'annotazione @Transactional. @annotation usato anche nella forma congiunta che si vedr in 4.2.4.6.
@annotation(org.springframework.transaction.annotation.Transactional)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) che prende un singolo parametro e dove il tipo di argomento passato a runtime ha l'annotazione @Classied. @args usato anche nella forma congiunta che si vedr in 4.2.4.6.
@args(com.xyz.security.Classified)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) sul bean di Spring chiamato 'tradeService'
bean(tradeService)
ogni join point (nel caso di Spring AOP solo esecuzione di metodi) sui bean di Spring che hanno il nome che matcha con l'espressione '*Service'
bean(*Service)
4.2.4.1 Before
Un advice viene dichiarato come before in un aspetto tramite l'annotazione @Before:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
In questo esempio abbiamo passato all'annotazione il nome del pointcut a cui fare riferimento. Possiamo per passargli direttamente l'espressione del pointcut:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("execution(* com.xyz.myapp.dao.*.*(..))") public void doAccessCheck() { // ... } }
43
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
Ovviamente possibile avere pi advice dichiarati all'interno dello stesso aspetto. In questi esempi ne viene mostrato solo uno per volta per focalizzarsi su quello di cui si sta discutendo. Qualche volta pu essere necessario, nel corpo dell'advice, accedere al valore attuale ritornato dal metodo intercettato. E' possibile utilizzare una forma che consenta di utilizzare tale valore:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... } }
Il nome usato nell'attributo di ritorno deve corrispondere al nome del parametro nel metodo dell'advice. Quando il metodo in esecuzione ritorna un valore, tale valore sar passato al metodo dell'advice attraverso il corrispondente argomento. La clausola returning restringe il matching solo a quei metodi che ritornano un valore di un tipo specicato (in questo caso Object ).
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... } }
44
Spesso si vuole che l'advice venga eseguito solo se stata sollevata un'eccezione di un certo tipo, e inoltre si vuole accedere all'eccezione all'interno dell'advice:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } }
Il nome usato nell'attributo di ritorno deve corrispondere al nome del parametro nel metodo dell'advice. Quando il metodo in esecuzione ritorna un'eccezione, essa sar passata al metodo dell'advice attraverso il corrispondente argomento. La clausola throwing restringe il matching solo a quei metodi che ritornano un'eccezione di un tipo specicato (in questo caso DataAccessException ).
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @Aspect public class AfterFinallyExample { @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // ... } }
4.2.4.5 Around
L'ultimo tipo di advice l'around. Questo advice parte intorno al metodo intercettato. Ha l'opportunit, infatti, di lavorare sia prima sia dopo l'esecuzione del metodo, e di determinare quando, come e se il metodo attualmente in esecuzione eettua completamente il suo lavoro. Tale advice spesso usato per condividere lo stato prima e dopo l'esecuzione del metodo in maniera thread-safe (facendo partire e stoppando un timer, per esempio). Questo advice viene dichiarato usando l'annotazione @Around. Il primo parametro dell'advice deve essere di tipo ProceedingJoinPoint. All'interno del corpo dell'advice, chiamando proceed() sul ProceedingJoinPoint viene evidenziato il metodo che in esecuzione. Tutto ci che verr scritto prima, verr eseguito prima del metodo intercettato e tutto ci che verr scritto dopo verr eseguito dopo il metodo intercettato. Questa funzione pu essere chiamata anche passando un Object[]. I valori nell'array saranno usati come argomenti al metodo quando andr in esecuzione.
45
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch System.out.println("Prima del metodo"); Object retVal = pjp.proceed(); // stop stopwatch System.out.println("Dopo il metodo"); } return retVal;
Il valore ritornato dall'around advice sar il valore di ritorno visto dall'invocatore del metodo. Un semplice aspetto che fa caching, per esempio, potrebbe ritornare un valore da una cache se ne ha una, e invocare proceed() se non ce l'ha. Da notare che proceed pu essere invocato una volta, molte volte o nessuna volta all'interno del corpo dell'around device. Tutte queste opzioni sono assolutamente legali.
Accesso al JoinPoint corrente Ogni advice pu dichiarare come suo primo parametro un
cezione. Per rendere i valore degli argomenti disponibili all'advice, possibile usare la forma congiunta di args. Se il nome di un parametro usato al posto del nome scritto nell'espressione del descrittore args, allora il valore dell'argomento corrispondente sar passato come valore quando l'advice viene invocato. Un esempio render pi chiaro. Supponiamo di voler intercettare l'esecuzione di un metodo del dao che prende un oggetto Account come primo parametro e di voler accedere a quest'oggetto all'interno dell'advice. Possiamo scrivere qualcosa del genere:
Passare parametri all'advice Abbiamo visto come recuperare un valore di ritorno o un'ec-
46
La parte args(account,..) dell'espressione del pointcut soddisfa due scopi: restringe il matching solo a quei metodi che prendono almeno un parametro e che tale parametro un'istanza di Account ; rende l'oggetto Account attuale disponibile all'advice attraverso il parametro account. L'oggetto proxy (this ), l'oggetto target (target ) e le annotazioni (@within, @target, @annotation, @args) possono tutte essere usate nello stesso modo.
4.2.4.7 Priorit
Cosa succede quando pi advice devono partire in corrispondenza dello stesso join point? Spring AOP segue le stesse regole di precedenza di AspectJ per determinare le priorit di esecuzione. L'advice con precedenza pi alta parte per primo on the way in (quindi dati tue advice before, quello con precedenza maggiore parte per primo). All'uscita di un join point (on the way out), l'advice con precedenza pi alta parte per ultimo. Quando due advice deniti in aspetti dierenti hanno bisogno entrambi di partire sullo stesso join point, a meno che non sia stato specicato diversamente, l'ordine di esecuzione non denito. E' possibile controllarlo specicando le priorit. Questo pu essere fatto nel modo normale di Spring implementando l'interfaccia org.springframework.core.Ordered nell'aspetto o annotandolo con l'annotazione @Order. Dati due aspetti, l'aspetto che restituisce il valore pi basso con Ordered.getValue() (o il valore dell'annotazione) ha la precedenza pi alta. Quando due advice deniti nello stesso aspetto hanno bisogno entrambi di parire sullo stesso join point, l'ordine di esecuzione non denito e non possibile recuperare l'ordine di dichiarazione attraverso le reections. In questo caso considerare una riorganizzazione degli aspetti e degli advice.
4.2.5 Introduction
Le introduzioni (note in AspectJ come dichiarazioni inter-type) consentono ad un aspetto di dichiarare che l'oggetto advised implementi una data interfaccia e provveda alla sua implementazione. Un'introduzione viene denita tramite l'annotazione @DeclareParents. Quest'annotazione utilizzata per dichiarare che il tipo che matcha ha un nuovo genitore. Per esempio, data un'interfaccia UsageTracked, e una sua implementazione, DefaultUsageTracked, il seguente aspetto dichiara che tutte le classi che implementano le interfacce legate al package service, implementeranno anche l'interfaccia UsageTracked.
@Aspect public class UsageTracking { @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() &&" + "this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); }
L'interfaccia che deve essere implementata determinata dal tipo del campo annotato (mixin, di tipo UsageTracked. L'attributo value di @DeclareParents un pattern AspectJ:
47
ogni bean che matcha implementer l'interfaccia UsageTracked. Viene anche fornita l'implementazione di default. Da notare che nell'advice before nell'esempio, i bean service possono essere direttamente usati come implementazioni di UsageTracked. Se si accede al bean, si pu scrivere:
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { private int someState; @Before(com.xyz.myapp.SystemArchitecture.businessService()) public void recordServiceUsage() { // ... }
L'eetto della clausola perthis che verr creata un'instanza dell'aspetto per ogni oggetto che esegue un servizio di business. L'istanza viene creata la prima volta che un metodo dell'oggetto service viene invocato. L'aspetto viene poi distrutto quando l'oggetto viene distrutto. Prima che l'istanza dell'aspetto sia creata, nessuno degli advice al suo interno viene eseguito. Una volta creata l'istanza dell'aspetto, gli advice dichiarati all'interno saranno eseguiti ad ogni matching con i join points d'interesse, ma solo quando l'oggetto service quello a cui l'istanza dell'aspetto stata associata. La clausola pertarget funziona nello stesso modo, ma crea un'istanza dell'aspetto per ogni oggetto target nel join point che matcha.
4.2.7 Esempio
Vediamo in questo paragrafo un semplice esempio del funzionamento di quanto visto nora. Immaginiamo di avere l'interfaccia IDao e la sua implementazione Dao, viste precedentemente.
// interfaccia public interface IDao { public void salva(); public void recupera(); } // classe dao public class Dao implements IDao { public void salva() {
48
Supponiamo di voler loggare tutte le operazioni eettuate dal Dao, prima e dopo la loro esecuzione. Possiamo denire quindi il seguente aspetto:
@Aspect public class LoggerWithAnnotation { @Pointcut("execution(* esempiospring.Dao.*(..))") public void inDao() {} @Before("inDao()") public void beforeDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Sto per eseguire il metodo "+ jp.getSignature().getName()); } @After("inDao()") public void afterDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Ho eseguito il metodo "+ jp.getSignature().getName()); }
Quello che segue il le di congurazione di Spring:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- ============================== --> <!-- === Attivazione AOP === --> <!-- ============================== -->
49
<aop:aspectj-autoproxy/> <!-- ================================== --> <!-- === Definizione aspetti === --> <!-- ================================== --> <bean id="daoAspect" class="esempiospring.LoggerWithAnnotation"/> <!-- ================================== --> <!-- === Definizione dei beans === --> <!-- ================================== --> <bean id="dao" class="esempiospring.Dao" /> </beans>
Il metodo main per fare un test il seguente:
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"/configSpring.xml"}); // Test con un bean gestito dal contenitore IDao dao = context.getBean("dao", IDao.class); dao.salva(); dao.recupera(); // Test con un bean non gestito dal contenitore IDao altrodao = new Dao(); altrodao.salva(); altrodao.recupera();
Eseguendolo, l'output sar:
esempiospring.Dao: Sto per eseguire il metodo salva Salvo un dato sul db esempiospring.Dao: Ho eseguito il metodo salva esempiospring.Dao: Sto per eseguire il metodo recupera Ho recuperato un dato dal db esempiospring.Dao: Ho eseguito il metodo recupera Salvo un dato sul db Ho recuperato un dato dal db
Come si pu vedere dalle ultime due righe, i bean al di fuori del container non subiscono intercettazioni.
50
public class LoggerWithXML { public void beforeDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Sto per eseguire il metodo "+ jp.getSignature().getName()); } public void afterDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Ho eseguito il metodo "+ jp.getSignature().getName()); }
La sua congurazione, all'interno del le XML la seguente:
<aop:config> <aop:aspect id="myAspect" ref="daoAspect"> ... </aop:aspect> </aop:config> <bean id="daoAspect" class="esempiospring.LoggerWithXML"/>
<aop:config> <aop:pointcut id="inDao" expression="execution(* esempiospring.Dao.*(..))"/> <!-- Definizione degli aspetti --> ... </aop:config>
Notare che la sintassi per l'espressione del pointcut esattamente la stessa presentata nei paragra precedenti. Un pointcut pu essere anche denito all'interno di un aspetto. La sua dichiarazione molto simile alla precedente:
51
<aop:config> <aop:aspect id="myAspect" ref="daoAspect"> <aop:pointcut id="inDao" expression="execution(* esempiospring.Dao.*(..))"/> ... </aop:aspect> </aop:config>
4.3.3.1 Before
Questo tipo di advice dichiarato all'interno del nodo <aop:aspect> utilizzando l'elemento <aop:before>.
<aop:config> <aop:pointcut id="daoService" expression="execution(* esempiospring.IDao.*(..))"/> <aop:aspect id="mioAspect" ref="daoAspect" > <aop:before pointcut-ref="daoService" method="beforeDaoOperationAdvise"/> </aop:aspect> </aop:config>
Qui daoService l'id del pointcut denito a livello di <aop:cong>.
52
4.3.3.5 Around
Questo tipo di advice denito nel modo seguente:
53
4.3.4 Introduzioni
Un'introduzione viene denita attraverso il nodo <aop:declare-parents> all'interno del nodo <aop:aspect>. Tale elemento utilizzato per dichiarare che il tipo che matcha ha un nuovo genitore. Per esempio, data un'interfaccia UsageTracked e una sua implementazione, DefaultUsageTracked, il seguente aspetto dichiara che tutti gli implementatori delle interfacce relative ai servizi dovranno implementare anche l'interfaccia UsageTracked.
<aop:aspect id="usageTrackerAspect" ref="usageTracking"> <aop:declare-parents types-matching="com.xzy.myapp.service.*+" implement-interface="com.xyz.myapp.service.tracking.UsageTracked" default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/> <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)" method="recordUsage"/> </aop:aspect>
4.3.6 Advisors
Il concetto di advisors proviene dal supporto alla AOP di Spring 1.2 e non ha un equivalente in AspectJ. Un advisor come un piccolo aspetto autocontenuto che ha un singolo advice. L'advice rappresentato da un bean e deve implementare una delle interfacce advice descritte nella sezione ??. Spring 2.0 supporta il concetto di advisors tramite l'elemento <aop:advisors>. Molto frequentemente utilizzato in congiunzione con l'advice transazionale. Qui abbiamo un esempio:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
54
4.3.7 Esempio
Rivediamo l'esempio presentato in 4.2.7 utilizzando l'XML.
public class LoggerWithXML { public void beforeDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Sto per eseguire il metodo "+ jp.getSignature().getName()); } public void afterDaoOperationAdvise(JoinPoint jp) { System.out.println(jp.getTarget().getClass().getName()+ ": Ho eseguito il metodo "+ jp.getSignature().getName()); }
Quello che segue il le di congurazione di Spring:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- ============================== --> <!-- === Attivazione AOP === --> <!-- ============================== --> <aop:aspectj-autoproxy/> <!-- ================================== --> <!-- === Definizione aspetti === --> <!-- ================================== --> <bean id="daoAspect" class="esempiospring.LoggerWithXML"/> <aop:config> <aop:pointcut id="inDao" expression="execution(* esempiospring.IDao.*(..))"/> <aop:aspect id="mioAspect" ref="daoAspect" > <aop:before pointcut-ref="inDao" method="beforeDaoOperationAdvise"/> <aop:after pointcut-ref="inDao" method="afterDaoOperationAdvise"/> </aop:aspect>
55
</aop:config> <!-- ================================== --> <!-- === Definizione dei beans === --> <!-- ================================== --> <bean id="dao" class="esempiospring.Dao" /> </beans>
Il metodo main per fare un test il seguente:
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"/configSpring.xml"}); // Test con un bean gestito dal contenitore IDao dao = context.getBean("dao", IDao.class); dao.salva(); dao.recupera(); // Test con un bean non gestito dal contenitore IDao altrodao = new Dao(); altrodao.salva(); altrodao.recupera();
Eseguendolo, l'output sar:
esempiospring.Dao: Sto per eseguire il metodo salva Salvo un dato sul db esempiospring.Dao: Ho eseguito il metodo salva esempiospring.Dao: Sto per eseguire il metodo recupera Ho recuperato un dato dal db esempiospring.Dao: Ho eseguito il metodo recupera Salvo un dato sul db Ho recuperato un dato dal db
56
Se invece si ha bisogno di intercettare oggetti che Spring non gestisce (come gli oggetti di dominio, spesso) allora c' bisogno di AspectJ. E' consigliato AspectJ anche quando c' bisogno di intercettare join points che non riguardino la semplice esecuzione di un metodo (per esempio l'accesso in lettura/scrittura di un campo). Quando si utilizza AspectJ si pu scegliere tra l'approccio basato sul linguaggio o l'approccio basato sulle annotazioni. Ovviamente, se non si usa almeno la versione 5 di Java, la scelta obbligata. Se gli aspetti giocano un ruolo importante all'interno del progetto e si ha la possibilit di usare AspectJ Development Tools (AJDT), il plugin per Eclipse, allora l'approccio basato sul linguaggio di AspectJ la scelta migliore e pi semplice perch il linguaggio stato specicatamente progettato per scrivere aspetti. Se non si ha a disposizione Eclipse o si hanno pochi aspetti che non giocano ruoli di primo piano, allora va considerata la possibilit di usare le annotazioni, risparmiandosi la fase di compilazione al di fuori di quella normale di Java.
@Pointcut(execution(* get*())) public void propertyAccess() {} @Pointcut(execution(org.xyz.Account+ *(..)) public void operationReturningAnAccount() {} @Pointcut(propertyAccess() && operationReturningAnAccount()) void accountPropertyAccess() {}
Nello stile XML si possono dichiarare i primi due pointcuts:
57
Un altro vantaggio dell'approccio @AspectJ che pu essere compreso sia da Spring AOP sia da AspectJ, quindi se per qualche motivo si decide che AspectJ debba essere integrato nella propria applicazione, eettuare la migrazione verso un approccio basato su AspectJ diventa molto semplice. In generale, @AspectJ preferibile in quelle situazioni in cui gli aspetti fanno qualcosa di pi della semplice gestione dei servizi di tipo enterprise.
<aop:aspectj-autoproxy proxy-target-class="true"/>
58
public class BankManagement { @Transactional public void processaPagamento() { // operazioni varie ... this.checkRatingCredito(); ... } public void checkRatingCredito() { // fa qualcosa... } // Altri metodi... ...
Com' possibile vedere, il metodo processaPagamento(), che pubblico, fa uso di un metodo interno alla classe, anch'esso pubblico, checkRatingCredito(). Situazioni del genere non sono molto frequenti. Tipicamente, quando un metodo utilizza un altro metodo della stessa classe, quest'ultimo privato. In questo caso invece la situazione diversa: il metodo checkRatingCredito() pubblico e quindi pu essere usato anche da altre classi esterne. Supponiamo che entrambi i metodi siano soggetti ad intercettazione. processaPagamento() necessita dell'apertura della transazione, che gestiamo tramite AOP, visto che probabilmente non l'unico metodo che ne ha bisogno. Anche checkRatingCredito() viene intercettato, poich come anche altri, prima della sua esecuzione va controllato il ruolo dell'utente che sta richiedendo l'operazione. Infatti tale operazione pu essere svolta solo da un BankManager. Deniamo quindi un aspetto dove gestiamo queste problematiche:
@Aspect public class Banca { @Poincut("...") public void transazione(){}; @Pointcut("...") public void permessoBankManager(){} @Before("transazione()") public void apriTransazione() { // Apri transazione... ... } @Before("permessoBankManager()") public void controllaPermessi() { // Controllo che l'utente abbia i permessi da BankManager ... }
59
Il codice dei pointcuts stato omesso, ma la semantica chiara: transazione() intercetta tutti i metodi che sono transazioni (per esempio indicati con @Transactional), tra cui processaPagamento(), e permessoBankManager() intercetta tutti i metodi su cui devono essere controllati i permessi dell'utente, tra cui checkRatingCredito(). Quando Spring AOP crea il proxy che gestir questo aspetto, creer un oggetto che far da ponte tra chi chiama i servizi di BankManagement e la classe BankManagement stessa. In pseudocodice, l'aspetto del proxy sar pi o meno questo:
processaPagamento() { // Attiva la transazione... ... INVOKE original.processaPagamento(); } checkRatingCredito() { // Controllo i permessi dell'utente... ... INVOKE original.checkRatingCredito(); }
Dove con la parola original si intende un riferimento all'istanza della classe BankManagement che si trova dietro questo proxy e che contiene il metodo invocato con INVOKE. Quando una classe esterna chiamer il metodo processaPagamento() verr in eetti eseguito quello del proxy, e non direttamente quello della classe BankManagement che sta dietro. Cosa c' che non va in tutto questo? La parola this all'interno del metodo processaPagamento()... La parola this, fa bene ricordarlo, si riferisce sempre all'istanza dell'oggetto stesso in cui compare. Cosa succede quando viene invocato dall'esterno il metodo checkRatingCredito() della classe BankManagement ? Quello che succede che viene invocato in realt il metodo checkRatingCredito() del proxy, il quale eettua il controllo sullo stato dell'utente, e poi esegue il codice di checkRatingCredito(). Tutto questo esattamente conforme a quello che volevamo. Cosa succede quando viene invocato dall'esterno il metodo processaPagamento() della classe BankManagement ? Anche in questo caso viene in realt invocato il metodo processaPagamento() del proxy. Viene quindi aperta la transazione e viene poi eseguito il codice di processaPagamento(). In questo metodo, tuttavia, viene utilizzato anche il metodo checkRatingCredito(). Tuttavia viene invocato con la parola this, che si riferisce quindi all'istanza corrente. Non viene quindi eseguito checkRatingCredito() del proxy, ma viene eseguito quello dell'istanza corrente della classe BankManagement e in tale metodo il controllo sullo stato dell'utente non viene fatto!!! Il risultato che quando si invoca il metodo processaPagamento(), viene saltato un importante controllo. Questa situazione, per fare un discorso generale, si rischia ogni volta in cui una classe ha due metodi pubblici che vengono entrambi intercettati e gestiti da un aspetto, e in uno viene invocato l'altro tramite this. Come risolvere questo problema? La soluzione pu sembrare banale, ma va detta: va evitata questa organizzazione del codice. In questo caso specico meglio eettuare il controllo anche direttamente dentro al metodo processaPagamento(). Per esempio si crea un nuovo metodo, privato, dentro BankManagement che eettua i controlli dell'advice e poi in-
60
voca il metodo this.checkRatingCredito(). Questo nuovo metodo quello che dev'essere usato all'interno di processaPagamento(). Un'altra soluzione (assolutamente da evitare!) quella in cui si richiede al contenitore il proxy della classe corrente e si invoca il metodo checkRatingCredito() su di esso. L'istruzione la seguente:
((BankManagement) AopContext.currentProxy()).checkRatingCredito();
Tale soluzione sconsigliata poich crea un forte accoppiamento tra il codice dell'applicazione e il contenitore Spring e questo va sempre assolutamente evitato!
// crea una factory che pu generare un proxy per un dato oggetto AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // aggiunge un aspetto (classe marcata con l'annotazione @AspectJ). // questo metodo pu essere chiamato quante volte si vuole in base ai // differenti aspetti che si vogliono considerare. factory.addAspect(SecurityManager.class); // possibile aggiungere istanze gi esistenti di aspetti factory.addAspect(usageTracker); // genera il proxy che ci interessa MyInterfaceType proxy = factory.getProxy();
Parte II
Data Access
61
Capitolo 5
63
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
E' anzitutto un'interfaccia provider di servizi (SPI, Service Provider Interface), bench si possa usare programmaticamente all'interno del codice. Non legata a nessuna strategia di lookup come JNDI. Le implementazioni di questa interfaccia sono denite come qualunque altro oggetto nel container IoC. Ogni metodo pu sollevare un'eccezione di tipo TransactionException, che un'eccezione unchecked (ovvero estende java.lang.RuntimeException ). I malfunzionamenti dell'infrastruttura transazionale sono spesso immancabilmente fatali. In rari casi, l dove tramite codice possibile ripristinare la situazione, lo sviluppatore pu scegliere se catturare e gestire la TransactionException. Il punto fondamentale che lo sviluppatore non costretto a farlo. Il metodo getTransaction(..) ritorna un oggetto TransactionStatus, che dipende da un parametro TransactionDenition. Tale oggetto ritornato potrebbe rappresentare una nuova transazione, oppure pu rappresentare una transazione esistente se la transazione chiamata gi esistente nello stack. Le implicazioni di questo risiedono nel fatto che, cosi come il contesto transazionale Java EE, un TransactionStatus associato ad un thread di esecuzione. L'interfaccia TransactionDenition denisce i seguenti aspetti: Isolation: il grado di isolamento della transazione dalle altre transazioni. Propagation: tipicamente tutto il codice eseguito all'interno dello scope di una transazione viene eseguito all'interno di quella transazione. Ci nonostante c' la possibilit di specicare questo comportamento per una transazione eseguita quando un contesto transazionale gi esiste. Per esempio, all'interno del codice di una transazione, viene richiamata una funzione che anch'essa una transazione. Il programmatore pu quindi decidere se aprire una nuova transazione o se utilizzare il contesto gi esistente. Timeout: determina il tempo di esecuzione disponibile alla transazione, prima che essa possa andare in timeout. Read-only status: una transazione read-only pu essere usata quando il codice deve leggere dei dati senza modicarli. L'interfaccia TransactionStatus fornisce una via semplice per codicare il controllo delle transazioni:
64
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
Indipendentemente che si scelga un approccio dichiarativo o programmatico per la gestione delle transazioni, denire correttamente l'implementazione del PlatformTransactionManager essenziale. Tipicamente questo si pu fare tramite Dependency Injection. Le implementazioni del PlatformTransactionManager di solito richiedono la conoscenza dell'ambiente all'interno del quale lavorano: JDBC, JTA, Hibenate e cosi via. Gli esempi che seguiranno aiuteranno a chiarire (Questo esempio lavora su JDBC). Si denisce un datasource:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="\${jdbc.driverClassName}" /> <property name="url" value="\${jdbc.url}" /> <property name="username" value="\${jdbc.username}" /> <property name="password" value="\${jdbc.password}" /> </bean>
Il relativo PlatformTransactionManager bean dovr settare un riferimento a tale DataSource. Sar qualcosa del genere:
<?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:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd"> <!-- Lookup delle risorse JNDI --> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/esempio"/> <!-- Definizione del transaction manager -->
65
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mappingResources"> <list> <value>esempiospring/hibernate/persona.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=\${hibernate.dialect} </value> </property> </bean> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
Se si sta utilizzando Hibernate la gestione delle transazioni JTA di un container Java EE, allora si pu utilizzare il JtaTransactionManager come nel precedente esempio per JDBC.
66
Questo tipo di gestione delle transazioni resa possibile grazie ad AOP. Come per gli EJB CMT, anche nell'approccio adottato da Spring possibile specicare il comportamento delle transazioni a livello dei singoli metodi. E' possibile fare una chiamata setRollbackOnly() nel contesto transazionale, se necessario. Queste sono le dierenze tra i due strumenti: mentre EJB CMT legato a JTA, Spring lavora in qualsiasi ambiente. Pu lavorare con transazioni JTA o con transazioni locali usando JDBC, JPA, Hibernate o JDO, semplicemente aggiustando in maniera opportuna il le di congurazione. possibile applicare la gestione dichiarativa ad ogni classe, e non solamente a classi speciali come sono gli EJB. Spring ore regole dichiarative per il rollback, una caratteristica che non ha equivalenti con gli EJB. Spring consente di customizzare il comportamento transazionale, usando AOP. Spring non consente la propagazione del contesto transazionale attraverso le chiamate remote. Il concetto di regole di rollback importante: consentono, infatti, di specicare quali eccezioni potrebbero causare automaticamente il rollback. Questo si pu fare dichiarativamente, senza uso di codice Java. Quindi, bench sia possibile chiamare il metodo setRollbackOnly() sull'oggetto TransactionStatus, pi spesso si specicano regole per cui un'eccezione specicata genera il rollback. Il vantaggio signicativo di questo approccio sta nel fatto che gli oggetti di business non dipendono dall'infrastruttura delle transazioni.
5.3.2 Esempio
Consideriamo la seguente interfaccia e la sua possibile implementazione. Questa classe contiene dei metodi che hanno il solo scopo di essere da esempio, concentrandoci sulla transazione e non sul modello di dominio. La classe DefaultFooService solleva eccezioni di tipo UnsupportedOperationException all'interno del corpo di ogni metodo; questo ci dar la possibilit di vedere la creazione delle transazioni e il loro rollback.
// l'interfaccia dei servizi che vogliamo che siano transazionali package esempiospring.service; public interface FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
67
// un'implementazione di tali servizi package esempiospring.service; public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { throw new UnsupportedOperationException(); } public Foo getFoo(String fooName, String barName) { throw new UnsupportedOperationException(); } public void insertFoo(Foo foo) { throw new UnsupportedOperationException(); } public void updateFoo(Foo foo) { throw new UnsupportedOperationException(); }
Assumiamo che i primi due metodi, getFoo(String) e getFoo(String, String), debbano essere eseguiti nel contesto di una transazione read-only, e che gli altri metodi, insertFoo(Foo) e updateFoo(Foo), debbano essere eseguiti nel contesto di una transazione read-write. La seguente congurazione descritta nel dettaglio nei prossimi paragra:
<!-- dal spring-config.xml --> <?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- definizione del DataSource --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>
68
<!-- definizione del PlatformTransactionManager --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- Questo il servizio che vogliamo rendere transazionale --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- Advice --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- semantica della transazione --> <tx:attributes> <!-- tutti i metodi che iniziano con 'get' sono read-only --> <tx:method name="get*" read-only="true"/> <!-- gli altri metodi usano il settaggio di default --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- Dobbiamo assicurarci che l'advice parta per ogni esecuzione di ogni operazione definita nell'interfaccia FooService --> <aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> </aop:config> </beans>
Esaminiamo questa congurazione. Vogliamo rendere un oggetto di servizio, il bean fooService, transazionale. La semantica da applicare per le transazioni incapsulata nella denizione <tx:advice/>. Questa denizione si pu leggere in questo modo: ...tutti i metodi che cominciano con get sono da eseguire nel contesto di una transazione read-only, e tutti gli altri metodi sono da eseguire nel contesto di deafult.. L'attributo transaction-manager nell'advice congurato con il nome del bean PlatformTransactionManager, che quello che deve gestire la transazione, in questo caso txManager. La denizione <aop:cong/> assicura che l'advice transazionale denito dal bean txAdvice viene eseguito nei punti appropriati. Prima si denisce un pointcut che matcha con l'esecuzione di ogni operazione denita nell'interfaccia FooService. Poi si associa il pointcut con il txAdvice. Un requisito diuso quello di rendere transazionale un intero strato dei servizi. La miglior via per farlo semplicemente quella di cambiare l'espressione del pointcut per matchare ogni operazione che riguardi lo strato dei servizi. Per esempio:
69
5.3.3 Rollback
Questa sezione descrive come sia possibile controllare il rollback di una transazione in modo dichiarativo. Il modo migliore per comunicare al container come gestire il rollback quello di sollevare un'Exception da codice che viene eseguita nel contesto di una transazione. Spring catturer ogni Exception non gestita determinando se marcare la transazione per il rollback o no. In questa congurazione di default, Spring marca per il rollback solo le transazioni che sollevano un'eccezione unchecked, ovvero quando viene sollevata un'eccezione istanza o sottoclasse di RuntimeException. Per le eccezioni checked non viene attivato nessun rollback di default. E' possibile congurare esattamente per quali eccezioni attivare il rollback, incluse le eccezioni checked. Il seguente frammento XML mostra come fare:
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
E' possibile specicare anche le regole per non attivare il rollback, se non si vuole annullare la transazione al sollevamento di un'eccezione. Il seguente frammento XML mostra un esempio:
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
Quando coesistono entrambe le notazioni, la pi stringente quella che vince. Vediamolo nel seguente esempio:
70
Assumiamo che tutti le classi di servizio sono denite all'interno del package x.y.service. Per avere una congurazione transazionale di default tutti i beans che sono istanze di classi denite in questo package (e nei sottopackage) e che hanno i nomi che terminano con Service, si pu scrivere quello che segue:
<aop:config> <aop:pointcut id="serviceOperation" expression="execution(* x.y.service..*Service.*(..)) <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/> </aop:config> <!-- questi due beans saranno transazionali... --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <bean id="barService" class="x.y.service.extras.SimpleBarService"/> <!-- ...e questi due no --> <bean id="anotherService" class="org.xyz.SomeService"/> // non nel package giusto <bean id="barManager" class="x.y.service.SimpleBarManager"/> // non finisce con 'Service' <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
L'esempio che segue mostra come realizzare due congurazione completamente dierenti.
<aop:config> <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/> <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/> <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/> <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/> </aop:config> <!-- questo bean sar transazionale --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- anche questo bean sar transazionale, ma con una configurazione differente --> <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/> <tx:advice id="defaultTxAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
71
5.3.5 <tx:advice/>
Questa sezione riassume le varie congurazioni che possono essere specicate usando questo tag. Le impostazioni di default sono le seguenti: Propagation settata come REQUIRED. Isolation settato come DEFAULT. Trasaction settato come read/write Timeout settato al valore di default del sistema di transazione in uso, oppure non settato se questa funzionalit non supportata. Tutte le transazioni che sollevano una RuntimeException vengono rollbackate, e tutte le eccezioni checked no. Questi settaggi si possono cambiare. I vari attributi dell'elemento <tx:method/> sono riassunti in tabella 5.1.
Attributo
name propagation isolation timeout read-only rollback-for no-rollback-for
Richiesto? Default
si no no no no no no REQUIRED DEFAULT -1 false
Descrizione
Nome del metodo che si vuole associare ad una o pi transazioni Comportamento di propagation Livello di isolation Timeout della transazione La transazione read-only?
5.3.6 @Transactional
Oltre all'approccio dichiarativo, possibile utilizzare le annotazioni. Dichiarare le transazioni direttamente all'interno del codice Java pone queste dichiarazioni molto vicine al codice. L'uso dell'annotazione @Transactional illustrato nel seguente esempio:
// la classe che implementa il servizio che vogliamo rendere transazionale @Transactional public class DefaultFooService implements FooService { Foo getFoo(String fooName);
72
Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo);
Quando tale bean denito all'interno del container IoC, l'istanza del bean pu essere resa transazionale aggiungendo una riga all'XML:
<!-- questo l'oggetto che vogliamo rendere transazionale --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- abilita la configurazione delle transazioni tramite annotazioni --> <tx:annotation-driven transaction-manager="txManager"/>
<!-- un PlatformTransactionManager richiesto --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManage <property name="dataSource" ref="dataSource"/> </bean>
Quando si usano i proxy, si dovrebbe applicare l'annotazione @Transactional solo sui metodi pubblici. Se lo si applica su metodi protetti o privati, non viene sollevato nessun errore, ma il metodo annotato non mostra la propria congurazione transazionale. Valutare in questo caso l'uso di AspectJ. E' possibile piazzare questa annotazione prima della denizione di un'interfaccia, di un metodo di un'interfaccia, della denizione di una classe o di un metodo pubblico di una classe. Comunque, la sola presenza dell'annotazione non suciente ad attivare il comportamento transazionale. L'annotazione infatti un semplice metadato per congurare in modo appropriato i beans con tale comportamento. Nell'esempio appena visto, tale comportamento poi abilitato dall'elemento <tx:annotation-driven/>. Si consiglia di annotare le classi concrete (o i metodi di classi concrete) e non le interfacce (o i metodi all'interno delle interfacce). In tabella 5.2 sono riportate le congurazioni dell'elemento <tx:annotation-driven/>. E' possibile annotare sia una classe sia i suoi metodi interni, per dare diversi livelli di congurazione. Consideriamo il seguente esempio:
@Transactional(readOnly = true) public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // fa qualcosa } // questa configurazione ha precedenza @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // fa qualcosa }
Quando viene chiamato il metodo getFoo(..), vale la regola generale all'inizio della denizione della classe. Quando invece viene chiamato il metodo updateFoo(..), poich a questo metodo stata applicata una regola, questa ha precedenza su quella generale.
73
Attributo
transaction-manager mode
Default
transactionManager proxy
Descrizione
Nome del transaction manager che si intende usare. Il comportamento di default fa s che venga usato il meccanismo dei proxy di Spring AOP. La modalit alternativa, aspectj, indica che il meccanismo usato sar quello basato su aspetti costruiti con AspectJ. E' applicato solo se mode=proxy. Controlla il tipo di proxy che viene creato sui bean annotati con @Transactional. Quando l'attributo settato a true vengono utilizzati i proxy class-based. Quando settato a false vengono utilizzati i proxy JDK standard basati sulle interfacce. Denisce l'ordine di precedenze tra gli advice da eseguire su un bean marcato come @Transactional.
proxy-target-class
false
order
Ordered.LOWEST_PRECEDENCE
74
Attributo
propagation isolation readOnly timeout rollbackFor
Tipo
enum: Propagation enum: Isolation boolean int (in secondi) Array di Class che devono essere derivati da Throwable Array di Class che devono essere derivati da Throwable Array di Class che devono essere derivati da Throwable Array di Class che devono essere derivati da Throwable
Descrizione
Opzionale Opzionale read/write vs read only timeout Array di eccezioni che possono portare al rollback. Array di nomi di eccezioni che possono portare al rollback. Array di eccezioni che non portano al rollback. Array di nomi di eccezioni che non portano al rollback.
rollbackForClassname
noRollbackFor
noRollbackForClassname
Tabella 5.3: opzioni di <tx:annotation-driven/> esterno indipendente da quello interno. Naturalmente, nel caso del comportamento standard di PROPAGATION_REQUIRED, tutti questi scope saranno mappati verso la stessa transazione sica. E' ovvio che un rollback della transazione interna avr eetto anche sulla possibilit di commit della transazione esterna.
5.3.7.2 RequiresNew
PROPAGATION_REQUIRES_NEW a dierenza di PROPAGATION_REQUIRED, utilizza una transazione completamente indipendente per ogni scope. In questo caso, le transazioni siche sono dierenti e quindi possono committare o eettuare un rollback in maniera indipendente.
5.3.7.3 Nested
PROPAGATION_NESTED usa una singola transazione sica con pi savepoints verso i quali possibile fare il rollback. Questo comportamento possibile solo con risorse JDBC.
CAPITOLO 5. GESTIONE DELLE TRANSAZIONI 2. Viene eseguito l'advice transazionale. 3. Viene eseguito il metodo updateFoo(..). 4. Viene committata la transazione. 5. L'aspetto iniziale riporta l'esatta durata della transazione.
75
Questo il codice d'esempio per la situazione sopra descritta. Le precedenze degli advice sono controllati tramite l'interfacca Ordered.
package x.y; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; import org.springframework.core.Ordered; public class SimpleProfiler implements Ordered { private int order; // ci permette di controllare le precedenze degli advice public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } // questo metodo un advice 'around' public Object profile(ProceedingJoinPoint call) throws Throwable { Object returnValue; StopWatch clock = new StopWatch(getClass().getName()); try { clock.start(call.toShortString()); returnValue = call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } return returnValue; }
Questo il le di congurazione di Spring.
76
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- questo l'aspetto, da eseguire PRIMA dell'adivice transazionale --> <bean id="profiler" class="x.y.SimpleProfiler"> <property name="order" value="1"/> </bean> <tx:annotation-driven transaction-manager="txManager" order="200"/> <aop:config> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
Il risultato di questa congurazione un bean fooService che ha un aspetto transazionale e un aspetto di timing applicati nell'ordine desiderato. E' possibile, ovviamente, congurare un qualunque numero di aspetti in questo modo. L'esempio che segue fa esattamente la stessa cosa, ma usando solo l'approccio dichiarativo XML.
77
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- advice timer --> <bean id="profiler" class="x.y.SimpleProfiler"> <property name="order" value="1"/> </bean> <aop:config> <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>
78
TransactionStatus status = txManager.getTransaction(def); try { // fai qualcosa txManager.commit(status); } catch (MyException ex) { txManager.rollback(status); throw ex; }
Bibliograa
[boo] Wikipedia. [boo09] Spring - Java Application Framework - Reference Documentation. Spring Source, 2009. [Tea03] The AspectJ Team. The AspectJ Programming Guide. Xerox Corporation, 2003.
79