Tutoriel pour découvrir la Programmation Orientée Aspect avec Spring AOP

Ce tutoriel s'adresse aux développeurs qui souhaitent découvrir la Programmation Orientée Aspect grâce au framework Spring

Commentez

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Prérequis

Vous pouvez utiliser l'IDE que vous souhaitez pour suivre ce tutoriel. Le code source des exemples présentés dans ce tutoriel est disponible dans ce repository Git, et plus particulièrement dans cette classe.

Les outils dont vous aurez besoin pour suivre ce tutoriel sont : JDK 8 d'Oracle, Maven 3.

N'hésitez pas à lire le tutoriel Maven 3 si vous souhaitez en apprendre plus sur ce dernier.

II. Qu'est-ce que l'AOP ?

L'AOP (Aspect Oriented Programming ou « Programmation Orienté Aspect » en français) n'est pas à proprement parler un langage de programmation pour créer des programmes, c'est un concept qui permet d'exécuter une action lorsqu'un événement est déclenché (ex: une méthode qui est exécutée). On peut donc voir l'AOP comme une extension au langage de programmation utilisé dans le projet.

III. Dans quel contexte utilise-t-on l'AOP ?

Lorsque l'on programme par aspect, on distingue le code métier du code technique. L'AOP permet de factoriser le code technique du projet. Le terme crosscutting concerns (ou fonctionnalité transversale en français) est utilisé pour désigner cette partie technique.

Pour citer quelques exemples de crosscutting concerns, l'AOP peut être utilisé pour :

  • Ouvrir/fermer des ressources (ex: Démarrer et commiter/rollbacker une transaction)
  • Vérifier les permissions de l'utilisateur (sécurité)
  • Du cache
  • Des loggers
  • Du monitoring
  • Des benchmarks
  • Interrompre une méthode qui prend beaucoup de temps pour s'exécuter.
  • Dans une moindre mesure : Changer le comportement d'une méthode pour retourner un nouveau résultat.

Voici un exemple de code sans l'AOP :

 
CacherSélectionnez
public void addPermissionsToUser(Permission[] permissions, User user) 
  throws SQLException {

  try {
    // Code technique : SI aucune transaction n'est en cours ALORS
    // Code technique :     créer la transaction (cf: START TRANSACTION en SQL)
    // Code technique : FIN SI
    // Code métier    : Ajouter des droits à l'utilisateur
  } catch (SQLException e) { 
    // Code technique : SI une exception est lancée ALORS
    // Code technique :     rollbacker
    // Code technique :     propager l'exception
    // Code technique : FIN SI
  } finally {
    // Code technique : SI une exception n'est pas lancée ET la méthode courante est la dernière méthode transactionnelle exécutée ALORS
    // Code technique :     commiter
    // Code technique : FIN SI
  }
}

Voici un exemple de code avec l'AOP :

 
Sélectionnez
@Transactional
public void addPermissionsToUser(Permission[] permissions, User user) {
  // Code métier : Ajouter des droits à l'utilisateur
}

IV. Quelques dates sur l'AOP

  • Juillet 1996 : La première apparition de l'AOP est attribuée à MTS (Microsoft Transaction Service) avec Windows NT 4.0, pour avoir mis en place un système d'interception pour les transactions et la sécurité.
  • L'AOP en Java avec AspectJ :

    • 1997 : Le projet AspectJ est créé par Gregor Kiczales et ses collègues, alors employés de la société Xerox PARC.
    • 2001 : Une première version publique est publiée.
    • Décembre 2002 : Le projet est confié à la fondation Eclipse.
    • 20 décembre 2005 : L'introduction des annotations dans Java 5 (4 octobre 2004) permet au projet AspectJ 5 d'avoir une nouvelle syntaxe à base d'annotations. Il est alors possible d'inclure du code AspectJ directement dans du code Java.
  • Le framework Spring intègre le concept d'AOP avec AspectJ, mais ce dernier limite les possibilités d'AspectJ à l'exécution des méthodes publiques.

V. AspectJ avec Spring

Il existe deux moyens pour mettre en place l'AOP avec AspectJ :

  • En incluant directement les concepts AspectJ dans du code Java, votre code Java sera alors présent dans un fichier d'extension « .aj » et vous aurez besoin d'un plugin qui permettra à votre IDE de comprendre la syntaxe.
  • En utilisant des annotations Java.

Dans ce tutoriel nous verrons la deuxième solution.

V-A. Dépendances Maven

Ajoutez la dépendance ci-dessous dans votre pom.xml :

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

Utilisez l'annotation @EnableAspectJAutoProxy pour activer l'AOP. Vous pouvez placer cette annotation sur votre classe main, ou bien créer une classe de configuration comme ci-dessous :

 
Sélectionnez
// Cette classe de configuration permet d'activer les aspects
// Vous pouvez la laisser vide
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {

}

V-B. Les annotations AspectJ

@Pointcut

Crée un alias pour une portion de condition(s)

@Before

S'exécute AVANT la méthode appelée. La méthode peut prendre en paramètre un JoinPoint.

@After

Similaire à @Before mais s'exécute APRÈS la méthode appelée. Peut prendre en paramètre un JoinPoint.

@Around

S'exécute lorsqu'une méthode est appelée, permet d'exécuter du code avant et après l'exécution de la méthode appelée (comme les filtres web). La méthode peut prendre en paramètre un ProceedingJoinPoint.

V-C. Syntaxe AspectJ

Il faut placer les exemples ci-dessous dans les annotations (au choix) : @Pointcut, @Before, @Around, ou @After.

Template

expression(<method scope> <return type> <fully qualified class name>.*(parameters))

Syntaxe représentant toutes les méthodes

execution(* *(..))

Syntaxe représentant toutes les méthodes ayant une annotation

@annotation(mypackage.MyAnnotation) && execution(* *(..))

V-D. Exemple avec les loggers

Créons une classe aspect « RestLogAspect » qui va afficher une ligne de log à chaque fois qu'une route, soit une méthode annotée avec @RequestMapping, de notre @RestController sera appelée.

Notre ligne de log aura le format suivant :

 
Sélectionnez
[REST][<durée-méthode-exécutée>ms][<nom-rest-contrôleur>][<nom-méthode-http>][<chemin-http>][<nom-méthode-contrôleur>()] <valeurs-des-paramètres>

Ainsi, l'exécution de la méthode sayHello() du contrôleur ci-dessous :

 
Sélectionnez
@RestController
public class HelloWorldController {

    @RequestMapping(path = "/hello/{name}", method = RequestMethod.GET)
    public ResponseEntity<String> sayHello(@PathVariable("name") String name) {
        return new ResponseEntity<>("hello " + name, HttpStatus.OK);
    }
}

… affichera ceci :

 
Sélectionnez
[REST][31ms][HelloWorldController][GET][/hello/{name}][sayHello()] world

Implémentation de notre classe (source) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
/**
 * An aspect class for logging all routes
 *
 * @author Gokan EKINCI
 */
@Component
@Aspect
@Log
public class RestLogAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void restAnnotation() {}

    @Around("restAnnotation() && execution(* *(..))")
    public Object restLog(ProceedingJoinPoint joinPoint) throws Throwable {
        final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        final Method method = signature.getMethod();

        final Class<?> controllerClass = joinPoint.getTarget().getClass();
        final RequestMapping controllerAnnotation = controllerClass.getAnnotation(RequestMapping.class);
        final String controllerRestPath = (controllerAnnotation.path().length == 0) ? "" : controllerAnnotation.path()[0];

        final RequestMapping controllerMethodAnnotation = method.getAnnotation(RequestMapping.class);
        final String methodRestPath = (controllerMethodAnnotation.path().length == 0) ? "" : controllerMethodAnnotation.path()[0];
        final RequestMethod requestMethod = (controllerMethodAnnotation.method().length == 0) ? RequestMethod.GET : controllerMethodAnnotation.method()[0];

        final Instant start = Instant.now();

        // @Before code
        Object returnValue = null;
        Throwable throwable = null;
        try {
            returnValue = joinPoint.proceed();
        } catch (Throwable throwable1) {
            throwable = throwable1;
            log.warning(String.format("[RestLogAspect] : %s", throwable1.getMessage()));
        }
        // @After code

        final String logMessage = String.format("[REST][%sms][%s][%s][%s][%s()] %s",
            Duration.between(start, Instant.now()).toMillis(),
            controllerClass.getSimpleName(),
            requestMethod,
            controllerRestPath + methodRestPath,
            method.getName(),
            Arrays.stream(joinPoint.getArgs())
                    .map(argument -> (argument != null) ? argument.toString() : "null")
                    .collect(Collectors.joining(", "))
        );

        log.info(logMessage);

        if (throwable != null) {
            throw throwable;
        }

        return returnValue;
    }}

Ce qu'il faut retenir de cette implémentation :

  1. Nous avons une classe annotée avec @Component et @Aspect pour indiquer à Spring que nous avons un bean de type aspect. L'annotation @Log est un simple utilitaire de librairie Lombok pour éviter de créer explicitement un attribut log de type Logger, n'en prenez pas compte.
  2. Nous avons créé une méthode annotée avec @PointCut pour créer un alias « restAnnotation() », que nous utiliserons dans l'annotation @Around de la méthode aspect « restLog() ». Cette étape est facultative mais augmente la lisibilité de l'expression AspectJ utilisée dans l'annotation @Around.
  3. Nous avons créé une méthode aspect restLog() annotée avec @Around, celle-ci va se déclencher lorsque l'expression indiquée dans l'annotation va matcher avec la méthode exécutée. Dans notre cas, lorsqu'une méthode annotée avec @RequestMapping va s'exécuter, notre méthode aspectrestLog()va s'exécuter aussi.
  4. La méthode restLog() possède en paramètre le type ProceedingJoinPoint pour récupérer toutes les informations nécessaires par rapport à la méthode exécutée.

VI. Remerciements

Je tiens à remercier XXX pour la relecture technique et XXX pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2017 Gokan EKINCI. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.