C’est un paradigme qui permet de séparer les préoccupations transversales (comme la journalisation, la sécurité, les transactions, etc.) du code métier principal. L’idée est d’éviter de répéter du code similaire dans plusieurs classes en le centralisant dans des aspects.
a. Préoccupations transversales (Cross-Cutting Concerns)
Ce sont des fonctionnalités qui traversent plusieurs modules de ton application, comme :
Un aspect est un module qui encapsule une préoccupation transversale. Il contient :
Les types de conseils en POA :
Définit où l’aspect s’applique, par exemple :
Prenons un exemple simple :
journaliser (logger) les appels aux méthodes du service AdherentService pour suivre leur exécution.
Étape 1 : Ajouter la dépendance Spring AOP
Dans le fichier pom.xml (Maven) :
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
Étape 2 : Créer un Aspect pour la Journalisation
Crée une classe LoggingAspect dans le package com.formation.aspect :
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect // Déclare cette classe comme un aspect @Component // Enregistre le bean dans Spring public class LoggingAspect { private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); // Définir un point de coupe pour toutes les méthodes du package service @Pointcut("execution(* com.philb.service.*.*(..))") public void serviceMethods() {} // Conseiller (@Before) : Log avant l'exécution de la méthode @Before("serviceMethods()") public void logBeforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); logger.info("Début de l'exécution de {}.{}({})", className, methodName, joinPoint.getArgs()); } // Conseiller (@AfterReturning) : Log après l'exécution réussie @AfterReturning(pointcut = "serviceMethods()", returning = "result") public void logAfterMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); logger.info("Fin de l'exécution de {}.{}({}) -> Résultat : {}", className, methodName, joinPoint.getArgs(), result); } // Conseiller (@AfterThrowing) : Log si une exception est levée @AfterThrowing(pointcut = "serviceMethods()", throwing = "exception") public void logAfterThrowing(JoinPoint joinPoint, Exception exception) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); logger.error("Exception dans {}.{}({}) : {}", className, methodName, joinPoint.getArgs(), exception.getMessage()); } }
Étape 3 : Tester l’Aspect
Voici un exemple de service AdherentService où l’aspect sera appliqué :
import org.springframework.stereotype.Service; @Service public class AdherentService { public String createAdherent(String nom) { // Logique de création d'un adhérent return "Adhérent " + nom + " créé avec succès !"; } public String getAdherent(Long id) { if (id < 1) { throw new IllegalArgumentException("L'ID doit être positif"); } return "Adhérent avec ID " + id; } }
Étape 4 : Résultat dans les Logs
Quand on appelle les méthodes de AdherentService, on verra des logs comme ceci :
Exemple 1 : Appel réussi à createAdherent
INFO c.p.aspect.LoggingAspect - Début de l'exécution de AdherentService.createAdherent([Philippe]) INFO c.p.aspect.LoggingAspect - Fin de l'exécution de AdherentService.createAdherent([Philippe]) -> Résultat : Adhérent Philippe créé avec succès !
Exemple 2 : Appel à getAdherent avec une exception
INFO c.p.aspect.LoggingAspect - Début de l'exécution de AdherentService.getAdherent([0]) ERROR c.p.aspect.LoggingAspect - Exception dans AdherentService.getAdherent([0]) : L'ID doit être positif
a. Mesurer le Temps d’Exécution des Méthodes
Ajoutre un @Around pour chronométrer les méthodes :
@Around("serviceMethods()") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); // Exécute la méthode originale long endTime = System.currentTimeMillis(); long duration = endTime - startTime; String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); logger.info("{}.{}({}) a pris {} ms", className, methodName, joinPoint.getArgs(), duration); return result; }
b. Vérifier les Droits d’Accès
On peut créer un aspect pour vérifier si un utilisateur a les droits d’accéder à une méthode :
@Before("@annotation(com.philb.annotation.RequiresAdmin)") public void checkAdminAccess(JoinPoint joinPoint) { // Logique pour vérifier si l'utilisateur est admin boolean isAdmin = SecurityContextHolder.getContext().getAuthentication().getAuthorities() .stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")); if (!isAdmin) { throw new AccessDeniedException("Accès refusé : droits admin requis"); } }
Annotation personnalisée :
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiresAdmin {} Utilisation dans un service : @RequiresAdmin public void deleteAdherent(Long id) { // Logique de suppression }
c. Gestion des Transactions
Spring utilise déjà la POA pour gérer les transactions avec @Transactional. Quand tu annotes une méthode avec @Transactional, Spring crée un aspect qui :
Démarre une transaction avant la méthode.
Valide la transaction si la méthode se termine sans exception. Annule la transaction si une exception est levée.
Avantage :
Utilise la POA pour :
Éviter la POA :
La POA permet de centraliser du code répétitif (logs, sécurité, etc.) dans des aspects. Avec Spring AOP, on peut facilement ajouter des comportements avant, après ou autour des méthodes.
On peut utiliser la POA pour :
La POA rend le code plus propre, plus modulaire et plus facile à maintenir.