You are on page 1of 86

Spring 3

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

1.10 1.11 1.12

1.13 1.14 2.1 2.2 2.3

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

3 Spring Expression Language (SpEL)


3.1 3.2 3.3 3.4 3.5 4.1

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 Gestione delle transazioni

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.

Figura 1: Architettura di Spring

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

CAPITOLO 1. IL CONTENITORE IOC

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 {

CAPITOLO 1. IL CONTENITORE IOC

public static void main(String[] args) { B b = BeansFactory.getB(); b.esegui(); }

1.1 Creazione di un bean


L'idea di IoC quindi una formalizzazione di una tecnica gi presente da diversi anni nell'ingegneria del software. Tale modulo ci mette quindi a disposizione una factory gi pronta per l'uso alla quale dobbiamo solo dare le istruzioni per la creazione dei beans. Come in tutti i framework, tale congurazione avviene tramite le xml. Quello che segue il codice d'esempio del le spring-cong.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" 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.

CAPITOLO 1. IL CONTENITORE IOC

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.

1.2 Congurazione di un bean


Naturalmente, se il ruolo dell'intermediario fosse solo quello di creare oggetti, sarebbe piuttosto complicato capirne l'utilit. Molto spesso capita che un bean, prima di essere usato, debba essere congurato. Potrebbe essere il caso di un DAO, che prima di essere usato devono essere settati i suoi parametri per la connessione ad un db. In generale, ogni oggetto il cui comportamento determinato dal valore dei suoi attributi, deve essere congurato prima di essere usato. In un caso come quello sopra descritto, alla creazione di un bean, dovrebbero seguire un elenco di chiamate ai suoi metodi set() per settare tutti gli attributi del bean, o in alternativa, creare un costruttore ad hoc per poterlo fare. Il modulo IoC di Spring, consente di settare i parametri di congurazione direttamente dentro il le di congurazione. Supponiamo che la classe B sia fatta cosi:

// 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>

CAPITOLO 1. IL CONTENITORE IOC

</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).

1.3 Costruire una rete di oggetti


E se l'attributo di un bean un riferimento ad un altro oggetto? Un'applicazione, tipicamente, strutturata come una rete di oggetti, pi o meno tta, che collaborano tra loro. Ogni oggetto, quindi, svolge il suo compito usandone altri.

CAPITOLO 1. IL CONTENITORE IOC

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 A public class A { private B b; public void esegui() { this.b.esegui(); }

// 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

CAPITOLO 1. IL CONTENITORE IOC

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:

CAPITOLO 1. IL CONTENITORE IOC

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.

1.5 La stringa vuota e il null


Spring tratta un argomento vuoto come una stringa vuota. Scrivendo:

<bean id="esempio" class="esempi.Prova"> <property name="email" value="" /> </bean>


l'attributo email verr settato come se avessimo lanciato il seguente comando:

setEmail("");
Se si vuole invece considerare il valore null, si deve scrivere:

<bean id="esempio" class="esempi.Prova"> <property name="email"> <null/> </property> </bean>

1.6 Inizializzazione Lazy


L'ApplicationContext tipicamente eettua una pre-istanziazione dei bean singletons in fase di inizializzazione dell'applicazione. Questo comportamento molto comodo perch permette di individuare alcuni errori subito piuttosto che dopo qualche ora o addirittura dopo qualche giorno. Tuttavia, qualora non si desideri questo comportamento, possibile chiedere esplicitamente al container un'inizializzazione di tipo lazy di uno o pi bean.

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.foo.AnotherBean"/>


Nel caso del bean chiamato lazy, questo verr istanziato solo quando verr esplicitamente indicato al container.

CAPITOLO 1. IL CONTENITORE IOC

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>:

<bean id="pippo" class="esempi.Pippo" scope="prototype" />


I valori ammessi sono quelli espressi in tabella 1.1. Quando lo scope di un bean viene denito singleton, il container memorizza l'istanza dell'oggetto all'interno di una cache. Ad ogni successiva richiesta di quel bean, il container restituir sempre la stessa istanza. Questo tipo di scope viene adottato da Spring di default. Quando invece lo scope viene denito prototype, il container ad ogni richiesta del bean, ne crea un'istanza nuova, sovrascrivendo quella vecchia. Gli altri tre scope rispondono alle stesse regole delle web application. Spring mette inoltre a disposizione un sistema per creare scopes personalizzati (vedi 3.5 delle reference).

1.8 Ciclo di vita


Spring consente di specicare quali azioni debbano essere svolte durante l'inizializzazione di un bean, cosi come anche le precedure da eseguire prima della sua distruzione. Per quanto riguarda l'inizializzazione, possibile denire all'interno del bean il metodo init() invocandolo poi in fase di congurazione con l'attributo init-method. Ad esempio:

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:

CAPITOLO 1. IL CONTENITORE IOC

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 Estensione del container


Vediamo ora alcune funzionalit che ci permettono di estendere le funzionalit del container.

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;

CAPITOLO 1. IL CONTENITORE IOC

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

Bean 'punto' creato : x=1 y=3

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; }

CAPITOLO 1. IL CONTENITORE IOC

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;

ApplicationContext estende questa interfaccia.

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

CAPITOLO 1. IL CONTENITORE IOC

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.

1.11 Usare i le .properties


Abbiamo visto come sia possibile costruire il le di congurazione di Spring in modo tale che contestualmente alla denizione di un bean si possano anche denire i valori dei suoi attributi. Tuttavia, scrivere esplicitamente i valori all'interno del le .xml pu risultare poco comodo. Risulta sicuramente pi pratico raccogliere i valori in un le di properties. Un le di properties un semplice le di testo, con estensione .properties, e che strutturato come una mappa:

chiave = valore
Quello che segue ne un esempio:

url = jdbc:mysql://localhost:3306/mydb driver = com.mysql.jdbc.Driver username = admin password = admin x = 1 y = 3


Tali le sono molto usati, per esempio, per passare i dati di congurazione di un db, oppure per gestire la localizzazione di un sito (un le di properties per ogni lingua, le chiavi sono sempre le stesse, ma cambiano i valori), ecc. Per catturare tali valori all'interno del le spring-cong.xml si usa la seguente sintassi:

CAPITOLO 1. IL CONTENITORE IOC

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 Uso delle annotation


A partire dalla versione 1.5 di Java, sono supportate ucialmente le annotation. Spring permette di realizzare la propria congurazione anche tramite questo strumento.

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.

public class SimpleMovieLister {

CAPITOLO 1. IL CONTENITORE IOC

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; }

1.12.3 @PostConstruct e @PreDestroy


Queste possono essere utilizzate per marcare i metodi di inizializzazione e di distruzione di un bean, come nell'esempio che segue:

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.

1.13 Rilevazione automatica dei componenti


Spring introduce, gi a partire dalla versione 2.0, in concetto di stereotipo con l'annotazione @Repository usata per caratterizzare componendi di accesso ai dati (dao). Con Spring 2.5 vengono aggiunte due nuove annotazioni, @Service e @Controller, per completare la classica architettura a tre livelli (accesso ai dati, servizi di business, controller dell'interfaccia). Inoltre introduce anche il generico @Component che gli altri stereotipi estendono. Queste annotazioni possono essere usate in combinazione con un'altra funzionalit di Spring: la rilevazione automatica di componenti a partire dal classpath.

CAPITOLO 1. IL CONTENITORE IOC

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.

1.13.1 Uso dei ltri per uno scanning customizzato


Di default, le classi annotate come @Component, @Repository, @Service, @Controller o con delle annotazioni personalizzate sono i soli componenti rilevati. E' possibile modicare ed estendere questo comportamento semplicemente applicando dei ltri customizzati. Tali ltri possono essere inclusivi (prendi tutti i componenti che...) o esclusivi (prendi tutti i componenti tranne...) e devono essere dichiarati come nodi dell'elemento component-scan. Ogni ltro richiede il tipo e un'espressione. La tabella 1.2 riassume le opzioni dei ltri.

Tipo di ltro Espressione d'esempio


annotation assignable org.example.SomeAnnotation org.example.SomeClass

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.

<beans> <context:component-scan base-package="org.example"> <context:include-filter type="regex" expression=".*Stub.*Repository"/>

CAPITOLO 1. IL CONTENITORE IOC

18

<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> </beans>

1.14 Internazionalizzazione con MessageSource


L'interfaccia ApplicationContext estende un interfaccia chiamata MessageSource e quindi provvede anche alla funzionalit di internazionalizzazione (i18n). I metodi deniti in questa interfaccia includono: String getMessage(String code, Object[] args, String default, Locale loc): Il metodo base utilizzato per recuperare un messaggio dal MessageSource. Se non viene trovato alcun messaggio, viene usato quello di default. String getMessage(String code, Object[] args, Locale loc): Fondamentalmente la stessa cosa del metodo precedente, ma con una dierenza: non specicato alcun messaggio di default; se il messaggio non viene trovato, viene sollevata una NoSuchMessageException. String getMessage(MessageSourceResolvable resolvable, Locale locale): Tutte le propriet utilizzate nei metodi precedenti vengono wrappate in una classe chiamata MessageSourceResolvable. Quando viene caricato un ApplicationContext, viene automaticamente cercato un bean MessageSource denito nel contesto. Il bean deve avere il nome messageSource. Se tale bean viene trovato, tutte le chiamate dei metodi sopra elencati sono delegati al messageSource. Se invece non viene trovato, l'ApplicationContext cerca un genitore che contenga un bean con lo stesso nome. Se lo trova lo usa come un MessageSource, altrimenti viene istanziato un DelegatingMessageSource vuoto, in grado di accettare chiamate ai metodi deniti sopra. L'uso del MessageSource evidenziato dal seguente esempio: messaggi.properties

benvenuto = Ciao Mondo!!!


spring-cong.xml

<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

CAPITOLO 1. IL CONTENITORE IOC

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

benvenuto = Ciao Mondo!!!


messaggi_en_GB.properties

benvenuto = Hello World!!!


Main.java

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:

Ciao Mondo!!! Hello World!!!


La dierenza la fa l'ultimo parametro della funzione getMessage(...). Nel primo caso, non essendo specicato nessun valore, viene cercato in automatico il le messaggi (senza sussi). Nel secondo caso viene specicata la localizzazione inglese (Locale.UK), e quindi viene cercato lo stesso le ma con il susso per la lingue inglese (_en_GB).

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.

2.2 L'interfaccia Validator


Spring utilizza l'interfaccia Validator per consentire l'implementazione di meccanismi di validazione. Ogni validatore che si vuole costruire deve implementare tale interfaccia. Questi sono i metodi esposti: public boolean supports(Class clazz); denisce su quali classi pu agire il validatore. O meglio, data una classe passata come parametro restituisce true se il validatore in questione stato scritto per poter validare quella classe. public void validate(Object obj, Errors e); il metodo che eettua la validazione sull'oggetto obj. Questo metodo fa anche uso dell'interfaccia Errors. Tutti gli errori rilevati durante l'esecuzione del metodo validate(...) verranno registrati all'interno di un apposito oggetto di tipo Errors. Vediamo il funzionamento di ci di cui si parlato nora con un esempio. Immaginiamo di avere un oggetto Indirizzo che racchiude tutte le informazioni per identicare univocamente un indirizzo nel mondo: 20

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;

// metodi set() e get() ...

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:

prefissoVia obbligatorio nazione obbligatorio


Vediamo ora qualche osservazione di approfondimento.

CAPITOLO 2. VALIDAZIONE

23

2.2.1 Gestione di controlli pi complessi


Nel validatore sono stati eettuati dei controlli estremamente semplici, ma in eetti di limitata utilit. Sul cap per esempio sarebbe opportuno non solo il controllo se null o no, ma anche una verica se il valore immesso, nel caso sia diverso dal valore nullo, sia eettivamente plausibile (un cap formato SEMPRE da cinque cifre). Il questo caso infatti necessario accedere direttamente al campo, eettuare un controllo a mano (utilizzando per esempio le espressioni regolari, contenute nel package java.util.regex ) ed eventualmente riutare l'errore. Per riutare l'errore si utilizza un metodo di Errors : rejectValue(...). Questo metodo pu ricevere vari parametri, tra cui l'attributo dell'oggetto che non ha passato la validazione, il codice d'errore e il messaggio di default. Il controllo sul cap quindi potrebbe essere fatto in questo modo:

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.

2.2.2 Gestione dei messaggi d'errore tramite messageSource


La gestione dei messaggi d'errore da presentare all'utente pu essere gestita in maniera pi elegante (e soprattutto localizzata) sfruttando i messageSource (visti nel paragrafo 1.14). Possiamo infatti considerare il seguente le di properties: validazione.properties

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.

2.3 Validazione di una rete di bean


Abbiamo visto come sia possibile validare un bean tramite un validatore costruito ad hoc. Consideriamo ora di voler validare il seguente bean: Persona.java

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"); }

// valido l'indirizzo e.pushNestedPath("indirizzo"); ValidationUtils.invokeValidator(new IndirizzoValidator(), p.getIndirizzo(), e); e.popNestedPath();

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

Figura 2.2: Gerarchia di oggetti

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

// validazione di c e.pushNestedPath("c"); ValidationUtils.invokeValidator(new CValidator(), a.getC(), e); e.popNestedPath();

La chiamata e.pushNestedPath(d) mi permette di scendere di un ulteriore livello nella gerarchia e di posizionarmi su d.

Capitolo 3

Spring Expression Language (SpEL)


3.1 Introduzione
Spring Expression Language (o SpEL) un potente linguaggio per le espressioni che supporta la possibilit di interrogare e manipolare reti di oggetti a runtime. La sintassi del linguaggio simile allo Unied EL, ma ore alcune funzionalit in pi, invocazione di metodi e funzionalit sulle stringhe. Tale linguaggio stato creato per fornire alla comunit di Spring un singolo linguaggio per le espressioni che pu essere usato fra tutti i prodotti Spring. Non essendo direttamente legato a Spring, un linguaggio che pu essere usato in maniera indipendente.

3.2 Panoramica sulle caratteristiche


SpEL supporta le seguenti funzionalit: espressioni letterali operatori booleani e relazionali espressioni regolari espressioni per le classi accesso agli attributi, array, liste e mappe invocazione di metodi assegnazioni chiamate dei costruttori operatori ternari variabili funzioni denite dall'utente proiezione delle collezioni selezione delle collezioni espressioni per i template 28

CAPITOLO 3. SPRING EXPRESSION LANGUAGE (SPEL)

29

3.3 Valutazione delle espressioni usando l'interfaccia Expression di Spring


Il codice che segue introduce le API di SpEL per valutare la semplice stringa Ciao Mondo.

ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Ciao Mondo'"); String message = (String) exp.getValue(); System.out.println("Visualizzazione stringa: "+message);
L'output prodotto :

Visualizzazione stringa: Ciao Mondo


L'interfaccia ExpressionParser responsabile del parsing di una stringa espressione. In questo esempio la stringa espressione una stringa letterale delimitata da apici. L'interfaccia Expression responsabile della valutazione della stringa espressione. Ci sono due eccezioni che possono essere sollevate, ParseException ed EvaluationException, chiamando rispettivamente parser.parseExpression() e exp.getValue(). SpEL supporta la chiamata di metodi, l'accesso agli attributi e la chiamata dei costruttori. Il seguente esempio mostra la chiamata del metodo concat() delle stringhe:

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 :

Esempio concatenazione: Ciao Mondo!


Nell'esempio che segue si mostra come sia possibile l'accesso ad un attributo di un bean, in questo caso l'attributo bytes di una stringa:

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 :

Accesso al campo bytes: 67 105 97 111 32 77 111 110 100 111


Naturalmente possibile accedere ad attributi annidati utilizzando l'operatore punto (.):

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);

CAPITOLO 3. SPRING EXPRESSION LANGUAGE (SPEL) L'output prodotto :

30

Lunghezza del campo bytes: 10


Con il prossimo esempio si mostra come sia possibile invocare un costruttore:

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 :

Creata la stringa: CIAO MONDO


Notare l'uso del metodo pubblico generico <T> T getValue(Class<T> desiredResultType). Usando questo metodo si rimuove la necessit di castare il valore dell'espressione al tipo del risultato desiderato. Viene sollevata una EvaluationException se il valore non pu essere castato al tipo T. L'uso pi comune di SpEL quello di provvedere a valutare un'espressione su una specica istanza di un oggetto. Nell'esempio che segue recuperiamo il valore della propriet nome da un'istanza della classe Persona. Vediamo un esempio:

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 :

Il nome del bean p : Simone


Nell'ultima istruzione, il valore della variabile String nome viene settata a Simone. La classe StandardEvaluationContext consente di specicare di quale oggetto deve essere valutato l'attributo nome. Per specicarglielo si usata la funzione setRootObject(). E' possibile riusare la stessa espressione pi volte settando di volta in volta un nuovo rootObject nell'EvaluationContext. Le espressioni sono valutate utilizzando le reection. Come esempio introduttivo nale, vediamo l'uso di un operatore booleano usando l'oggetto Persona usato nell'esempio precedente.

Persona p = new Persona(); p.setNome("Simone"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("nome == 'Simone'");

CAPITOLO 3. SPRING EXPRESSION LANGUAGE (SPEL)

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 :

Valutazione dell'espressione booleana: true

3.3.1 L'interfaccia EvaluationContext


L'interfaccia EvaluationContext usata per valutare espressioni per risolvere propriet, metodi, campi ed aiutare ad eettuare conversione di tipi. L'implementazione StandardEvaluationContext usa le reection per manipolare gli oggetti, nascondendo i metodi, i campi e i costruttori delle istanze del package java.lang.reect per incrementare le prestazioni. L'oggetto StandartEvaluationContext dove si specica l'oggetto radice da valutare attraverso il metodo setRootObject o passando l'oggetto root al costruttore. E' possibile inoltre specicare variabili e funzioni che possono essere usate nelle espressioni usando i metodi setVariable e registerFunction.

3.4 Supporto delle espressioni alla denizione dei bean


SpEL pu essere usato con l'XML o con le annotazioni per la denizione dei bean. In entrambi i casi la sintassi per denire le espressioni #{ <stringa espressione> }.

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>

CAPITOLO 3. SPRING EXPRESSION LANGUAGE (SPEL)

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; }

3.5 Reference del linguaggio


Per una descrizione esaustiva delle possibilit sintattiche di SpEL consultare la sezione 6.5 delle reference uciali di Spring.

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.

4.1.1 Concetti dell'AOP


Cominciamo con il denire alcuni concetti fondamentali e un po' di terminologia. Questi termini non sono specici di Spring. Aspect : una modularizzazione di un concetto che attraversa pi classi. In Spring AOP gli aspetti sono implementati usando le classi tradizionali o con classi tradizionali annotate con l'annotazione @Aspect. Join Point : un punto (un momento) durante l'esecuzione di un programma, come potrebbe essere l'esecuzione di un metodo o la gestione di un eccezione, l'accesso ad una variabile, ecc. In Spring AOP un join point rappresenta sempre l'esecuzione di un metodo. Advice : azione presa da un aspect in un particolare join point. Ci sono dierenti tipi di advice, come around, before e after. Molti framework AOP, incluso Spring, modellano un advice come un intercettatore, mantenendo una catena di intercettatori attorno ad un join point. Pointcut : un predicato che matcha con i join point. Un advice associato con un'espressione di un pointcut e viene eseguito ogni volta che il join point e il pointcut matchano 33

CAPITOLO 4. SPRING AOP

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.

4.1.2 Capacit e obiettivi di Spring AOP


Spring AOP implementato in Java e quindi non c' bisogno di alcun processo di compilazione. E' possibile utilizzarlo all'interno di container per Java EE e application server. Spring AOP attualmente supporta solo join point sull'esecuzione di metodi, ovvero gli eventi intercettati sono solo queli che coincidono con la chiamata di metodi. L'intercettazione dell'accesso ai campi non stata implementata, nonostante il supporto per tale funzionalit potrebbe essere aggiunto senza danni per il core delle API di Spring AOP. Se si ha necessit di queste funzionalit, valutare la possibilit di usare un linguaggio come AspectJ.

CAPITOLO 4. SPRING AOP

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.

4.1.3 AOP Proxies


Spring AOP di default utilizza i proxy dinamici standard di J2SE per realizzare i proxy AOP. Questo permette l'uso dei proxy sulle interfacce. Vengono inoltre utilizzati i proxy CGLIB. Questo necessario per eettuare proxy sulle classi invece che sulle interfaccie. CGLIB usato di default su tutti gli oggetti di business che non implementano interfacce. E' buona norma programmare pensando prima di tutto alle interfacce, piuttosto che alle classi. Capire che Spring AOP basato su proxy importante.

4.2 AOP con @AspectJ


parte della release 5. Spring 2.0 interpreta le stesse annotazioni come AspectJ 5, usando una libreria a supporto di AspectJ per il parsing e il matching dei pointcut. Non c' alcuna dipendenza dal compilatore di AspectJ.

@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

4.2.1 Abilitare il supporto a @AspectJ


Per utilizzare gli aspetti @AspectJ, necessario abilitare in Spring il supporto per la loro congurazione, cosi come il supporto all'autoproxying. Per autoproxying si intende il fatto che Spring determina se un bean un target per uno o pi aspetti, poich deve essere automaticamente generato un proxy per quel bean, che intercetter le invocazioni dei metodi, assicurandosi che gli advice opportuni vengano eseguiti. Il supporto @AspectJ viene attivato includendo il seguente elemento nel le di congurazione:

<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:

CAPITOLO 4. SPRING AOP

36

<bean class="org.springframework.aop.aspectj. annotation.AnnotationAwareAspectJAutoProxyCreator"/>


Va segnalato inoltre che necessario includere alcune librerie AspectJ nel classpath dell'applicazione: aspectjweaver.jar e aspectjrt.jar. Queste librerie sono disponibili nella cartella lib nella directory principale d'installazione di AspectJ.

4.2.2 Dichiarare un aspetto


Con il supporto @AspectJ abilitato, ogni bean denito all'interno del contesto dell'applicazione con una classe che un aspetto (ovvero ha l'annotazione @Aspect) sar automaticamente rilevata da Spring e usata per congurare Spring AOP. L'esempio che segue mostra la denizione (minimale) per un aspetto:

<bean id="logger" class="esempiospring.LoggerWithAnnotation"/>


Come si vede, dichiarato come qualunque altro bean. La classe corrispondente denita come segue:

@Aspect public class LoggerWithAnnotation { //... }


Un aspetto pu avere metodi e attributi come qualsiasi altra classe. Potrebbe contenere inoltre pointcut, advice e introduction. In Spring AOP non possibile che un aspetto faccia da target per un advice da un altro aspetto. Una classe annotata con @Aspect esclusa dal meccanismo di autoproxy.

4.2.3 Dichiarare un pointcut


Ricordiamo che i pointcuts determinano i join pojnts di interesse e quindi ci consentono di controllare quando un advice deve essere eseguito. Spring AOP supporta solo l'intercettazione dei join points che coincidono con l'esecuzione dei metodi dei beans (e solo di quelli gestiti dal contenitore Spring). La dichiarazione di un pointcut composta da due parti: una rma comprendente il nome e alcuni parametri, e un'espressione che determina esattamente a quale esecuzione di quali metodi siamo interessati. La rma di un pointcut prevede la normale denizione di un metodo e l'espressione del pointcut indicata usando l'annotazione @Pointcut (tale metodo deve avere il tipo di ritorno void). Supponiamo di avere il seguente Dao (il quale, poich serve solo a titolo d'esempio, eettua delle nte query) con relativa interfaccia IDao:

// interfaccia public interface IDao { public void salva(); public void recupera(); } // classe dao public class Dao implements IDao {

CAPITOLO 4. SPRING AOP

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.

4.2.3.1 Indicatori supportati per i Pointcut


Spring AOP supporta i seguenti indicatori AspectJ per i pointcut (Pointcut Designators, o PCD) per la denizione delle espressioni dei pointcut: execution : per intercettare l'esecuzione di un metodo; il principale indicatore che si utilizza in Spring AOP. within : indica l'insieme dei join point corrispondenti all'esecuzione di metodi che siano parte di un tipo specicato. this : indica l'insieme dei join point corrispondenti all'esecuzione di un metodo di una particolare istanza di un certo tipo specicato. target : indica l'insieme dei join point nei quali l'oggetto target un'istanza di un certo tipo specicato.

CAPITOLO 4. SPRING AOP

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.

4.2.3.2 Combinare le espressioni dei pointcut


Le espressioni dei pointcut possono essere combinate usando gli operatori '&&', '||' e ' !'. E' inoltre possibile riferirsi all'espressione di un pointcut attraverso il suo nome. L'esempio che segue mostra tre pointcut:

// 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() {}

CAPITOLO 4. SPRING AOP

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.

4.2.3.3 Condividere le denizioni dei pointcut in comune


Molto spesso nelle applicazioni enterprise un pointcut non legato ad un singolo aspetto, ma pu essere condiviso tra vari aspetti diversi. Pu essere utile quindi denirli all'interno di un'unica classe. Sar possibile poi potersi riferire ad essi in qualunque momento e da qualunque punto dell'applicazione. Per esempio:

@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() {} /**

CAPITOLO 4. SPRING AOP

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() {}

4.2.3.4 Alcuni esempi


Gli utenti di Spring AOP utilizzano molto spesso l'indicatore execution. Tale indicatore ha la seguente forma:

execution( modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)


Tutte le parti tranne il tipo di ritorno (re-type-pattern) sono opzionali. Il tipo di ritorno determina quale dev'essere il tipo di ritorno dei metodi che si intende intercettare. Molto spesso si usa l'asterisco (*) per indicare che va bene qualunque tipo di ritorno. Quando ne viene specicato uno, il pointcut matcher solo con tutti i metodi che, oltre a rispettare il resto dell'espressione, ritornano il tipo indicato. Il name-pattern matcha con il nome del metodo. E' possibile usare l'asterisco in qualunque parte del nome. Ad esempio:

"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

CAPITOLO 4. SPRING AOP

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)

CAPITOLO 4. SPRING AOP

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 Dichiarare un advice


Un advice viene associato con l'espressione di un pointcut, e parte prima, dopo o durante l'esecuzione del metodo matchato dal pointcut.

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() { // ... } }

CAPITOLO 4. SPRING AOP

43

4.2.4.2 After returning


Un advice di questo tipo parte quando il metodo intercettato return un valore (termina senza errori). Viene dichiarato utilizzando l'annotazione @AfterReturning:

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 ).

4.2.4.3 After throwing


Questo tipo di advice parte quando matcha con un metodo che termina sollevando un'eccezione. E' dichiarato utilizzando l'annotazione @AfterThrowing:

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() { // ... } }

CAPITOLO 4. SPRING AOP

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 ).

4.2.4.4 After (nally)


Questo advice parte quando un metodo termina, in qualunque modo. Viene dichiarato con l'annotazione @After. Tale advice dev'essere preparato a gestire sia la situazione di ritorno normale, sia le eventuali condizioni di eccezione. Tipicamente viene utilizzato per il rilascio delle risorse, ecc.

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.

CAPITOLO 4. SPRING AOP

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.

4.2.4.6 Gestione dei parametri


parametro di tipo org.aspectj.lang.JoinPoint (notare che l'around advice richiede la dichiarazione di un primo parametro di tipo ProceedingJoinPoint che una sottoclasse di JoinPoint ). L'interfaccia JoinPoint espone un certo numero di metodi utili come getArgs() (ritorna gli argomenti del metodo), getThis() (ritorna l'oggetto proxy), getTarget() (ritorna l'oggetto target), getSignature() (ritorna una descrizione del metodo che stato intercettato) e toString() (stampa una descrizione del metodo intercettato). Consultare il Javadocs per maggiori dettagli.

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-

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" + "args(account,..)") public void validateAccount(Account account) { // ... }

CAPITOLO 4. SPRING AOP

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:

CAPITOLO 4. SPRING AOP

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:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

4.2.6 Modello di instanziazione di un aspetto


Di default c' una singola istanza di ogni aspetto nel contesto dell'applicazione. In AspectJ questa caratteristica viene chiamata modello singleton di instanziazione. E' possibile, per, denire aspetti con cicli di vita diversi. Spring supporta i modelli perthis e pertarget di AspectJ (percow, percowbelow e pertypewithin attualmente non sono supportati). Un aspetto perthis viene dichiarato specicato la clausola perthis nell'annotazione @Aspect. Vediamo un esempio:

@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() {

CAPITOLO 4. SPRING AOP

48

System.out.println("Salvo un dato sul db");

public void recupera() { System.out.println("Ho recuperato un dato dal db"); }

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 === --> <!-- ============================== -->

CAPITOLO 4. SPRING 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.

4.3 AOP con XML


Se si impossibilitati all'uso di Java 5 o semplicemente si preferisce lavorare con l'XML, Spring supporta l'uso dell'AOP anche tramite il le di congurazione. Sono supportate le stesse espressioni per i pointcuts e le stesse tipologie di advice viste nel paragrafo 4.2.

CAPITOLO 4. SPRING AOP

50

4.3.1 Dichiarare un aspetto


Utilizzando l'XML, un aspetto viene dichiarato usando il tag <aop:aspect>, e il bean che lo denisce referenziato usando l'attributo ref. Riprendendo l'esempio visto nel paragrafo 4.2.7, l'aspetto implementato dal seguente bean:

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"/>

4.3.2 Dichiarare un pointcut


Un pointcut pu essere dichiarato all'interno dell'elemento <aop:cong>, in modo che sia disponibile a diversi aspetti e advisors. Un pointcut che rappresenta l'esecuzione di un qualunque metodo di esempiospring.Dao scritto come segue:

<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:

CAPITOLO 4. SPRING AOP

51

<aop:config> <aop:aspect id="myAspect" ref="daoAspect"> <aop:pointcut id="inDao" expression="execution(* esempiospring.Dao.*(..))"/> ... </aop:aspect> </aop:config>

4.3.3 Dichiarare un advice


I tipi di advice supportati sono gli stessi visti in precedenza.

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>.

4.3.3.2 After returning


Questo tipo di advice denito in questo modo:

<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>


E' possibile naturalmente catturare il valore di ritorno per usarlo all'interno dell'advice. La sintassi la seguente:

<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> ... </aop:aspect>

CAPITOLO 4. SPRING AOP

52

4.3.3.3 After throwing


Questo tipo di advice denito nel seguente modo:

<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" method="doRecoveryActions"/> ... </aop:aspect>


E' possibile naturalmente catturare l'eccezione per usarla all'interno dell'advice. La sintassi la seguente:

<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" throwing="dataAccessEx" method="doRecoveryActions"/> ... </aop:aspect>

4.3.3.4 After (nally)


Questo tipo di advice denito nel seguente modo:

<aop:aspect id="mioAspect" ref="daoAspect"> <aop:after pointcut-ref="daoService" method="afterDaoOperationAdvise"/> ... </aop:aspect>

4.3.3.5 Around
Questo tipo di advice denito nel modo seguente:

<aop:aspect id="aroundExample" ref="aBean"> <aop:around pointcut-ref="businessService" method="doBasicProfiling"/> ... </aop:aspect>

4.3.3.6 Gestione dei parametri


La dichiarazione basata su XML supporta le stesse funzionalit di quella basata su @AspectJ. Se si vuole esplicitamente specicare i nomi degli argomenti per i metodi advice si deve utilizzare l'attributo arg-names nel nodo dell'advice, che trattato nello stesso modo dell'attributo argNames per le annotazioni. Ad esempio:

<aop:before pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)" method="audit" arg-names="auditable"/>


L'attributo arg-names accetta una lista di nomi di parametri separati da virgole.

CAPITOLO 4. SPRING AOP

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.5 Modello di instanziazione di un aspetto


L'unico modello di instanziazione nella denizione degli aspetti tramite XML il modello singleton. Altri modelli potrebbero essere supportati in release future.

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>

CAPITOLO 4. SPRING AOP

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>

CAPITOLO 4. SPRING AOP

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

4.4 Scegliere lo strumento per gestire l'AOP


Una volta che si deciso che un aspetto l'approccio migliore per implementare un certo requisito dell'applicazione, come decidere se utilizzare Spring AOP o AspectJ, e tra lo stile basato sul linguaggio, sulle annotazioni @AspectJ o lo stile XML? Queste decisioni sono inuenzate da un certo numero di fattori, tra cui i requisiti dell'applicazione, gli strumenti di sviluppo e la familiarit del team con AOP.

4.4.1 Spring AOP o AspectJ?


Usa la cosa pi semplice che pu funzionare! Spring AOP pi semplice rispetto ad AspectJ poich non richiede di introdurre il compilatore o il weaver di AspectJ nel processo di sviluppo. Se si ha bisogno di intercettare solo l'esecuzione delle operazioni dei beans di Spring, allora Spring AOP la scelta migliore.

CAPITOLO 4. SPRING AOP

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.

4.4.2 @AspectJ o XML?


Se si scelto di usare Spring AOP, si pu scegliere tra i due approcci visti nei paragra 4.2 e 4.3. Naturalmente se non si ha a disposizione almeno la Java 5, la scelta obbligata. Lo stile XML potrebbe essere pi familiare a chi gi sviluppa con Spring. Quando si usa AOP come tool per congurare i servizi di tipo enterprise, allora l'XML pu essere una buona scelta. Con l'XML inoltre molto semplice visualizzare quali aspetti sono presenti nel sistema. Lo stile XML ha due svantaggi. Il primo svantaggio che non incapsula completamente l'implementazione degli aspetti in un singolo punto. Infatti la congurazione XML non suciente, poich l'aspetto va comunque dichiarato in una classe con i relativi metodi advice. Quando si usa @AspectJ c' un singolo modulo, l'aspetto, che incapsula tutte le informazioni. Il secondo svantaggio sta nel fatto che lo stile XML un po' pi limitato quanto ad espressivit rispetto allo stile @AspectJ: supportato come modello di instanziazione solo quello per gli aspetti singleton e non possibile combinare i nomi dei pointcuts dichiarati con l'XML. Per esempio, nello stile @AspectJ si pu scrivere qualcosa del tipo:

@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:

<aop:pointcut id="propertyAccess" expression="execution(* get*())"/> <aop:pointcut id="operationReturningAnAccount" expression="execution(org.xyz.Account+ *(..))"/>


Non possibile per dichiarare il pointcut accountPropertyAccess combinando queste denizioni.

CAPITOLO 4. SPRING AOP

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.

4.5 Il meccanismo dei proxy


Spring AOP usa i proxies dinamici JDK o CGLIB per creare i proxy dato un oggetto target (i proxy JDK sono da preferire, quando si ha possibilit di scelta). Vengono utilizzati i primi quando l'oggetto target implementa un'interfaccia. In caso contrario si utilizzano i CGLIB. Nel caso in cui si voglia forzare l'uso dei CGLIB, possibile farlo (per esempio nel caso in cui si vogliano intercettare tutti i metodi di un oggetto target, e non solo quelli che implementano l'interfaccia). Ci sono alcuni punti di cui tenere conto sui proxy CGLIB: i metodi nal non possono essere intercettati. necessario avere i binari CGLIB 2 all'interno del proprio classpath, altrimenti Spring solleva dei warning. il costruttore dell'oggetto intercettato verr chiamato due volte. Questo a causa del meccanismo di funzionamento di questo tipo di proxy. Per forzare l'uso dei proxies CGLIB bisogna settare a true il valore dell'attributo proxytarget-class nel nodo <aop:cong>:

<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>


Per forzare l'uso dei proxy CGLIB quando si usa il supporto autoproxy @AspectJ necessario settare a true l'attributo proxy-target-class del nodo <aop:aspectj-autoproxy>:

<aop:aspectj-autoproxy proxy-target-class="true"/>

4.5.1 Capire i proxy AOP


Come gi ribadito pi volte, Spring AOP basato su proxy. E' di fondamentale importanza capire il meccanismo dei proxy poich se da una parte facilitano molto l'uso della AOP, dall'altra possono presentare situazioni di cui poi risulta dicile capire il funzionamento. Supponiamo, per esempio, di avere una classe BankManagement, che implementa un'interfaccia IBankManagement con una serie di metodi pubblici per la gestione di operazioni bancarie:

public interface IBankManagement { public void processaPagamento(); public void checkRatingCredito(); }

CAPITOLO 4. SPRING AOP

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 ... }

CAPITOLO 4. SPRING AOP

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-

CAPITOLO 4. SPRING AOP

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!

4.6 Creazione programmatica dei proxy @AspectJ


Oltre all'uso di <aop:cong> o <aop:aspectj-autoproxy> possibile creare dei proxies su degli oggetti target in maniera programmatica. La classe che pu essere usata per farlo org.springframework.aop.aspectj.annotation.AspectJProxyFactory. L'uso di questa classe molto semplice ed illustrato nell'esempio che segue:

// 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

Gestione delle transazioni


Il supporto per la gestione delle transazioni uno di punti di forza di Spring. Il framework infatti fornisce un'astrazione per la gestione delle transazioni che porta i seguenti vantaggi: Modello di programmazione adatto ad API dierenti, come Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), Java Data Objects (JDO). Supporto per la gestione dichiarativa delle transazioni. API per la gestione programmatica delle transazioni pi semplici rispetto a JTA. Integrazione eccellente con le astrazioni di Spring per l'accesso ai dati.

5.1 Vantaggi del supporto di Spring alla gestione delle transazioni


Tipicamente gli sviluppatori Java EE hanno due scelte per la gestione delle transazioni: globali o locali, entrambe le quali hanno alcune limitazioni.

5.1.1 Transazioni globali


Le transazioni globali consentono di lavorare con pi risorse transazionali, tipicamente database relazionali e code di messaggi. Gli application server gestiscono le transazioni globali tramite JTA che un'API piuttosto articolata da usare. Inoltre, un UserTransaction JTA normalmente ha bisogno di essere connesso ad una risorsa JNDI, il che vuol dire che bisogna prima gestire questo aspetto e poi JTA. Questo limita il riuso del codice dell'applicazione, anche perch JTA disponibile solo in ambiente web server. In precedenza il modo preferito per gestire le transazioni era attraverso gli EJB CMT (Cointainer Managed Transaction). EJB CMT una forma di gestione dichiarativa, che riduce la quantit di codice Java da scrivere, ma che comunque connessa a JTA e al web server in uso.

5.1.2 Transazioni locali


Le transazioni locali sono speciche per una risorsa, come per esempio le transazioni associate ad una connessione JDBC. Sono pi semplici da usare, ma hanno uno svantaggio signicativo: non possono lavorare su pi risorse transazionali. Per esempio, il codice che gestisce le transazioni usando una connessione JDBC non pu lavorare con una transazione globale JTA. Un altro difetto che il loro modello di programmazione piuttosto invasivo. 62

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

63

5.1.3 Modello di programmazione di Spring


Spring risolve gli svantaggi di entrambi gli approcci, fornendo agli sviluppatori un modello di programmazione adatto ad ogni ambiente di lavoro. E' possibile scrivere il codice una volta e beneciare di qualunque strategia in qualunque ambiente.

5.2 Comprendere l'astrazione delle transazioni di Spring


La chiave alla base dell'astrazione delle transazioni la nozione di strategia delle transazioni. Una strategia denita dall'interfaccia org.springframework.transaction.PlatformTransactionManager :

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:

CAPITOLO 5. GESTIONE 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:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>


Se si usa JTA all'interno di un container Java EE, allora si pu usare il DataSource del container, ottenuto tramite JNDI, in congiunzione con il JtaTransactionManager di Spring. Il codice potrebbe essere 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 -->

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

65

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> </beans>


Il JtaTransactionManager non necessita di sapere nulla sul DataSource, o su altre speciche risorse, perch usa l'infrastruttura del container per la gestione delle transazioni globali. Questa denizione del dataSource utilizza il tag <jndi-lookup/> dal namespace jee. Per maggiori informazioni guardare l'appendice ??. E' possibile utilizzare molto facilmente anche le transazioni Hibernate, come mostrato nel seguente esempio. In questo caso va denito un LocalSessionFactoryBean, che l'applicazione user per ottenere istanze di Session di Hibernate. La denizione del bean DataSource simile a quella JDBC vista poco sopra e quindi nell'esempio non sar mostrata. Il bean txManager in questo caso un HibernateTransactionManager. Cosi come il DataSourceTransactionManager necessita di un riferimento al DataSource, l'HibernateTransactionManager necessit di un riferimento al SessionFactory.

<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.

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>


In tutti questi casi, il codice dell'applicazione non necessita di cambiamenti. E' possibile cambiare il modo in cui le transazioni sono gestite cambiando solo la congurazione.

5.3 Gestione dichiarativa delle transazioni


Sono molti tra gli sviluppatori Spring ad usare la gestione dichiarativa delle transazioni. Questa opzione la meno invasiva.

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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.1 Comprendere l'approccio dichiarativo


Non suciente dire che bisogna annotare una classe come @Transactional, aggiungere la linea <tx:annotation-driven/> alla congurazione, e aspettarsi che si capisca come fa tutto a funzionare. Questa sezione spiegher in che modo l'approccio dichiarativo permette a Spring di funzionare. Alla base del meccanismo c' il modulo AOP.

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); }

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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>

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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:

<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>


Questa congurazione vista nora utilizzata da Spring per creare un proxy transazionale attorno all'oggetto creato dalla denizione del bean fooService.

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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:

<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/> </tx:attributes> </tx:advice>


Ogni eccezione tranne quelle di tipo InstrumentNotFoundException provocheranno un rollback della transazione che le ha sollevate.

5.3.4 Congurazione dierenti per bean dierenti


Consideriamo uno scenario dove si ha un certo numero di oggetti nello strato dei servizi e si vogliono applicare regole transazionali dierenti tra i vari oggetti in questione. E' possibile farlo denendo dierenti <aop:advisor/> con dierenti pointcut.

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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>

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

71

<tx:advice id="noTxAdvice"> <tx:attributes> <tx:method name="*" propagation="NEVER"/> </tx:attributes> </tx:advice>

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?

Exception(s) che determinano rollback Exception(s) che non determinano rollback


Tabella 5.1: opzioni di <tx:method/>

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);

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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.

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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

Tabella 5.2: opzioni di <tx:annotation-driven/>

5.3.6.1 Congurazione di @Transactional


Le impostazioni di default dell'annotation @Transactional sono le seguenti: Propagation settata PROPAGATON_REQUIRED Isolation settato ISOLATION_DEFAUT Transaction settato come read/write Timeout settato a quello di default del sistema transazionale in uso, oppure non settato se non supportato. Tutte le transazioni che sollevano una RuntimeException vengono rollbackate, e tutte le eccezioni checked no. In tabella 5.3 sono riportate tutti i settaggi dei vari attributi di @Transactional.

5.3.7 Propagazione delle transazioni


5.3.7.1 Required
Quando propagation settato come PROPAGATION_REQUIRED, viene creato uno scope logico di transazione per ogni metodo a cui questo settaggio applicato. Ognuno di questi scope determina lo status rollback-only in maniera individuale, con lo scope transazionale

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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.

5.3.8 Advise per le transazioni


Supponiamo di voler eseguire sia un advice transazionale sia un advice che gestisca un timer per valutare i tempi delle transazioni. Come si pu fare utilizzando l'elemento <tx:annotationdriven/>? Quando invochiamo il metodo updateFoo(Foo) noi vogliamo vedere la seguente sequenza di azioni: 1. Viene eseguito l'aspetto congurato per gestire il timer.

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.

<?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"

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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.

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

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>

5.4 Gestione programmatica delle transazioni


5.4.1 Usare il PlatformTransactionManager
Per gestire le transazioni possibile usare il org.springframework.transaction.PlatformTransactionManager direttamente. Bisogna semplicemente passare l'implementazione del PlatformTransactionManager al bean che lo deve usare, tramite un riferimento. Poi, suciente usare il TransactionDenition e il TransactionStatus per iniziare la transazione, per il rollback e per il commit.

DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

CAPITOLO 5. GESTIONE DELLE TRANSAZIONI

78

TransactionStatus status = txManager.getTransaction(def); try { // fai qualcosa txManager.commit(status); } catch (MyException ex) { txManager.rollback(status); throw ex; }

5.5 Gestione dichiarativa Vs gestione programmatica


La scelta cade sull'uno o sull'altro approccio principalmente in base al numero di metodi transazionali della nostra applicazioni. Se sono pochi, pu essere una buona idea utilizzare la gestione programmatica. Al contrario, se le transazioni sono molte, l'approccio dichiarativo senz'altro pi semplice e comodo.

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

You might also like