Un esempio pratico di AOP in Spring
Dopo aver fatto una panoramica dell'AOP (Aspect Oriented Programming) nel precedente articolo vediamo in concreto come utilizzarla in Spring Boot.
Partiamo da un progetto sviluppato in precedenza per le nostre esercitazioni di Spring Boot.
L'unica dipendenza richiesta è lo starter spring-boot-starter-aop che non è disponibile in Spring Initializr nè in STS quindi bisogna agire manualmente sul file pom.xml aggiungendo il seguente blocco
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Passiamo al codice.
Supponiamo di voler centralizzare la gestione dei log utilizzando l'AOP.
Prima di tutto introduciamo un nuovo package aop
all'interno del quale inseriamo la classe LoggingAspect
. Il nome non è vincolante ma è conveniente renderlo esplicativo. Annotiamola con @Component
e @Aspect
.
@Component
@Aspect
public class LoggingAspect {
}
Il prossimo passo consiste nel definire i pointcut con cui selezionare i joinpoint a cui applicare i vari advice.
Per fare ciò occorre aggiungere un metodo pubblico, che possiamo chiamare p1()
e in questo caso privo di valore di ritorno e parametri.
In seguito vedremo quali parametri possono essere eventualmente specificati.
Aggiungiamo l'annotazione @Pointcut
all'interno della quale indichiamo il metodo a cui facciamo riferimento.
Sono disponibili varie opzioni, incluse le espressioni regolari, ma per questo esempio ci interessa specificare solo l'esecuzione di un metodo.
Quindi utilizziamo il seguente schema execution(specificatore_di_accesso tipo_di_ritorno nome_package.nome_classe.nome_metodo())
.
In sostanza dobbiamo indicare l'azione ovvero "execution" nell'esempio proposto e il riferimento completo al metodo con tanto di nome del package e specificatore di accesso (public, private, ecc.).
Facciamo riferimento al metodo getAllPlayers()
presente all'interno della classe service PlayerServiceDB
.
Ed ecco il codice del pointcut
@Pointcut("execution(public java.util.List<net.emmecilab.players.model.Player> net.emmecilab.players.service.PlayerServiceDB.getAllPlayers())")
public void p1() {
}
Notiamo l'indicazione dei package anche nel caso di List. Vedremo che è possibile semplificare la notazione utilizzando il carattere jolly "*".
Ora specifichiamo un advice da eseguire dopo l'invocazione del metodo in corrispondenza del precedente pointcut.
Il seguente codice
@After("p1()")
public void logGetAllPlayers() {
logger.info("Called getAllPlayers()");
}
è costituito da un banale metodo all'interno del quale è presente il codice "aggiuntivo" da eseguire dopo il metodo getAllPlayers()
e che si limita a scrivere un messaggio nei log.
Il collegamento con il pointcut avviene attraverso l'annotazione @After
in cui si riporta proprio il metodo richiesto, ovvero p1()
nel nostro caso.
Ovviamente ci saranno altre annotazioni corrispondenti alle diverse tipologie di advice e che approfondiremo in un prossimo articolo.
Per ora limitiamoci ad analizzarne solo un'altra.
Aggiungiamo il seguente codice per realizzare un nuovo advice.
@Before("p1()")
public void logJoinPointName(JoinPoint jp) {
logger.info("Called joinpoint " + jp.getSignature());
}
In questo caso il pointcut resta invariato ma il codice viene eseguito "prima" (before) dell'invocazione del joinpoint. Quest'ultimo viene passato come parametro, consentendo il recupero della firma dello stesso.
Lanciamo l'applicazione e accediamo da browser (o in alternativa da terminale con curl o con Postman) all'URL http://localhost:8080/v1/players in modo da richiamare la lista dei calciatori.
Nei log possiamo osservare le presenza dei due messaggi, prima e dopo l'esecuzione del joinpoint.
Naturalmente il discorso non si esaurisce così facilmente ma ora abbiamo delle basi da cui partire per ulteriori implementazioni.