Introduzione a Spring AOP

Aspect Oriented Programming

La programmazione orientata agli aspetti è un paradigma di programmazione che ha come obiettivo la modularizzazione del software attraverso la creazione di entità software, denominate aspetti, che intervengono nell’interazione tra oggetti al fine di eseguire compiti comuni.

Vedremo che con questo paradigma sarà possibile aggiungere comportamento ad un codice esistente senza andare a modificare il codice stesso. E lo faremo specificando separatamente quale codice viene modificato attraverso la definizione di un pointcut. Questo permette ai comportamenti che non rientrano nella logica di business (come il logging) di essere aggiunti ad un programma senza incasinare il codice.

Formalmente un Aspect è una classe java che espone metodi chiamati advice. Un pointcut invece è una espressione che indica quale metodo della logica di business è interessato dallo specifico aspetto. Tuttavia un punto da notare è che esso non indica mai quale metodo dell’aspect (advice) deve essere applicato. Ma semplicemente, attraverso l’interpretazione dell’espressione, indica i metodi della logica di business in cui applicare un advice. In breve, è una sorta di selettore di metodi della logica di business nel suo complesso.

La tabella seguente mostra le tipologie di advice messe a disposizione dal framework Spring AOP.

Before Advice Viene eseguito prima dell’esecuzione del metodo di business e una volta completata l’esecuzione dell’advice, l’elaborazione del metodo avverrà automaticamente.
After Advice Viene eseguito dopo l’esecuzione del metodo di business e una volta che l’esecuzione del metodo è terminata, l’esecuzione dell’advice avrà luogo automaticamente.
Around Advice Viene eseguito in due momenti: prima dell’esecuzione del metodo di business e dopo.
After Returning Advice Viene eseguito dopo l’esecuzione del metodo di business ma solamente quando questo ritorna con successo.
After Throwing Advice Viene eseguito dopo l’esecuzione del metodo di business ma solo se questo fallisce.

 

Implementazione in Spring Boot

Generalmente esistono  due modi per implementare AOP in un’applicazione Spring. Il primo, più classico, consiste nell’utilizzare un file di configurazione XML in cui vengono dichiarati i vari concetti dell’AOP. Il secondo, più comune, fa uso delle annotazioni della libreria AspectJ. Si tratta di una libreria di programmazione orientata agli aspetti (AOP) specifica per il linguaggio di programmazione Java, ampiamente utilizzate grazie alla sua semplicità e l’usabilità.

Vediamo quindi come utilizzare tale libreria in un progetto Spring Boot. Procediamo creano un progetto base attraverso il tool Spring Initializr, senza aggiungere alcuna dipendenza. Quindi aggiungiamo al pom.xml generato, la dipendenza a spring-boot-starter-aop.

Per utilizzare l’AOP è necessario annotare la classe di configurazione del progetto con @EnableAspectJAutoProxy, la quale abiliterà Spring a riconoscere e gestire l’annotaziona @Aspect, che vedremo nel prossimo paragrafo.

Aspect, Pointcut e Advice

L’annotazione @Aspect, inserita nella dichiarazione di una classe, serve ad identificarla come classe aspect. Al suo interno sono poi definiti i pointcut e gli advice attraverso l’annotazione dei suoi metodi rispettivamente con @Pointcut o una qualsiasi delle annotazioni advice (@Before, @After, @AfterReturning, @AfterThrowing e @Around).

Prima però di vedere tali annotazioni all’opera consideriamo un caso abbastanza comune di classe di business InvoiceService che implementa un metodo save(), in cui si accede ad una base dati per inserire un nuovo record. 

Trattandosi di un accesso al DB in scrittura, si ha la necessità di operare in un contesto transazionale, che può essere gestito interamente all’interno del metodo save(), oppure, attraverso AOP, creando e distruggendo tale contesto ogni volta che il metodo è invocato. Per farlo introduciamo la classe InvoiceAspect ed il pointcut relativo al metodo che vogliamo intercettare, nel modo seguente:

Come ci aspettavamo la classe è un componente Spring annotata con @Aspect. Il metodo pointcut1() è invece annotato con @Pointcut, annotazione che richiede l’utilizzo di una pointcut expression. Non aprofondiamo in questo articolo la sintassi delle espressioni pointcut, ci basti sapere per ora che execution(...) identifica esattamente l’esecuzione del metodo specificato. Nel nostro esempio, quindi, il pointcut identifica esattamente l’esecuzione del metodo save(). Esiste però la possibilità di utilizzare delle wildcard, in modo da rendere il pointcut meno specifico. Ad esempio, volendo intercettare l’esecuzione di tutti i metodi della classe InvoiceService, si può utilizzare l’espressione: "execution(* it.javaboss.invoiceaop.service.InvoiceService.*(..))").

Gestione delle Transazioni

Per gestire il contesto transazionale dobbiamo implementare tre metodi nella classe InvoiceAspect che dovranno rispettivamente: aprire una transazione, eseguire il commit nel caso in cui l’esecuzione del metodo save() vada a buon fine ed infine chiudere la transazione. La classe quindi si presenterà nel seguente modo:

Si noti che le tre annotazioni advice @Before, @After e @AfterReturning richiedono anch’esse la definizione di una poitcut expression, che nel caso specifico identifica esattamente l’espressione definita in pointcut1(). Nulla ci vieta però di utilizzare direttamente l’espressione definita sul metodo pointcut1() nel’annotazione advice. Quindi ad esempio potremmo avere: 

Per eseguire il test di quanto implementato utilizziamo un componente  che implementa l’interfaccia Spring CommandLineRunner, e che verrà quindi eseguito immediatamente all’avvia dell’applicazione:

Una volta eseguita l’applicazione troveremo nell standard output i messaggi mostrati di seguito, in cui è evidente che l’esecuzione del metodo di business è preceduta dall’esecuzione del metodo che si occupa di aprire la transazione e seguita da quelli che, rispettivamente, eseguono il commit e chiudono la transazione.

Gestione delle Eccezioni

Naturalmente immaginare che un metodo di business non vada mai in eccezione non è pensabile, e la gestione dei casi eccezionali è molto importante, soprattutto se abbiamo a che fare con accessi al DB. Dobbiamo infatti prevedere la possibilità di eseguire il rollback della transazione nel caso in cui il metodo non termini correttamente. Per farlo ci viene in aiuto l’annotazione @AfterThrowing, che può essere utilizzata per identificare l’operazione da eseguire in caso di eccezione. Implementiamo quindi il metodo rollbackTransaction():

Si noti che il metodo rollbackTransaction() sarà eseguito esclusivamente nel caso in cui sia sollevata una eccezione durante l’esecuzione del metodo save(), e che inoltre il metodo commitTransaction() non sarà eseguito in tale eventualità. Quello che ci aspettiamo di vedere sulla console in caso di eccezione saranno i seguenti messaggi:

 

 @AfterReturning e @AfterThrowing

Le annotazioni @AfterReturning e @AfterThrowing hanno la particolarità di poter intercettare rispettivamente l’oggetto restituito dal metodo o l’eccezione sollevata. Ad esempio consideriamo il seguente metodo advice:

Come è facile comprendere l’annotazione ha una poitcut expression che intercetta l’esecuzione di tutti i metodi find della classe di business. Ma la peculiarità è che, attraverso il parametro returning è possibile indicare quale, tra i parametri del metodo getAdviceReturnValue, dovrà contenere il risultato dell’elaborazione del metodo find(). Quindi, ad esempio, se si immagina di invocare findById( 10L ), sulla console troveremo il messaggio:

Returning Value is : Invoice Id 10

In modo del tutto analogo possiamo procedere con l’annotazione @AfterThrowing con l’eccezione che questa volta andremo ad indicare il parametro destinato a contenere l’oggetto Throwable rappresentativo dell’eccezione sollevata.

ProceedingJoinPoint

L’oggetto ProceedingJoinPoint può essere utilizzato come argomento di un metodo advice per accedere ad informazioni relativi al junction point ed al metodo intercettato. La classe dispone di diversi metodi (ereditati dalla classe JoinPoint) tra i quali:

  • getTarget() – restituisce l’oggetto/componente intercettato;
  • getSignature() – restituisce la firma del metodo invocato sull’oggetto target;
  • getArgs() – restituisce gli argomenti del metodo invocato.

Supponiamo ad esempio di avere nella classe InvoiceService un metodo update() così definito:

Aggiungiamo quindi alla classe InvoiceAspect il seguente pointcut e metodo advice annotato con @Around:

Si noti che il metodo proceed() svolge un ruolo importante in questa tipologia di advice; esso infatti consente di procedere nell’esecuzione del metodo intercettato. Eseguendo il codice verranno stampati nella console i seguenti messaggi:

 

Il Codice Sorgente

Il codice sorgente del progetto è scaricabile qui invoce-aop.

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 5

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!