Introduzione all'Aspect Oriented Programming con Spring AOP

Mai sentito parlare di AOP (Aspect Oriented Programming)?

In questo post scopriamo di cosa si tratta e vediamo come applicare questo interessante paradigma di programmazione ai nostri progetti grazie a Spring AOP.

Cos’è l’Aspect Oriented Programming?

La programmazione orientata agli aspetti è un paradigma di programmazione complementare al classico paradigma ad oggetti. L’obiettivo dell’AOP è quello di separare funzionalità trasversali all’applicazione dalla logica di business.

Questo oltre a garantire separation of concerns e riutilizzo del codice, permette di non “sporcare” il codice principale con funzionalità non strettamente appartenenti alla business logic.

Per meglio comprendere il funzionamento dell’AOP, di seguito ne esaminiamo alcuni concetti fondamentali:

  • Aspect: rappresenta una funzionalità trasversale al progetto. Esempi tipici sono la gestione delle transazioni o il logging. Solitamente troviamo la logica relativa a queste funzionalità ripetuta e frammentata all’interno del codice che implementa la logica di business. Un aspect può essere visto come la combinazione di un pointcut e di un advice.

  • Join point: rappresenta il punto, all’interno della logica di business, in cui va applicata la logica “trasversale” dall’aspetto. Per usare un gergo ancora più specifico, si tratta del punto di esecuzione di un advice. Questo “punto” può essere rappresentato, ad esempio, dall’ingresso o dall’uscita del thread in esecuzione da un metodo.

  • Pointcut: è l’espressione che identifica il join point all’interno della logica di business. Il pointcut è spesso rappresentato da una regex che matcha il punto nel codice in cui andrà eseguito l’advice.

  • Advice: è l’azione eseguita dall’aspect in corrispondenza di un join point. Esistono differenti tipi di advice, identificati dal “momento” in cui la logica dello stesso verrà eseguita rispetto al join point, ad esempio around, before o after.

Dopo questo bagno di termini astrusi e poco intuitivi, passiamo a qualcosa di più concreto e vediamo come sfruttare l’AOP con Spring framework.

Spring AOP vs AspectJ

Prima di mettere mano al codice occorre fare una piccola premessa.

Spring mette a disposizione una soluzione AOP chiamata… Spring AOP.

Spring AOP è un framework AOP con funzionalità limitate… o meglio, il suo obiettivo (come dichiarato nella documentazione ufficiale), non è quello di competere con progetti come AspectJ, ma quello di fornire un set di funzionalità utili a risolvere le problematiche tipiche di un’applicazione enterprise.

In particolare, Spring AOP supporta solamente join points a livello di metodo, contrariamente ad AspectJ che permette d’implementare soluzioni più granulari.

Allo stesso tempo però Spring AOP supporta l’utilizzo di annotazioni in stile AspectJ per la definizione degli aspetti e dei pointcut. In questo post esamineremo proprio questo approccio.

Ma ora passiamo all’azione! E per prendere confidenza con AOP e Spring AOP andiamo ad aggiungere un semplice aspect alla nostra celebre e pluripremiata SuperRubricaREST! :-D

Dipendenze

Per poter utilizzare Spring AOP nel nostro progetto dovremo aggiungere l’apposito starter di Spring Boot tra le dipendenze del nostro pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Nota Poiché stiamo utilizzando Spring Boot, le annotazioni AspectJ-style saranno abilitate in automatico tramite il meccanismo di auto-configuration, senza la necessità di annotare la nostra applicazione (o una delle sue @Configuration) con @EnableAspectJAutoProxy.

Creiamo un aspect

A questo punto siamo pronti a creare la nostra “classe-aspetto”; partiamo con un esempio super-semplice.

Supponiamo di voler loggare ogniqualvolta venga invocato un metodo della classe ContactService, già presente nel nostro progetto. Il codice che andremo a scrivere sarà qualcosa di simile:

@Component
@Aspect
@Slf4j
public class LoggingAspect {

    @Before("execution(* com.davioooh.srr.services.ContactService.*(..))")
    public void logBeforeContactServiceMethoodsInvocation(JoinPoint joinPoint){
        log.trace("Invoking method: " + joinPoint.getSignature());
    }
}

Andiamo con ordine:

  • La classe LoggingAspect viene dichiarata come un classico @Component, per cui sarà gestita dallo Spring IoC Container e individuata tramite component scanning.
  • L’annotazione @Aspect identifica la classe come aspetto: questo ulteriore marker serve a Spring per evitare che la logica di un aspect (il suo advice) non venga inavvertitamente eseguita su altri aspect.
  • L’annotazione @Slf4j è un’utilità di Lombok che crea automaticamente un logger per la nostra classe. Niente di essenziale… ma comodo.
  • L’annotazione @Before serve a dichiarare un advice (il metodo logBeforeContactServiceMethoodsInvocation) di tipo before, ovvero la cui esecuzione avviene prima del join point. L’elemento value di quest’annotazione esprime il pointcut dell’aspetto (tra poco ne esamineremo la sintassi).
  • Il paremetro di tipo JoinPoint del metodo logBeforeContactServiceMethoodsInvocation, infine, permette di recuperare informazioni relative al metodo target dell’aspetto.

Dichiarare un pointcut

Il tema della dichiarazione dei pointcut con Spring AOP è un mondo a sé e non ho la pretesa di parlarne in modo esaustivo in questo post; rimando invece alla documentazione ufficiale di Spring AOP e alla programming guide di AspectJ.

Cerchiamo però di comprendere la sintassi generale di un pointcut esaminando l’esempio precedente:

execution(* com.davioooh.srr.services.ContactService.*(..))

L’espressione utilizzata nel nostro aspect può essere descritta in questo modo:

esegui l’advice ogni volta che viene invocato un qualsiasi metodo della classe ContactService

Diamo un’occhiata più da vicino.

execution è un designator (nel gergo di AspectJ) che serve ad agganciare l’esecuzione di un metodo. Esistono diversi designator; execution è quello utilizzato nella maggior parte dei casi.

Per specificare il metodo (o i metodi) target dell’advice, andiamo ad utilizzare una sintassi specifica di AspectJ che descrive la firma di tali metodi:

* com.davioooh.srr.services.ContactService.*(..)

Riprendendo l’esempio precedente notiamo diversi elementi:

  • il primo asterisco sta ad indicare che cercheremo corrispondenza con metodi che anno un qualsiasi valore di ritorno;
  • com.davioooh.srr.services.ContactService è, semplicemente, il fully-qualified name della classe all’interno della quale sono definiti i metodi che ci interessa matchare;
  • .* significa che c’interessano tutti i metodi della classe precedentemente indicata;
  • (..) indica che c’interessano tutti i metodi, indipendentemente dal numero e dal tipo dei parametri accettati.

Testiamo il tutto

Siamo pronti a fare un giro!

Lanciamo la nostra applicazione avendo cura di configurare un opportuno logging level, così da visualizzare correttamente i log generati dal nostro aspect.

Mettiamo mano al fedele Postman (o ad un client HTTP equivalente) e facciamo una richiesta al nostro REST service.

Ad esempio, la request /contacts che restituisce la lista di tutti i contatti fa riferimento al metodo getList del ContactService. Se tutto va liscio dovremmo trovare nella console qualcosa di simile:

2019-03-18 17:07:25.228 TRACE 10320 --- [nio-8080-exec-1] com.davioooh.srr.aspects.LoggingAspect   : Invoking method: List com.davioooh.srr.services.ContactService.getList()

Allo stesso modo, anche le altre richieste relative ai Contact dovrebbero generare una riga di log con l’indicazione del metodo invocato.

In conclusione

L’Aspect Oriented Programming è un tema davvero vasto ed interessante che vale la pena di conoscere ed approfondire.

In questo post ho cercato di condensare alcuni dei concetti principali e di fornire un esempio pratico dell’applicazione di Spring AOP ad un progetto web.

Spero di avervi incuriosito e di avervi dato gli input giusti per proseguire lo studio di Spring AOP. ;-)

A presto,

David