Introduction à la programmation orientée aspect avec Spring
- Compréhension du concept
La programmation orientée aspect (AOP) permet d'ajouter des fonctionnalités transversales à une application sans modifier le code source existant. L'idée principale consiste à injecter des comportements supplémentaires à certains points du flux d'exécution, sans polluer la logique métier principale.
Avantages
- Découplage du code : les fonctionnalités transversales sont isolées dans des aspects distincts.
- Maintenabilité : chaque préoccupations est gérer de manière indépendante.
- Réutilisabilité : les aspects peuvent être appliqués à plusieurs modules.
- Terminologie essentielle
Prenons l'exemple d'un inspecteur qui vérifie les compteurs électriques chez les abonnés.
Conseil (Advice) :
L'inspecteur sait quelles tâches effectuer et à quel moment. De même, un advice définit quoi faire et quand l'exécuter dans le contexte d'un aspect.
Cinq types de conseil existent
- Before : s'exécute avant la méthode cible.
- After : s'exécute après la méthode目标, quelle que soit l'issue.
- After-returning : s'exécute après le succès de la méthode.
- After-throwing : s'exécute si une exception est levée.
- Around : enveloppe l'exécution de la méthode, permettant d'intervenir avant et après.
Point de jointure (Join Point) :
Chaque habitation peut être inspectée. Dans une application, les points de jointure représentent les moments où un aspect peut s'intercaler : appels de méthodes, levées d'exceptions, etc.
Point de coupe (Pointcut) :
Un inspecteur ne visite pas tous les abonnés. Les pointcuts permettent de sélectionner précisément quels points de jointure seront affectés par un aspect. Ils définissent le « où » à travers des expressions de correspondantion sur les noms de classes et méthodes.
Aspect :
L'aspect combine le conseil (quoi faire) et le pointcut (où le faire). C'est l'unité modulaire qui encapsule le comportement transversal.
Introduction :
Cette fonctionnalité permet d'ajouter de nouvelles méthodes ou propriétés à des classes existantes sans modification directe du code source.
- Support Spring AOP
Spring implémente AOP en utilisant des proxys dynamiques au moment de l'exécution. Le framewokr ne gère que les points de jointure au niveau des méthodes. Cette approche offre une intégration transparente avec le conteneur IoC de Spring.
- Expressions de pointcut avec AspectJ
Spring utilise la syntaxe AspectJ pour définir les pointcuts :
| Indicateur | Description |
|---|---|
| args() | Correspond aux méthodes dont les paramètres sont d'un type spécifié |
| @args() | Correspond aux méthodes dont les paramètres sont annotés |
| execution() | Définit la correspondance principale |
| this() | Correspond aux beans proxy d'un type donné |
| target() | Correspond aux objets cibles d'un type spécifié |
| @target() | Correspond aux objets annotés d'un type donné |
| within() | Limite la correspondance à un package |
| @within() | Correspond aux classes annotées |
| @annotation | Correspond aux éléments annotés |
L'indicateur execution est le seul qui effectue réellement la correspondance ; les autres servent de filtres.
4.1 Exemple pratique
Configuration Maven nécessaire :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9</version>
</dependency>
Définition d'une interface de service :
public interface ServiceCalcul {
int calculer(int valeur);
}
Expression de pointcut permettant d'intercepter l'appel :
execution(* math.ServiceCalcul.calculer(..))
Explications :
- execution : interception au moment de l'exécution
- ***** : tout type de retour
- math.ServiceCalcul : classe cible
- calculer : méthode concernée
- (..) : peu importe les paramètres
Limitation de la portée avec with in :
execution(* math.ServiceCalcul.calculer(..))
&& within(math.*)
4.2 Sélection par bean
L'indicateur bean permet de cibler un bean spécifique par son identifiant :
execution(* math.ServiceCalcul.calculer())
&& bean('calculateurPrincipal')
4.3 Création d'un aspect avec annotations
Les annotations AspectJ simplifient considérablement la déclaration des aspects.
Définition de l'aspect pour un service de logging :
package com.example.math;
import org.aspectj.lang.annotation.*;
@Aspect
public class Journaliseur {
@Before("execution(* com.example.math.ServiceCalcul.calculer(..))")
public void initialiserJournal() {
System.out.println("Démarrage du calcul");
}
@Before("execution(* com.example.math.ServiceCalcul.calculer(..))")
public void verifierParametres() {
System.out.println("Vérification des paramètres");
}
@AfterReturning("execution(* com.example.math.ServiceCalcul.calculer(..))")
public void enregistrerResultat() {
System.out.println("Calcul terminé avec succès");
}
@AfterThrowing("execution(* com.example.math.ServiceCalcul.calculer(..))")
public void gererErreur() {
System.out.println("Une erreur s'est produite");
}
}
Configuration Spring :
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConfigurationApplication {
@Bean
public Journaliseur journaliseur() {
return new Journaliseur();
}
@Bean
public ServiceCalcul calculateur() {
return new CalculateurImplementation();
}
}
Implémentation du service :
package com.example.math;
public class CalculateurImplementation implements ServiceCalcul {
@Override
public int calculer(int valeur) {
System.out.println("Exécution du calcul pour : " + valeur);
return valeur * 2;
}
}
Test d'exécution :
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ProgrammeTest {
public static void main(String[] args) {
ApplicationContext contexte = new AnnotationConfigApplicationContext("com.example.math");
ServiceCalcul service = contexte.getBean(ServiceCalcul.class);
int resultat = service.calculer(5);
}
}
Résultat obtenu :
Démarrage du calcul Vérification des paramètres Exécution du calcul pour : 5 Calcul terminé avec succès