Spring AOP
Vediamo come l’AOP (Aspect Oriented Programming) può essere utilizzato nei nostri progetti, attraverso Spring AOP.
AOP for dummies
AOP (Aspect Oriented Programming) è un paradigma di sviluppo che introduce il concetto di “aspetto” nello sviluppo software. Possiamo identificare l’aspetto con qualcosa di trasversale alle nostri componenti software, qualcosa che comunque viene impiegato in maniera simile in diversi punti e che non coinvolge direttamente la logica di business del nostro componente
I classici esempi che vengono utilizzati nella “letteratura AOP” sono il logging, la connessione ai database, le transazioni etc. etc. Queste funzionalità sono tendenzialmente trasversali al nostro software, vengono richiamate in diversi punti e chiaramente non sono il “core business” delle nostre componenti software, ma degli aspetti trasversali che utilizziamo. Se analizziamo un nostro componente di business, possiamo identificare una serie di aspetti trasversali e isolandoli vediamo che poi la logica vera e propria è ristretta in una parte minore del nostro componente.
Oltre ai classici esempi di aspetti che vengono illustrati, dobbiamo chiaramente ampliare il discorso al business della nostra applicazione per renderci conto degli effettivi benefici che potrebbe introdurre questo paradigma di sviluppo. Immaginiamo che la nostra applicazione gestisca dei clienti di tipo A, B e C. Su questi clienti vengono effettuate una serie di operazioni differenti, ma alla base di ogni funzionalità abbiamo comunque la necessità di reperire le informazioni complete di questo cliente.
Questo potrebbe essere un aspetto della nostra applicazione, ovvero il fatto di operare su informazioni complete di un cliente è un aspetto trasversale a tutte le funzionalità del nostro software, quindi può essere gestito in maniera univoca ed essere applicato nei punti di interesse dei nostri componenti.
Spring AOP
Spring basa tutte le sue funzionalità sul paradigma del Dependency injection (una tecnica di IoC, Inversion of control) che permette di slegare la creazione e le dipendenze relative ad un oggetto. In un framework simile l’utilizzo di AOP è chiaramente consigliato, anche perchè questo paradigma è un ulteriore modo per creare un basso accoppiamento tra le funzionalità del nostro software.
In Spring possiamo utilizzare diverse tecniche per implementare l’AOP, alcune derivanti da una vecchia versione di API della 1.2, altre nuove introdotte con la 2.0 oppure sfruttando direttamente il supporto di AspectJ, una libreria che permette di definire gli aspetti con una sua sintassi particolare. Prima di sporcarci le mani con codice e configurazioni dobbiamo però capire alcune terminologie chiave dell’AOP, presenti anche in Spring.
Nell’immagine precedente vengono riportate alcuni termini dell’AOP che dobbiamo conoscere (soprattutto per non perderci quando andiamo a leggere documentazione a riguardo). Il JoinPoint è un particolare punto del nostro codice dove possiamo inserire in maniera trasversale un aspetto.
Advice invece mappa la logica relativa all’aspetto: se stiamo parlando di un aspetto relativo al logging, nel rispettivo Advice troveremo la logica che permette di fare il logging. Infine abbiamo il Pointcut, che è il modo in cui applichiamo un determinato Advice ai nostri componenti.
Le nostre classi
Definiamo prima di tutto l’interfaccia sulla quale andremo ad operare, LibreriaInterface
package com.javastaff.testaop; import java.util.List; public interface LibreriaInterface { public List elencaLibri(); public void inserisciLibri(List libri); public void rimuoviLibri(List libri); public void metodoDaMonitorare() throws Exception; public void setLibri(List libri); public List getLibri(); }
Questa è un’interfaccia che utilizzeremo per creare un proxy alle chiamate tramite Spring. La classe che implementa questa interfaccia, Libreria riporta semplicemente l’implementazione dei vari metodi
package com.javastaff.testaop; import java.util.ArrayList; import java.util.List; public class Libreria implements LibreriaInterface { private ArrayList libri; public List elencaLibri() { return libri; } public void inserisciLibri(List libri) { } public void metodoDaMonitorare() throws Exception { int a = 4 / 0; } public void rimuoviLibri(List libri) { } public List getLibri() { return libri; } public void setLibri(List libri) { this.libri = (ArrayList) libri; } }
L’ultima cosa che dobbiamo definire è semplicemente il mapping del concetto di libro, che verrà gestito nel ArrayList presente nella classe Libreria
package com.javastaff.testaop; public class Libro { private String titolo; private String autore; private double prezzo; public String getTitolo() { return titolo; } public void setTitolo(String titolo) { this.titolo = titolo; } public String getAutore() { return autore; } public void setAutore(String autore) { this.autore = autore; } public double getPrezzo() { return prezzo; } public void setPrezzo(double prezzo) { this.prezzo = prezzo; } }
Questo è il dominio della nostra applicazione, ora passiamo alla definizione dei diversi aspetti e delle relative configurazioni/classi
Definizioni degli Advise
In Spring abbiamo la possibilità di utilizzare diversi tipi di Advise. Quelli che riportiamo qui di seguito lavorano direttamente sui metodi, permettendoci di inserire degli aspetti relativamente a dei metodi che vengono chiamati
- org.springframework.aop.MethodBeforeAdvice: Questo advice ci permette di intercettare la chiamata prima che venga eseguita, in modo tale da inserire un aspetto in questo istante
- org.springframework.aop.AfterReturningAdvice: Da utilizzare per inserire un aspetto quando un determinato metodo termina la sua esecuzione
- org.springframework.aop.ThrowsAdvice: Può essere utile per intercettare il momento in cui vengono sollevate eccezioni
- org.springframework.intercept.MethodInterceptor: Questo advice è molto interessante perchè ci permette di gestire sia quello che succede prima e dopo di un metodo, sia di cambiare il tipo di ritorno
Nel nostro codice vogliamo prima di tutto inserire un aspetto che riguarda il logging, quindi definiremo una classe che implementa MethodBeforeAdvice
package com.javastaff.testaop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class LogAdvice implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.print("Classe: " + arg2.getClass()); System.out.println(" - Metodo: " + arg0.getName()); } }
Con questa classe potremo loggare le informazioni relative allo stack di chiamata nel nostro codice. Ora vogliamo anche gestire gli errori su alcuni tipi di metodi (che vedremo nel seguente paragrafo di configurazione), quindi definiamo una classe che implementa ThrowsAdvice.Questa interfaccia è vuota, perchè ci permette di definire N diversi metodi per gestire N diversi tipi di errore. In questo caso avremo una definizione globale a livello di Exception
package com.javastaff.testaop; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class ErrorAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Exception throwable) { System.out.print("Errore: " + throwable); System.out.println("Classe: " + target.getClass() + " - Metodo: " + method.getName()); System.out.println("Comunicazione al sistema esterno"); } }
Infine definiamo un Advice che ci permette di caricare i libri all’interno della nostra classe Libreria, successivamente poi definiremo l’aspetto che intercetta le chiamate al metodo elencaLibri di questa interfaccia. In questo modo sleghiamo il nostro codice dal caricamento dei libri e quindi possiamo lavorare tranquillamente con i libri, senza preoccuparci dell’aspetto relativo al caricamento
package com.javastaff.testaop; import java.lang.reflect.Method; import java.util.ArrayList; import org.springframework.aop.MethodBeforeAdvice; public class LoadAdvice implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { ArrayList lista = new ArrayList(); Libro l1 = new Libro(); l1.setTitolo("1984"); l1.setAutore("George Orwell"); l1.setPrezzo(20.0); lista.add(l1); Libro l2 = new Libro(); l2.setTitolo("Il nome della rosa"); l2.setAutore("Umberto Eco"); l2.setPrezzo(16.0); lista.add(l2); ((LibreriaInterface) arg2).setLibri(lista); System.out.println("Caricamento effettuato"); } }
Configurazione di Spring AOP
La configurazione che viene ora riportata è quella che possiamo fare all’interno di un classico file di Spring. Essendoci diverse cose da chiarire, prima vediamo il file per intero e poi scendiamo nel dettaglio
<!--?xml version="1.0" encoding="UTF-8"?--> <beans> <bean id="libri" class="java.util.ArrayList"/> <bean id="libreria" class="com.javastaff.testaop.Libreria"> <property name="libri" ref="libri"> </property> </bean> <bean id="libreriaProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value> com.javastaff.testaop.LibreriaInterface </value> </property> <property name="target"> <ref local="libreria"> </ref></property> <property name="interceptorNames"> <list> <value>logAdvisor</value> <value>errorAdvisor</value> <value>loadAdvisor</value> </list> </property> </bean> <bean id="logAdvice" class="com.javastaff.testaop.LogAdvice"/> <bean id="loadAdvice" class="com.javastaff.testaop.LoadAdvice"/> <bean id="errorAdvice"class="com.javastaff.testaop.ErrorAdvice"/> <bean id="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <constructor-arg> <ref local="logAdvice"> </ref></constructor-arg> <property name="pattern"> <value>.*</value> </property> </bean> <bean id="loadAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <constructor-arg> <ref local="loadAdvice"> </ref></constructor-arg> <property name="pattern"> <value>.*elencaLibri</value> </property> </bean> <bean id="errorAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <constructor-arg> <ref local="errorAdvice"> </ref></constructor-arg> <property name="pattern"> <value>.*Monitorare.*</value> </property> </bean> </beans>
La prima cosa che viene fatta è la definizione del bean Libreria, con il riferimento all’attributo lista. Successivamente definiamo il proxy attraverso il quale utilizzeremo questo bean. La classe proxy è definita proprio all’interno di Spring, org.springframework.aop.framework.ProxyFactoryBean, e ci permette di caricare il bean ed di collegare ad esso gli aspetti.
Nella definizione del proxy indichiamo il bean a cui facciamo riferimento, Libreria, l’interfaccia LibreriaInterfaccia e gli Advisor da collegare a questo. L’Advisor, riguardando il diagramma iniziale, è l’unione tra Advice e Pointcut. Successivamente infatti vengono definiti tutti gli Advisor e per ognuno prima si definisce l’Advice che gestirà l’aspetto (le varie classi *Advice che abbiamo definito precedentemente) e un pattern che ci permette di definire a quali metodi applicare l’Advice.
In questo caso abbiamo utilizzato org.springframework.aop.support.RegexpMethodPointcutAdvisor che ci permette di definire un espressione regolare per identificare i metodi a cui applicare i nostri aspetti. Ad esempio nel caso del logging abbiamo utilizzato la regex .* che praticamente identifica ogni metodo. Questo non è l’unico modo di definire dei pointcut, per gli altri metodi vi rimando alla documentazione ufficiale di Spring AOP
Il test
Arrivati alla fine di questo articolo vogliamo giustamente vedere quale è il risultato quando andiamo ad utilizzare Spring AOP con questa configurazione. Vediamo quindi la classe di test che ci permette di analizzare quanto fatto
package com.javastaff.testaop; import java.util.ArrayList; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAOP { public static void main(String a[]) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"spring-beans.xml"}); LibreriaInterface libreria = (LibreriaInterface) applicationContext.getBean("libreriaProxy"); ArrayList lista = (ArrayList) libreria.elencaLibri(); System.out.println("Libri presenti " + lista.size()); libreria.inserisciLibri(null); libreria.rimuoviLibri(null); libreria.metodoDaMonitorare(); } }
Prima di tutto dobbiamo caricare il file di configurazione di Spring che abbiamo definito precedentemente. Grazie a questo file possiamo ottenere il bean proxy. Con gli aspetti che abbiamo definiti ora avremo il logging su tutti metodi che andiamo a richiamare, avremo l’inizializzazione della variabile ArrayList quando dovremo elencare i libri e infine gestiremo il metodoDaMonitorare comunicando l’errore avvenuto. Avviando la classe avremo il seguente output
Classe: class com.javastaff.testaop.Libreria - Metodo: elencaLibri Caricamento effettuato Libri presenti 2 Classe: class com.javastaff.testaop.Libreria - Metodo: inserisciLibri Classe: class com.javastaff.testaop.Libreria - Metodo: rimuoviLibri Classe: class com.javastaff.testaop.Libreria - Metodo: metodoDaMonitorare Errore: java.lang.ArithmeticException: / by zeroClasse: Classe: com.javastaff.testaop.Libreria - Metodo: metodoDaMonitorare Comunicazione al sistema esterno
Conclusione
Abbiamo analizzato uno dei tanti modi che offre Spring per definire degli aspetti nel nostro codice tramite Spring AOP. Gli aspetti che abbiamo definito possono non essere molto significativi e chiaramente questa tecnica dovrebbe poi essere calata di volta in volta nella logica di business del nostro applicativo
Riferimenti
Documentazione ufficiale Spring AOP
Implement crosscutting concerns using Spring 2.0 AOP
The AOP@Work series
Spring in Action, Second Edition

Looking for a right “about me”…
Commenti recenti